Repository: durdyev/webdriverxx Branch: master Commit: 11c4addbb3f7 Files: 83 Total size: 200.1 KB Directory structure: gitextract_52i55o91/ ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include/ │ ├── webdriverxx/ │ │ ├── browsers/ │ │ │ ├── chrome.h │ │ │ ├── firefox.h │ │ │ ├── ie.h │ │ │ └── phantom.h │ │ ├── by.h │ │ ├── capabilities.h │ │ ├── client.h │ │ ├── client.inl │ │ ├── conversions.h │ │ ├── detail/ │ │ │ ├── error_handling.h │ │ │ ├── factories.h │ │ │ ├── factories_impl.h │ │ │ ├── finder.h │ │ │ ├── finder.inl │ │ │ ├── http_client.h │ │ │ ├── http_connection.h │ │ │ ├── http_request.h │ │ │ ├── keyboard.h │ │ │ ├── meta_tools.h │ │ │ ├── resource.h │ │ │ ├── shared.h │ │ │ ├── time.h │ │ │ ├── to_string.h │ │ │ └── types.h │ │ ├── element.h │ │ ├── element.inl │ │ ├── errors.h │ │ ├── js_args.h │ │ ├── keys.h │ │ ├── picojson.h │ │ ├── response_status_code.h │ │ ├── session.h │ │ ├── session.inl │ │ ├── types.h │ │ ├── wait.h │ │ ├── wait_match.h │ │ ├── webdriver.h │ │ └── window.h │ └── webdriverxx.h └── test/ ├── CMakeLists.txt ├── alerts_test.cpp ├── browsers_test.cpp ├── capabilities_test.cpp ├── client_test.cpp ├── conversions_test.cpp ├── element_test.cpp ├── environment.h ├── examples_test.cpp ├── finder_test.cpp ├── frames_test.cpp ├── http_connection_test.cpp ├── js_test.cpp ├── keyboard_test.cpp ├── main.cpp ├── mouse_test.cpp ├── pages/ │ ├── alerts.html │ ├── element.html │ ├── finder.html │ ├── frame1.html │ ├── frame2.html │ ├── frame3.html │ ├── frames.html │ ├── frameset.html │ ├── js.html │ ├── keyboard.html │ ├── mouse.html │ ├── navigation1.html │ ├── navigation2.html │ ├── redirect.html │ ├── session.html │ └── webdriver.html ├── resource_test.cpp ├── session_test.cpp ├── shared_test.cpp ├── to_string_test.cpp ├── wait_match_test.cpp ├── wait_test.cpp └── webdriver_test.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ================================================ FILE: .travis.yml ================================================ language: cpp compiler: - gcc - clang branches: only: - master - dev before_script: - mkdir build - cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. - phantomjs --webdriver=7777 --webdriver-loglevel=WARN & - npm install http-server -g - http-server ./test/pages --silent & script: - cmake --build . - ctest -V ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8) project(webdriverxx) if (NOT MSVC) include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) if(COMPILER_SUPPORTS_CXX11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") elseif(COMPILER_SUPPORTS_CXX0X) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") endif() endif() enable_testing() include_directories(include) add_subdirectory(test) install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION /usr/local/include/${PROJECT_NAME} FILES_MATCHING PATTERN "*") ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Sergey Kogan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Webdriver++ A C++ client library for [Selenium Webdriver](http://www.seleniumhq.org/). You can use this library in any C++ project. ## Install ```bash mkdir build cd build && cmake .. sudo make && sudo make install ``` ## A quick example ##### Dependencies You need to download and run selenium-server before run this example. http://www.seleniumhq.org/download/ ```cpp #include using namespace webdriverxx; int main() { WebDriver firefox = Start(Firefox()); firefox .Navigate("http://google.com") .FindElement(ByCss("input[name=q]")) .SendKeys("Hello, world!") .Submit(); return 0; } ``` ## Features - Chainable commands. - Value-like objects compatible with STL containers. - Header-only. - Lightweight dependencies: - [libcurl](http://curl.haxx.se/libcurl/), - [picojson](https://github.com/kazuho/picojson). - Can be used with any testing framework. - Linux, Mac and Windows. - clang (3.4), GCC (4.6) and Visual Studio (2010). ## More examples `#include ` and `using namespace webdriverxx` are assumed in all examples. ### Start browser ```cpp #include WebDriver ff = Start(Firefox()); ``` ```cpp #include WebDriver gc = Start(Chrome()); ``` ```cpp #include WebDriver ie = Start(InternetExplorer()); ``` ### Use proxy ```cpp WebDriver ie = Start(InternetExplorer().SetProxy( SocksProxy("127.0.0.1:3128") .SetUsername("user") .SetPassword("12345") .SetNoProxyFor("custom.host") )); ``` ```cpp WebDriver ff = Start(Firefox().SetProxy(DirectConnection())); ``` ### Navigate browser ```cpp driver .Navigate("http://facebook.com") .Navigate("http://twitter.com") .Back() .Forward() .Refresh(); ``` ### Find elements ```cpp // Throws exception if no match is found in the document Element menu = driver.FindElement(ById("menu")); // Returns empty vector if no such elements // The search is performed inside the menu element std::vector items = menu.FindElements(ByClass("item")); ``` ### Send keyboard input ```cpp // Sends text input or a shortcut to the element driver.FindElement(ByTag("input")).SendKeys("Hello, world!"); // Sends text input or a shortcut to the active window driver.SendKeys(Shortcut() << keys::Control << "t"); ``` ### Execute Javascript ```cpp // Simple script, no parameters driver.Execute("console.log('Hi there!')"); // A script with one parameter driver.Execute("document.title = arguments[0]", JsArgs() << "Cowabunga!"); // A script with more than one parameter driver.Execute("document.title = arguments[0] + '-' + arguments[1]", JsArgs() << "Beep" << "beep"); // Arrays or containers can also be used as parameters const char* ss[] = { "Yabba", "dabba", "doo" }; driver.Execute("document.title = arguments[0].join(', ')", JsArgs() << ss); // Even an Element can be passed to a script auto element = driver.FindElement(ByTag("input")); driver.Execute("arguments[0].value = 'That was nuts!'", JsArgs() << element); ``` ### Get something from Javascript ```cpp // Scalar types auto title = driver.Eval("return document.title") auto number = driver.Eval("return 123"); auto another_number = driver.Eval("return 123.5"); auto flag = driver.Eval("return true"); // Containers (all std::back_inserter compatible) std::vector v = driver.Eval>( "return [ 'abc', 'def' ]" ); // Elements! Element document_element = driver.Eval("return document.documentElement"); ``` ### [Wait implicitly](http://selenium-python.readthedocs.org/en/latest/waits.html) for asynchronous operations ```cpp driver.SetImplicitTimeoutMs(5000); // Should poll the DOM for 5 seconds before throwing an exception. auto element = driver.FindElement(ByName("async_element")); ``` ### [Wait explicitly](http://selenium-python.readthedocs.org/en/latest/waits.html) for asynchronous operations ```cpp #include auto find_element = [&]{ return driver.FindElement(ById("async_element")); }; Element element = WaitForValue(find_element); ``` ```cpp #include auto element_is_selected = [&]{ return driver.FindElement(ById("asynchronously_loaded_element")).IsSelected(); }; WaitUntil(element_is_selected); ``` ### Use matchers from [Google Mock](https://code.google.com/p/googlemock/) for waiting ```cpp #define WEBDRIVERXX_ENABLE_GMOCK_MATCHERS #include driver.Navigate("http://initial_url.host.net"); auto url = [&]{ return driver.GetUrl(); }; using namespace ::testing; auto final_url = WaitForMatch(url, HasSubstr("some_magic")); ``` ### Testing with real browsers Prerequisites: - [Selenium Server](http://www.seleniumhq.org/download/) ```bash selenium-server -p 4444 & ./webdriverxx --browser= ``` ## Advanced topics ### Unicode The library is designed to be encoding-agnostic. It doesn't make any assumptions about encodings. All strings are transferred as is, without modifications. The WebDriver protocol is based on UTF-8, so all strings passed to the library/received from the library should be/are encoded using UTF-8. ### Thread safety - Webdriver++ objects are not thread safe. It is not safe to use neither any single object nor different objects obtained from a single WebDriver concurrently without synchronization. On the other side, Webdriver++ objects don't use global variables so it is OK to use different instances of WebDriver in different threads. - The CURL library should be explicitly initialized if several WebDrivers are used from multiple threads. Call `curl_global_init(CURL_GLOBAL_ALL);` from `` once per process before using this library. ### Use common capabilities for all browsers ```cpp Capabilities common; common.SetProxy(DirectConnection()); auto ff = Start(Firefox(common)); auto ie = Start(InternetExplorer(common)); auto gc = Start(Chrome(common)); ``` ### Use required capabilities ```cpp Capabilities required = /* ... */; auto ff = Start(Firefox(), required); ``` ### Use custom URL for connecting to WebDriver ```cpp const char* url = "http://localhost:4444/wd/hub/"; auto ff = Start(Firefox(), url); // or auto ff = Start(Firefox(), Capabilities() /* required */, url); ``` ### Transfer objects between C++ and Javascript ```cpp namespace custom { struct Object { std::string string; int number; }; // Conversion functions should be in the same namespace as the object picojson::value CustomToJson(const Object& value) { return JsonObject() .Set("string", value.string) .Set("number", value.number); } void CustomFromJson(const picojson::value& value, Object& result) { assert(value.is()); result.string = FromJson(value.get("string")); result.number = FromJson(value.get("number")); } } // namespace custom custom::Object o1 = { "abc", 123 }; driver.Execute("var o1 = arguments[0];", JsArgs() << o1); custom::Object o1_copy = driver.Eval("return o1"); custom::Object o2 = driver.Eval("return { string: 'abc', number: 123 }"); ``` -------------------- Copyright © 2014 Sergey Kogan. Licensed under [The MIT license](https://github.com/sekogan/webdriverxx/blob/master/LICENSE). ================================================ FILE: include/webdriverxx/browsers/chrome.h ================================================ #ifndef WEBDRIVERXX_BROWSERS_CHROME_H #define WEBDRIVERXX_BROWSERS_CHROME_H #include "../capabilities.h" namespace webdriverxx { namespace chrome { struct PerfLoggingPrefs : JsonObject { WEBDRIVERXX_PROPERTIES_BEGIN(PerfLoggingPrefs) WEBDRIVERXX_PROPERTY(EnableNetwork, "enableNetwork", bool) WEBDRIVERXX_PROPERTY(enablePage, "enablePage", bool) WEBDRIVERXX_PROPERTY(enableTimeline, "enableTimeline", bool) WEBDRIVERXX_PROPERTY(tracingCategories, "tracingCategories", std::string) WEBDRIVERXX_PROPERTY(bufferUsageReportingInterval, "bufferUsageReportingInterval", int) WEBDRIVERXX_PROPERTIES_END() }; } // namespace chrome struct Chrome : Capabilities { // copyable Chrome(const Capabilities& defaults = Capabilities()) : Capabilities(defaults) { SetBrowserName(browser::Chrome); SetVersion(""); SetPlatform(platform::Any); } // See https://sites.google.com/a/chromium.org/chromedriver/capabilities for details WEBDRIVERXX_PROPERTIES_BEGIN(Chrome) WEBDRIVERXX_PROPERTY(LoggingPrefs, "loggingPrefs", LoggingPrefs) WEBDRIVERXX_PROPERTY(Args, "args", std::vector) WEBDRIVERXX_PROPERTY(Binary, "binary", std::string) // Each extension is a base64-encoded .crx file WEBDRIVERXX_PROPERTY(Extensions, "extensions", std::vector) WEBDRIVERXX_PROPERTY(LocalState, "localState", JsonObject) WEBDRIVERXX_PROPERTY(Prefs, "prefs", JsonObject) WEBDRIVERXX_PROPERTY(Detach, "detach", bool) WEBDRIVERXX_PROPERTY(DebuggerAddress, "debuggerAddress", std::string) WEBDRIVERXX_PROPERTY(ExcludeSwitches, "excludeSwitches", std::vector) WEBDRIVERXX_PROPERTY(MinidumpPath, "minidumpPath", std::string) WEBDRIVERXX_PROPERTY(MobileEmulation, "mobileEmulation", JsonObject) WEBDRIVERXX_PROPERTY(PerfLoggingPrefs, "perfLoggingPrefs", chrome::PerfLoggingPrefs) WEBDRIVERXX_PROPERTIES_END() }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/browsers/firefox.h ================================================ #ifndef WEBDRIVERXX_BROWSERS_FIREFOX_H #define WEBDRIVERXX_BROWSERS_FIREFOX_H #include "../capabilities.h" namespace webdriverxx { struct Firefox : Capabilities { // copyable Firefox(const Capabilities& defaults = Capabilities()) : Capabilities(defaults) { SetBrowserName(browser::Firefox); SetVersion(""); SetPlatform(platform::Any); } WEBDRIVERXX_PROPERTIES_BEGIN(Firefox) // Profile is a profile folder, zipped and base64 encoded. // TODO: add FirefoxProfile WEBDRIVERXX_PROPERTY(Profile, "firefox_profile", std::string) WEBDRIVERXX_PROPERTY(LoggingPrefs, "loggingPrefs", LoggingPrefs) WEBDRIVERXX_PROPERTY(FirefoxBinary, "firefox_binary", std::string) WEBDRIVERXX_PROPERTIES_END() }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/browsers/ie.h ================================================ #ifndef WEBDRIVERXX_BROWSERS_IE_H #define WEBDRIVERXX_BROWSERS_IE_H #include "../capabilities.h" namespace webdriverxx { namespace ie { namespace log_level { typedef std::string Value; typedef const char* const ConstValue; ConstValue Trace = "TRACE"; ConstValue Debug = "DEBUG"; ConstValue Info = "INFO"; ConstValue Warning = "WARN"; ConstValue Error = "ERROR"; ConstValue Fatal = "FATAL"; } // namespace log_level } // namespace ie struct InternetExplorer : Capabilities { // copyable InternetExplorer(const Capabilities& defaults = Capabilities()) : Capabilities(defaults) { SetBrowserName(browser::InternetExplorer); SetVersion(""); SetPlatform(platform::Any); } WEBDRIVERXX_PROPERTIES_BEGIN(InternetExplorer) WEBDRIVERXX_PROPERTY(SkipProtectedModeCheck, "ignoreProtectedModeSettings", bool) WEBDRIVERXX_PROPERTY(IgnoreZoomSetting, "ignoreZoomSetting", bool) WEBDRIVERXX_PROPERTY(InitialUrl, "initialBrowserUrl", std::string) WEBDRIVERXX_PROPERTY(EnablePersistentHover, "enablePersistentHover", bool) WEBDRIVERXX_PROPERTY(EnableElementCacheCleanup, "enableElementCacheCleanup", bool) WEBDRIVERXX_PROPERTY(RequireWindowFocus, "requireWindowFocus", bool) WEBDRIVERXX_PROPERTY(BrowserAttachTimeoutMs, "browserAttachTimeout", int) WEBDRIVERXX_PROPERTY(ForceCreateProcessApi, "ie.forceCreateProcessApi", bool) WEBDRIVERXX_PROPERTY(CommandLineSwitches, "ie.browserCommandLineSwitches", std::string) WEBDRIVERXX_PROPERTY(UsePerProcessProxy, "ie.usePerProcessProxy", bool) WEBDRIVERXX_PROPERTY(EnsureCleanSession, "ie.ensureCleanSession", bool) WEBDRIVERXX_PROPERTY(LogFile, "logFile", std::string) WEBDRIVERXX_PROPERTY(LogLevel, "logLevel", ie::log_level::Value) WEBDRIVERXX_PROPERTY(Host, "host", std::string) WEBDRIVERXX_PROPERTY(ExtractPath, "extractPath", std::string) WEBDRIVERXX_PROPERTY(Silent, "silent", bool) WEBDRIVERXX_PROPERTY(ProxyByServer, "ie.setProxyByServer", bool) WEBDRIVERXX_PROPERTIES_END() }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/browsers/phantom.h ================================================ #ifndef WEBDRIVERXX_BROWSERS_PHANTOM_H #define WEBDRIVERXX_BROWSERS_PHANTOM_H #include "../capabilities.h" namespace webdriverxx { struct Phantom : Capabilities { // copyable Phantom(const Capabilities& defaults = Capabilities()) : Capabilities(defaults) { SetBrowserName(browser::Phantom); SetVersion(""); SetPlatform(platform::Any); } WEBDRIVERXX_PROPERTIES_BEGIN(Phantom) // Profile is a profile folder, zipped and base64 encoded. WEBDRIVERXX_PROPERTY(LoggingPrefs, "loggingPrefs", LoggingPrefs) WEBDRIVERXX_PROPERTIES_END() }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/by.h ================================================ #ifndef WEBDRIVERXX_BY_H #define WEBDRIVERXX_BY_H #include namespace webdriverxx { class By { // copyable public: By(const std::string& strategy, const std::string& value) : strategy_(strategy) , value_(value) {} const std::string& GetStrategy() const { return strategy_; } const std::string& GetValue() const { return value_; } private: std::string strategy_; std::string value_; }; inline By ByClass(const std::string& value) { return By("class name", value); } inline By ByCss(const std::string& value) { return By("css selector", value); } inline By ById(const std::string& value) { return By("id", value); } inline By ByName(const std::string& value) { return By("name", value); } inline By ByLinkText(const std::string& value) { return By("link text", value); } inline By ByPartialLinkText(const std::string& value) { return By("partial link text", value); } inline By ByTag(const std::string& value) { return By("tag name", value); } inline By ByXPath(const std::string& value) { return By("xpath", value); } } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/capabilities.h ================================================ #ifndef WEBDRIVERXX_CAPABILITIES_H #define WEBDRIVERXX_CAPABILITIES_H #include "conversions.h" #include "picojson.h" #include namespace webdriverxx { namespace browser { typedef std::string Value; typedef const char* const ConstValue; ConstValue Android = "android"; ConstValue Chrome = "chrome"; ConstValue Firefox = "firefox"; ConstValue HtmlUnit = "htmlunit"; ConstValue InternetExplorer = "internet explorer"; ConstValue IPhone = "iPhone"; ConstValue IPad = "iPad"; ConstValue Mock = "mock"; ConstValue Opera = "opera"; ConstValue Safari = "safari"; ConstValue Phantom = "phantomjs"; } // namespace browser namespace platform { typedef std::string Value; typedef const char* const ConstValue; ConstValue Any = "ANY"; ConstValue Windows = "WINDOWS"; ConstValue Xp = "XP"; ConstValue Vista = "VISTA"; ConstValue Mac = "MAC"; ConstValue Linux = "LINUX"; ConstValue Unix = "UNIX"; ConstValue Android = "ANDROID"; } // namespace platform namespace unexpected_alert_behaviour { typedef std::string Value; typedef const char* const ConstValue; ConstValue Accept = "accept"; ConstValue Dismiss = "dismiss"; ConstValue Ignore = "ignore"; } // namespace unexpected_alert_behaviour namespace proxy_type { typedef std::string Value; typedef const char* const ConstValue; ConstValue Direct = "direct"; ConstValue Manual = "manual"; // Manual proxy settings configured, e.g. setting a proxy for HTTP, a proxy for FTP ConstValue Pac = "pac"; // Proxy autoconfiguration from a URL ConstValue Autodetect = "autodetect"; // Proxy autodetection, probably with WPAD ConstValue System = "system"; // Use system settings } // namespace proxy_type #define WEBDRIVERXX_PROPERTIES_BEGIN(this_class) typedef this_class This; #define WEBDRIVERXX_PROPERTIES_END() #define WEBDRIVERXX_PROPERTY_RONLY(name, id, type) \ type Get##name() const { return GetOptional(id); } \ bool Has##name() { return Has(id); } #define WEBDRIVERXX_PROPERTY(name, id, type) \ WEBDRIVERXX_PROPERTY_RONLY(name, id, type) \ This& Set##name(const type& value) { Set(id, value); return *this; } struct Proxy : JsonObject { // copyable WEBDRIVERXX_PROPERTIES_BEGIN(Proxy) WEBDRIVERXX_PROPERTY(ProxyType, "proxyType", proxy_type::Value) WEBDRIVERXX_PROPERTIES_END() }; struct DirectConnection : Proxy { // copyable DirectConnection() { SetProxyType(proxy_type::Direct); } }; struct AutodetectProxy : Proxy { // copyable AutodetectProxy() { SetProxyType(proxy_type::Autodetect); } }; struct SystemProxy : Proxy { // copyable SystemProxy() { SetProxyType(proxy_type::System); } }; struct AutomaticProxyFromUrl : Proxy { // copyable explicit AutomaticProxyFromUrl(const std::string& url) { SetProxyType(proxy_type::Pac); SetAutoconfigUrl(url); } WEBDRIVERXX_PROPERTIES_BEGIN(AutomaticProxyFromUrl) WEBDRIVERXX_PROPERTY(AutoconfigUrl, "proxyAutoconfigUrl", std::string) WEBDRIVERXX_PROPERTIES_END() }; struct ManualProxy : Proxy { // copyable ManualProxy() { SetProxyType(proxy_type::Manual); } WEBDRIVERXX_PROPERTIES_BEGIN(ManualProxy) WEBDRIVERXX_PROPERTY(NoProxyFor, "noProxy", std::string) WEBDRIVERXX_PROPERTIES_END() }; struct FtpProxy : ManualProxy { // copyable explicit FtpProxy(const std::string& address) { SetProxyAddress(address); } WEBDRIVERXX_PROPERTIES_BEGIN(FtpProxy) WEBDRIVERXX_PROPERTY(ProxyAddress, "ftpProxy", std::string) WEBDRIVERXX_PROPERTIES_END() }; struct HttpProxy : ManualProxy { // copyable explicit HttpProxy(const std::string& address) { SetProxyAddress(address); } WEBDRIVERXX_PROPERTIES_BEGIN(HttpProxy) WEBDRIVERXX_PROPERTY(ProxyAddress, "httpProxy", std::string) WEBDRIVERXX_PROPERTIES_END() }; struct SslProxy : ManualProxy { // copyable explicit SslProxy(const std::string& address) { SetProxyAddress(address); } WEBDRIVERXX_PROPERTIES_BEGIN(SslProxy) WEBDRIVERXX_PROPERTY(ProxyAddress, "sslProxy", std::string) WEBDRIVERXX_PROPERTIES_END() }; struct SocksProxy : ManualProxy { // copyable explicit SocksProxy(const std::string& address) { SetProxyAddress(address); } WEBDRIVERXX_PROPERTIES_BEGIN(SocksProxy) WEBDRIVERXX_PROPERTY(ProxyAddress, "socksProxy", std::string) WEBDRIVERXX_PROPERTY(Username, "socksUsername", std::string) WEBDRIVERXX_PROPERTY(Password, "socksPassword", std::string) WEBDRIVERXX_PROPERTIES_END() }; namespace log_level { typedef std::string Value; typedef const char* const ConstValue; ConstValue Off = "OFF"; ConstValue Severe = "SEVERE"; ConstValue Warning = "WARNING"; ConstValue Info = "INFO"; ConstValue Config = "CONFIG"; ConstValue Fine = "FINE"; ConstValue Finer = "FINER"; ConstValue Finest = "FINEST"; ConstValue All = "ALL"; } // namespace log_level struct LoggingPrefs : JsonObject { WEBDRIVERXX_PROPERTIES_BEGIN(LoggingPrefs) WEBDRIVERXX_PROPERTY(Level, "driver", log_level::Value) WEBDRIVERXX_PROPERTIES_END() }; // List of keys and values indicating features that server can or should provide. struct Capabilities : JsonObject { // copyable Capabilities() {} explicit Capabilities(const picojson::object& object) : JsonObject(object) {} // Hardcoded capabilities are here just to add some sugar. // If a capability is not listed below use Get/Set/Has public members. WEBDRIVERXX_PROPERTIES_BEGIN(Capabilities) WEBDRIVERXX_PROPERTY(BrowserName, "browserName", browser::Value) WEBDRIVERXX_PROPERTY(Version, "version", std::string) WEBDRIVERXX_PROPERTY(Platform, "platform", platform::Value) WEBDRIVERXX_PROPERTY_RONLY(TakesScreenshot, "takesScreenshot", bool) WEBDRIVERXX_PROPERTY_RONLY(HandlesAlerts, "handlesAlerts", bool) WEBDRIVERXX_PROPERTY_RONLY(CssSelectorsEnabled, "cssSelectorsEnabled", bool) WEBDRIVERXX_PROPERTY(JavascriptEnabled, "javascriptEnabled", bool) WEBDRIVERXX_PROPERTY(DatabaseEnabled, "databaseEnabled", bool) WEBDRIVERXX_PROPERTY(LocationContextEnabled, "locationContextEnabled", bool) WEBDRIVERXX_PROPERTY(ApplicationCacheEnabled, "applicationCacheEnabled", bool) WEBDRIVERXX_PROPERTY(BrowserConnectionEnabled, "browserConnectionEnabled", bool) WEBDRIVERXX_PROPERTY(WebStorageEnabled, "webStorageEnabled", bool) WEBDRIVERXX_PROPERTY(AcceptSslCerts, "acceptSslCerts", bool) WEBDRIVERXX_PROPERTY(Rotatable, "rotatable", bool) WEBDRIVERXX_PROPERTY(NativeEvents, "nativeEvents", bool) WEBDRIVERXX_PROPERTY(Proxy, "proxy", Proxy) WEBDRIVERXX_PROPERTY(UnexpectedAlertBehaviour, "unexpectedAlertBehaviour", unexpected_alert_behaviour::Value) WEBDRIVERXX_PROPERTY(ElementScrollBehavior, "elementScrollBehavior", int) WEBDRIVERXX_PROPERTY_RONLY(SessionId, "webdriver.remote.sessionid", std::string) WEBDRIVERXX_PROPERTY(QuietExceptions, "webdriver.remote.quietExceptions", bool) WEBDRIVERXX_PROPERTIES_END() }; inline void CustomFromJson(const picojson::value& value, Capabilities& result) { WEBDRIVERXX_CHECK(value.is(), "Capabilities is not an object"); result = Capabilities(value.get()); } } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/client.h ================================================ #ifndef WEBDRIVERXX_CLIENT_H #define WEBDRIVERXX_CLIENT_H #include "session.h" #include "capabilities.h" #include "detail/resource.h" #include "detail/http_connection.h" #include "picojson.h" #include #include namespace webdriverxx { const char *const kDefaultWebDriverUrl = "http://localhost:4444/wd/hub/"; // Gives low level access to server's resources. You normally should not use it. class Client { // copyable public: explicit Client(const std::string& url = kDefaultWebDriverUrl); virtual ~Client() {} picojson::object GetStatus() const; // Returns existing sessions. std::vector GetSessions() const; // Creates new session. Session CreateSession( const Capabilities& desired, const Capabilities& required ) const; private: Session MakeSession( const std::string& id, detail::Resource::Ownership mode ) const; private: detail::Shared resource_; }; } // namespace webdriverxx #include "client.inl" #endif ================================================ FILE: include/webdriverxx/client.inl ================================================ #include "conversions.h" #include "detail/shared.h" #include "detail/error_handling.h" #include "detail/types.h" #include namespace webdriverxx { inline Client::Client(const std::string& url) : resource_(new detail::RootResource( url, detail::Shared(new detail::HttpConnection) )) {} inline picojson::object Client::GetStatus() const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() const auto value = resource_->Get("status").get("value"); WEBDRIVERXX_CHECK(value.is(), "Value is not an object"); return value.get(); WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline std::vector Client::GetSessions() const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() const auto sessions = FromJson>( resource_->Get("sessions").get("value") ); std::vector result; result.reserve(sessions.size()); std::transform(sessions.begin(), sessions.end(), std::back_inserter(result), [this](const detail::SessionRef& session_ref) { return MakeSession(session_ref.id, detail::Resource::IsObserver); }); return result; WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline Session Client::CreateSession( const Capabilities& desired, const Capabilities& required ) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() const auto response = resource_->Post("session", JsonObject() .Set("desiredCapabilities", static_cast(desired)) .Set("requiredCapabilities", static_cast(required)) ); WEBDRIVERXX_CHECK(response.get("sessionId").is(), "Session ID is not a string"); WEBDRIVERXX_CHECK(response.get("value").is(), "Capabilities is not an object"); const auto sessionId = response.get("sessionId").to_str(); return MakeSession(sessionId, detail::Resource::IsOwner); WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline Session Client::MakeSession( const std::string& id, detail::Resource::Ownership mode ) const { return Session(detail::MakeSubResource(resource_, "session", id, mode)); } } // namespace webdriverxx ================================================ FILE: include/webdriverxx/conversions.h ================================================ #ifndef WEBDRIVERXX_CONVERSIONS_H #define WEBDRIVERXX_CONVERSIONS_H #include "types.h" #include "detail/error_handling.h" #include "detail/meta_tools.h" #include "picojson.h" #include namespace webdriverxx { template picojson::value ToJson(const T& value); template T FromJson(const picojson::value& value); class JsonObject { // copyable public: JsonObject() : value_(picojson::object()) {} explicit JsonObject(const picojson::object& object) : value_(object) {} template T Get(const std::string& name) const { const auto& map = value_.get(); const auto it = map.find(name); WEBDRIVERXX_CHECK(it != map.end(), detail::Fmt() << "No \"" << name << "\" in JsonObject"); return FromJson(it->second); } template T GetOptional(const std::string& name, const T& default_value = T()) const { const auto& map = value_.get(); const auto it = map.find(name); return it != map.end() ? FromJson(it->second) : default_value; } template JsonObject& Set(const std::string& name, const T& value) { value_.get()[name] = ToJson(value); return *this; } bool Has(const std::string& name) const { const auto& map = value_.get(); return map.find(name) != map.end(); } operator const picojson::value& () const { return value_; } private: picojson::value value_; }; namespace conversions_detail { struct DefaultTag {}; struct IterableTag {}; using namespace detail; template struct Tag : if_, type_is, type_is > {}; template picojson::value ToJsonImpl(const T& value, DefaultTag) { // Compile error here usually indicates // that compiler doesn't know how to convert the type T // to the picojson::value. Define CustomToJson // function (see examples below) in the T's namespace // to resolve the issue. return picojson::value(value); } template picojson::value ToJsonImpl(const T& value, IterableTag) { typedef typename std::iterator_traits::value_type Item; picojson::value result = picojson::value(picojson::array()); picojson::array& dst = result.get(); std::transform(std::begin(value), std::end(value), std::back_inserter(dst), [](const Item& item) { return ToJson(item); }); return result; } } // conversions_detail inline picojson::value CustomToJson(const char* value) { return picojson::value(value); } inline picojson::value CustomToJson(const std::string& value) { return ToJson(value.c_str()); } inline picojson::value CustomToJson(const picojson::value& value) { return value; } inline picojson::value CustomToJson(const picojson::object& value) { return picojson::value(value); } inline picojson::value CustomToJson(const JsonObject& value) { return static_cast(value); } inline picojson::value CustomToJson(int value) { return picojson::value(static_cast(value)); } template picojson::value CustomToJson(const T& value) { using conversions_detail::ToJsonImpl; using conversions_detail::Tag; return ToJsonImpl(value, typename Tag::type()); } template picojson::value ToJson(const T& value) { return CustomToJson(value); } /////////////////////////////////////////////////////////////////// namespace conversions_detail { template void FromJsonImpl(const picojson::value& value, T& result, DefaultTag) { // Compile error here usually indicates // that compiler doesn't know how to convert the picojson::value // to the type T. Define CustomFromJson function (see examples below) // in the T's namespace to resolve the issue. result = value.get(); } template void FromJsonImpl(const picojson::value& value, T& result, IterableTag) { WEBDRIVERXX_CHECK(value.is(), "Value is not an array"); const picojson::array& array = value.get(); typedef typename std::iterator_traits::value_type Item; std::transform(array.begin(), array.end(), std::back_inserter(result), FromJson); } } // conversions_detail inline void CustomFromJson(const picojson::value& value, std::string& result) { result = value.to_str(); } inline void CustomFromJson(const picojson::value& value, bool& result) { result = value.evaluate_as_boolean(); } inline void CustomFromJson(const picojson::value& value, int& result) { WEBDRIVERXX_CHECK(value.is(), "Value is not a number"); result = static_cast(value.get()); } inline void CustomFromJson(const picojson::value& value, unsigned& result) { WEBDRIVERXX_CHECK(value.is(), "Value is not a number"); result = static_cast(value.get()); } inline void CustomFromJson(const picojson::value& value, picojson::value& result) { result = value; } inline void CustomFromJson(const picojson::value& value, picojson::object& result) { WEBDRIVERXX_CHECK(value.is(), "Value is not an object"); result = value.get(); } inline void CustomFromJson(const picojson::value& value, JsonObject& result) { WEBDRIVERXX_CHECK(value.is(), "Value is not an object"); result = JsonObject(value.get()); } template void CustomFromJson(const picojson::value& value, T& result) { using conversions_detail::FromJsonImpl; using conversions_detail::Tag; return FromJsonImpl(value, result, typename Tag::type()); } template T FromJson(const picojson::value& value) { T result; CustomFromJson(value, result); return result; } template T OptionalFromJson(const picojson::value& value, const T& default_value = T()) { return value.is() ? default_value : FromJson(value); } /////////////////////////////////////////////////////////////////// inline picojson::value CustomToJson(const Size& size) { return JsonObject() .Set("width", size.width) .Set("height", size.height) ; } inline void CustomFromJson(const picojson::value& value, Size& result) { WEBDRIVERXX_CHECK(value.is(), "Size is not an object"); result.width = FromJson(value.get("width")); result.height = FromJson(value.get("height")); } inline picojson::value CustomToJson(const Point& position) { return JsonObject() .Set("x", position.x) .Set("y", position.y) ; } inline void CustomFromJson(const picojson::value& value, Point& result) { WEBDRIVERXX_CHECK(value.is(), "Point is not an object"); result.x = FromJson(value.get("x")); result.y = FromJson(value.get("y")); } inline picojson::value CustomToJson(const Cookie& cookie) { JsonObject result; result.Set("name", cookie.name); result.Set("value", cookie.value); if (!cookie.path.empty()) result.Set("path", cookie.path); if (!cookie.domain.empty()) result.Set("domain", cookie.domain); if (cookie.secure) result.Set("secure", true); if (cookie.http_only) result.Set("httpOnly", true); if (cookie.expiry != Cookie::NoExpiry) result.Set("expiry", cookie.expiry); return result; } inline void CustomFromJson(const picojson::value& value, Cookie& result) { WEBDRIVERXX_CHECK(value.is(), "Cookie is not an object"); result.name = FromJson(value.get("name")); result.value = FromJson(value.get("value")); result.path = OptionalFromJson(value.get("path")); result.domain = OptionalFromJson(value.get("domain")); result.secure = OptionalFromJson(value.get("secure"), false); result.http_only = OptionalFromJson(value.get("httpOnly"), false); result.expiry = OptionalFromJson(value.get("expiry"), Cookie::NoExpiry); } } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/error_handling.h ================================================ #ifndef WEBDRIVERXX_DETAIL_ERROR_HANDLING_H #define WEBDRIVERXX_DETAIL_ERROR_HANDLING_H #include "../errors.h" #include #include namespace webdriverxx { namespace detail { class Fmt { public: template Fmt& operator << (const T& value) { stream_ << value; return *this; } operator std::string() const { return stream_.str(); } private: std::ostringstream stream_; }; template bool BoolCast(T value) { return !!value; } } // namespace detail } // namespace webdriverxx #if __cplusplus >= 201103L #define WEBDRIVERXX_CURRENT_FUNCTION __func__ #else #define WEBDRIVERXX_CURRENT_FUNCTION __FUNCTION__ #endif #define WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() \ try { #define WEBDRIVERXX_FUNCTION_CONTEXT_END() \ } catch (const std::exception& e) { \ throw ::webdriverxx::WebDriverException(std::string(e.what()) \ + " called from " + WEBDRIVERXX_CURRENT_FUNCTION); \ } #define WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(details) \ } catch (const std::exception& e) { \ throw ::webdriverxx::WebDriverException(std::string(e.what()) \ + " called from " + WEBDRIVERXX_CURRENT_FUNCTION \ + " (" + std::string(details) + ")"); \ } #define WEBDRIVERXX_THROW(message) \ throw ::webdriverxx::WebDriverException(::webdriverxx::detail::Fmt() \ << std::string(message) \ << " at line " << __LINE__ \ << ", file " << __FILE__ \ ) #define WEBDRIVERXX_CHECK(pred, message) \ for (;!detail::BoolCast(pred);) \ WEBDRIVERXX_THROW(message) #endif ================================================ FILE: include/webdriverxx/detail/factories.h ================================================ #ifndef WEBDRIVERXX_DETAIL_FACTORIES_H #define WEBDRIVERXX_DETAIL_FACTORIES_H #include "shared.h" #include namespace webdriverxx { class Element; namespace detail { class Finder; class Resource; struct IFinderFactory { virtual Finder MakeFinder(const Shared& context) = 0; virtual ~IFinderFactory() {} }; struct IElementFactory { virtual Element MakeElement(const std::string& id) = 0; virtual ~IElementFactory() {} }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/factories_impl.h ================================================ #ifndef WEBDRIVERXX_DETAIL_FACTORIES_IMPL_H #define WEBDRIVERXX_DETAIL_FACTORIES_IMPL_H #include "factories.h" #include "resource.h" #include "finder.h" #include "shared.h" #include "../element.h" namespace webdriverxx { namespace detail { class SessionFactory // noncopyable : public IElementFactory , public IFinderFactory , public SharedObjectBase { public: explicit SessionFactory(const Shared& session_resource) : session_resource_(session_resource) {} virtual Element MakeElement(const std::string& id) { return Element( id, detail::MakeSubResource(session_resource_, "element", id), Shared(this) ); } virtual Finder MakeFinder(const Shared& context) { return Finder(context, Shared(this)); } private: const Shared session_resource_; }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/finder.h ================================================ #ifndef WEBDRIVERXX_DETAIL_FINDER_H #define WEBDRIVERXX_DETAIL_FINDER_H #include "shared.h" #include "resource.h" #include "factories.h" #include "../by.h" #include namespace webdriverxx { class Element; namespace detail { class Finder { // copyable public: Finder( const Shared& context, const Shared& factory ); Element FindElement(const By& by) const; std::vector FindElements(const By& by) const; private: Shared context_; Shared factory_; }; } // namespace detail } // namespace webdriverxx #include "finder.inl" #endif ================================================ FILE: include/webdriverxx/detail/finder.inl ================================================ #include "../conversions.h" #include "../element.h" #include "types.h" #include namespace webdriverxx { namespace detail { inline Finder::Finder( const Shared& context, const Shared& factory ) : context_(context) , factory_(factory) {} inline Element Finder::FindElement(const By& by) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return factory_->MakeElement(FromJson( context_->Post("element", JsonObject() .Set("using", by.GetStrategy()) .Set("value", by.GetValue()) )).ref); WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() << "context: " << context_->GetUrl() << ", strategy: " << by.GetStrategy() << ", value: " << by.GetValue() ) } inline std::vector Finder::FindElements(const By& by) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() const auto ids = FromJson>( context_->Post("elements", JsonObject() .Set("using", by.GetStrategy()) .Set("value", by.GetValue()) )); std::vector result; result.reserve(ids.size()); std::transform(ids.begin(), ids.end(), std::back_inserter(result), [this](const ElementRef& element_ref) { return factory_->MakeElement(element_ref.ref); }); return result; WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() << "context: " << context_->GetUrl() << ", strategy: " << by.GetStrategy() << ", value: " << by.GetValue() ) } } // namespace detail } // namespace webdriverxx ================================================ FILE: include/webdriverxx/detail/http_client.h ================================================ #ifndef WEBDRIVERXX_DETAIL_HTTP_CLIENT_H #define WEBDRIVERXX_DETAIL_HTTP_CLIENT_H #include namespace webdriverxx { namespace detail { struct HttpResponse { long http_code; std::string body; HttpResponse() : http_code(0) {} }; struct IHttpClient { virtual HttpResponse Get(const std::string& url) const = 0; virtual HttpResponse Delete(const std::string& url) const = 0; virtual HttpResponse Post(const std::string& url, const std::string& data) const = 0; virtual ~IHttpClient() {} }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/http_connection.h ================================================ #ifndef WEBDRIVERXX_DETAIL_HTTP_CONNECTION_H #define WEBDRIVERXX_DETAIL_HTTP_CONNECTION_H #include "http_client.h" #include "http_request.h" #include "error_handling.h" #include "shared.h" #include namespace webdriverxx { namespace detail { class HttpConnection // noncopyable : public IHttpClient , public SharedObjectBase { public: HttpConnection() : connection_(InitCurl()) {} ~HttpConnection() { curl_easy_cleanup(connection_); } HttpResponse Get(const std::string& url) const { return HttpGetRequest(connection_, url).Execute(); } HttpResponse Delete(const std::string& url) const { return HttpDeleteRequest(connection_, url).Execute(); } HttpResponse Post( const std::string& url, const std::string& upload_data ) const { return HttpPostRequest(connection_, url, upload_data).Execute(); } private: static CURL* InitCurl() { CURL *const result = curl_easy_init(); WEBDRIVERXX_CHECK(result, "Cannot initialize CURL"); return result; } private: CURL *const connection_; }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/http_request.h ================================================ #ifndef WEBDRIVERXX_DETAIL_HTTP_REQUEST_H #define WEBDRIVERXX_DETAIL_HTTP_REQUEST_H #include "error_handling.h" #include #include #include namespace webdriverxx { namespace detail { const char *const kContentTypeJson = "application/json;charset=UTF-8"; class HttpHeaders { public: HttpHeaders() : head_(nullptr) {} ~HttpHeaders() { curl_slist_free_all(head_); } void Add(const std::string& name, const std::string& value) { head_ = curl_slist_append(head_, (name + ": " + value).c_str()); WEBDRIVERXX_CHECK(head_, "Cannot add HTTP header"); } curl_slist* Get() const { return head_; } private: curl_slist* head_; }; class HttpRequest { public: HttpRequest( CURL* http_connection, const std::string& url ) : http_connection_(http_connection) , url_(url) {} virtual ~HttpRequest() {} HttpResponse Execute() { curl_easy_reset(http_connection_); SetOption(CURLOPT_URL, url_.c_str()); HttpResponse response; SetOption(CURLOPT_WRITEFUNCTION, &WriteCallback); SetOption(CURLOPT_WRITEDATA, &response.body); char error_message[CURL_ERROR_SIZE]; SetOption(CURLOPT_ERRORBUFFER, &error_message); AddHeader("Accept", kContentTypeJson); SetCustomRequestOptions(); SetOption(CURLOPT_HTTPHEADER, headers_.Get()); const CURLcode result = curl_easy_perform(http_connection_); WEBDRIVERXX_CHECK(result == CURLE_OK, Fmt() << "Cannot perform HTTP request (" << "result: " << result << ", message: " << error_message << ")" ); response.http_code = GetHttpCode(); return response; } protected: virtual void SetCustomRequestOptions() {} template void SetOption(CURLoption option, const T& value) const { const auto result = curl_easy_setopt(http_connection_, option, value); WEBDRIVERXX_CHECK(result == CURLE_OK, Fmt() << "Cannot set HTTP session option (" << "option: " << option << ", message: \"" << curl_easy_strerror(result) << "\"" << ")" ); } void AddHeader(const std::string& name, const std::string& value) { headers_.Add(name, value); } private: long GetHttpCode() const { long http_code = 0; const auto result = curl_easy_getinfo(http_connection_, CURLINFO_RESPONSE_CODE, &http_code); WEBDRIVERXX_CHECK(result == CURLE_OK, Fmt() << "Cannot get HTTP code (" << curl_easy_strerror(result) << ")" ); return http_code; } static size_t WriteCallback(void* buffer, size_t size, size_t nmemb, void* userdata) { std::string* data_received = reinterpret_cast(userdata); const auto buffer_size = size * nmemb; data_received->append(reinterpret_cast(buffer), buffer_size); return buffer_size; } private: HttpRequest(HttpRequest&); HttpRequest& operator=(HttpRequest&); private: CURL *const http_connection_; const std::string url_; HttpHeaders headers_; }; typedef HttpRequest HttpGetRequest; class HttpDeleteRequest : public HttpRequest { public: HttpDeleteRequest(CURL* http_connection, const std::string& url) : HttpRequest(http_connection, url) {} private: void SetCustomRequestOptions() { SetOption(CURLOPT_CUSTOMREQUEST, "DELETE"); } }; class HttpPostRequest : public HttpRequest { public: HttpPostRequest( CURL* http_connection, const std::string& url, const std::string& upload_data ) : HttpRequest(http_connection, url) , upload_data_(upload_data) , unsent_ptr_(upload_data.c_str()) , unsent_length_(upload_data.size()) {} protected: void SetCustomRequestOptions() { SetOption(CURLOPT_POST, 1L); SetOption(CURLOPT_POSTFIELDSIZE, upload_data_.length()); AddHeader("Content-Type", kContentTypeJson); SetOption(CURLOPT_READFUNCTION, ReadCallback); SetOption(CURLOPT_READDATA, this); } private: static size_t ReadCallback(void* buffer, size_t size, size_t nmemb, void* userdata) { HttpPostRequest* that = reinterpret_cast(userdata); auto buffer_size = size * nmemb; auto copy_size = that->unsent_length_ < buffer_size ? that->unsent_length_ : buffer_size; std::copy(that->unsent_ptr_, that->unsent_ptr_ + copy_size, reinterpret_cast(buffer)); that->unsent_length_ -= copy_size; that->unsent_ptr_ += copy_size; return copy_size; } private: const std::string& upload_data_; const char* unsent_ptr_; size_t unsent_length_; }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/keyboard.h ================================================ #ifndef WEBDRIVERXX_DETAIL_KEYBOARD_H #define WEBDRIVERXX_DETAIL_KEYBOARD_H #include "resource.h" #include "../conversions.h" #include "../keys.h" namespace webdriverxx { namespace detail { class Keyboard { // copyable public: Keyboard(const Shared& resource, const std::string& command) : resource_(resource) , command_(command) {} const Keyboard& SendKeys(const std::string& keys) const { return SendKeys(Shortcut() << keys); } const Keyboard& SendKeys(const Shortcut& shortcut) const { resource_->Post(command_, JsonObject() .Set("value", ToJson(shortcut.keys_))); return *this; } private: Shared resource_; std::string command_; }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/meta_tools.h ================================================ #ifndef WEBDRIVERXX_DETAIL_META_TOOLS_H #define WEBDRIVERXX_DETAIL_META_TOOLS_H #include #include #include #include namespace webdriverxx { namespace detail { template struct type_is { typedef T type; }; template T& value_ref(); // MSVC2010 doesn't have std::declval template class is_iterable { template static std::false_type test(...); template static decltype(std::begin(value_ref()), std::end(value_ref()), std::true_type()) test(int); typedef decltype(test(0)) verdict; public: static const bool value = verdict::value; }; template struct is_string : std::is_convertible {}; template struct if_ : std::conditional {}; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/resource.h ================================================ #ifndef WEBDRIVERXX_DETAIL_RESOURCE_H #define WEBDRIVERXX_DETAIL_RESOURCE_H #include "error_handling.h" #include "http_client.h" #include "shared.h" #include "../conversions.h" #include "../response_status_code.h" #include "../picojson.h" namespace webdriverxx { namespace detail { class Resource : public SharedObjectBase { // noncopyable public: enum Ownership { IsOwner, IsObserver }; Resource( const std::string& url, const Shared& http_client, Ownership mode = IsObserver ) : http_client_(http_client) , url_(url) , ownership_(mode) {} Resource( const Shared& parent, const std::string& name, Ownership mode = IsObserver ) : http_client_(parent->http_client_) , parent_(parent) , url_(ConcatUrl(parent->url_, name)) , ownership_(mode) {} virtual ~Resource() { try { if (ownership_ == IsOwner) DeleteResource(); } catch (const std::exception&) {} } const std::string& GetUrl() const { return url_; } picojson::value Get(const std::string& command = std::string()) const { return Download(command, &IHttpClient::Get, "GET"); } template T GetValue(const std::string& command) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return FromJson(Get(command)); WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() << "command: " << command ) } std::string GetString(const std::string& command) const { return GetValue(command); } bool GetBool(const std::string& command) const { return GetValue(command); } picojson::value Delete(const std::string& command = std::string()) const { return Download(command, &IHttpClient::Delete, "DELETE"); } picojson::value Post( const std::string& command = std::string(), const picojson::value& upload_data = picojson::value() ) const { return Upload(command, upload_data, &IHttpClient::Post, "POST"); } template void Post( const std::string& command, const std::string& arg_name, const T& arg_value ) const { Post(command, JsonObject().Set(arg_name, arg_value)); } template void PostValue(const std::string& command, const T& value) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() Post(command, ToJson(value)); WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() << "command: " << command ) } protected: virtual picojson::value TransformResponse(picojson::value& response) const { picojson::value result; response.get("value").swap(result); return result; } virtual void DeleteResource() { Delete(); } private: picojson::value Download( const std::string& command, HttpResponse (IHttpClient::* member)(const std::string& url) const, const char* request_type ) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return ProcessResponse((http_client_->*member)( ConcatUrl(url_, command) )); WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() << "request: " << request_type << ", command: " << command << ", resource: " << url_ ) } static std::string ToUploadData(const picojson::value& upload_data) { return upload_data.is() ? std::string() : upload_data.serialize(); } picojson::value Upload( const std::string& command, const picojson::value& upload_data, HttpResponse (IHttpClient::* member)(const std::string& url, const std::string& upload_data) const, const char* request_type ) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return ProcessResponse((http_client_->*member)( ConcatUrl(url_, command), ToUploadData(upload_data) )); WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() << "request: " << request_type << ", command: " << command << ", resource: " << url_ << ", data: " << ToUploadData(upload_data) ) } picojson::value ProcessResponse( const HttpResponse& http_response ) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() WEBDRIVERXX_CHECK( http_response.http_code / 100 != 4 && http_response.http_code != 501, "HTTP code indicates that request is invalid"); picojson::value response; std::string error_message; picojson::parse(response, http_response.body.begin(), http_response.body.end(), &error_message); WEBDRIVERXX_CHECK(error_message.empty(), Fmt() << "JSON parser error (" << error_message << ")" ); WEBDRIVERXX_CHECK(response.is(), "Server response is not an object"); WEBDRIVERXX_CHECK(response.contains("status"), "Server response has no member \"status\""); WEBDRIVERXX_CHECK(response.get("status").is(), "Response status code is not a number"); const auto status = static_cast(static_cast(response.get("status").get())); WEBDRIVERXX_CHECK(response.contains("value"), "Server response has no member \"value\""); const auto& value = response.get("value"); if (http_response.http_code == 500) { // Internal server error WEBDRIVERXX_CHECK(value.is(), "Server returned HTTP code 500 and \"response.value\" is not an object"); WEBDRIVERXX_CHECK(value.contains("message"), "Server response has no member \"value.message\""); WEBDRIVERXX_CHECK(value.get("message").is(), "\"value.message\" is not a string"); WEBDRIVERXX_THROW(Fmt() << "Server failed to execute command (" << "message: " << value.get("message").to_str() << ", status: " << response_status_code::ToString(status) << ", status_code: " << status << ")" ); } WEBDRIVERXX_CHECK(status == response_status_code::kSuccess, "Non-zero response status code"); WEBDRIVERXX_CHECK(http_response.http_code == 200, "Unsupported HTTP code"); return TransformResponse(response); WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() << "HTTP code: " << http_response.http_code << ", body: " << http_response.body ) } static std::string ConcatUrl(const std::string& a, const std::string& b, const char delim = '/') { auto result = a.empty() ? b : a; if (!a.empty() && !b.empty()) { if (result[result.length()-1] != delim) result += delim; result.append(b[0] == delim ? b.begin() + 1 : b.begin(), b.end()); } return result; } private: const Shared http_client_; const Shared parent_; const std::string url_; const Ownership ownership_; }; class RootResource : public Resource { // noncopyable public: RootResource( const std::string& url, const Shared& http_client ) : Resource(url, http_client, IsObserver) {} private: virtual picojson::value TransformResponse(picojson::value& response) const { picojson::value result; response.swap(result); return result; } }; inline Shared MakeSubResource( const Shared& parent, const std::string& subpath, Resource::Ownership mode = Resource::IsObserver ) { return Shared(new Resource(parent, subpath, mode)); } inline Shared MakeSubResource( const Shared& parent, const std::string& subpath1, const std::string& subpath2, Resource::Ownership mode = Resource::IsObserver ) { return Shared(new Resource(parent, subpath1 + "/" + subpath2, mode)); } } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/shared.h ================================================ #ifndef WEBDRIVERXX_DETAIL_SHARED_H #define WEBDRIVERXX_DETAIL_SHARED_H #include namespace webdriverxx { namespace detail { class SharedObjectBase { // noncopyable public: SharedObjectBase() : ref_(0) {} virtual ~SharedObjectBase() {} virtual void AddRef() { ++ref_; } virtual void Release() { if (--ref_ == 0) delete this; } private: SharedObjectBase(SharedObjectBase&); SharedObjectBase& operator = (SharedObjectBase&); private: unsigned ref_; }; // Copyable, not thread safe template class Shared { public: Shared() : ptr_(nullptr) , ref_(nullptr) {} template Shared(T2* ptr) : ptr_(ptr) , ref_(ptr) { if (ref_) ref_->AddRef(); } Shared(const Shared& other) : ptr_(other.ptr_) , ref_(other.ref_) { if (ref_) ref_->AddRef(); } template Shared(const Shared& other) : ptr_(other.ptr_) , ref_(other.ref_) { if (ref_) ref_->AddRef(); } ~Shared() { if (ref_) ref_->Release(); } Shared& operator = (const Shared& other) { if (&other != this) Shared(other).Swap(*this); return *this; } void Swap(Shared& other) { std::swap(ptr_, other.ptr_); std::swap(ref_, other.ref_); } T& operator * () const { return *ptr_; } T* operator -> () const { return ptr_; } T* Get() const { return ptr_; } operator T* () const { return ptr_; } private: template friend class Shared; T* ptr_; SharedObjectBase* ref_; }; } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/time.h ================================================ #ifndef WEBDRIVERXX_DETAIL_TIME_H #define WEBDRIVERXX_DETAIL_TIME_H #include "error_handling.h" #include "../types.h" #ifdef _WIN32 #include #else #include #include #endif namespace webdriverxx { namespace detail { inline TimePoint Now() { #ifdef _WIN32 FILETIME time; ::GetSystemTimeAsFileTime(&time); return ((static_cast(time.dwHighDateTime) << 32) + time.dwLowDateTime)/10000; #else timeval time = {}; WEBDRIVERXX_CHECK(0 == gettimeofday(&time, nullptr), "gettimeofday failure"); return static_cast(time.tv_sec)*1000 + time.tv_usec/1000; #endif } inline void Sleep(Duration milliseconds) { #ifdef _WIN32 ::Sleep(static_cast(milliseconds)); #else timespec time = { static_cast(milliseconds/1000), static_cast(milliseconds%1000)*1000000 }; while (nanosleep(&time, &time)) {} #endif } } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/to_string.h ================================================ #ifndef WEBDRIVERXX_TO_STRING_H #define WEBDRIVERXX_TO_STRING_H #include "meta_tools.h" #include #include namespace webdriverxx { namespace detail { template void ToStream(const T& value, std::ostream& stream); namespace to_string_impl { template void WriteNonStreamableValue(const T&, std::ostream& stream) { stream << ""; } } // namespace to_string_impl } // detail } // webdriverxx namespace webdriverxx_to_string_impl { template std::ostream& operator << (std::ostream& stream, const T& value) { webdriverxx::detail::to_string_impl::WriteNonStreamableValue(value, stream); return stream; } } // namespace webdriverxx_to_string_impl namespace webdriverxx { namespace detail { namespace to_string_impl { struct DefaultTag {}; struct IterableTag {}; struct StringTag {}; template struct Tag : if_, type_is, if_, type_is, type_is >> {}; template void ToStreamImpl(const T& value, std::ostream& stream, DefaultTag) { using namespace webdriverxx_to_string_impl; stream << value; } template void ToStreamImpl(T* value, std::ostream& stream, DefaultTag) { stream << '*'; ToStream(*value, stream); } inline void ToStreamImpl(const char value, std::ostream& stream, DefaultTag) { stream << "'" << value << "'"; } inline void ToStreamImpl(const char* value, std::ostream& stream, StringTag) { stream << '"' << value << '"'; } inline void ToStreamImpl(const std::string& value, std::ostream& stream, StringTag) { ToStream(value.c_str(), stream); } template void ToStreamImpl(const T& value, std::ostream& stream, IterableTag) { auto it = std::begin(value); const auto end = std::end(value); int limit = 20; stream << "["; if (it != end) { ToStream(*it, stream); while (++it != end && --limit > 0) { stream << ", "; ToStream(*it, stream); } } stream << "]"; } } // namespace to_string_impl template void PrintTo(const T& value, std::ostream* stream) { using to_string_impl::ToStreamImpl; using to_string_impl::Tag; ToStreamImpl(value, *stream, typename Tag::type()); } template void ToStream(const T& value, std::ostream& stream) { PrintTo(value, &stream); // for compatibility with Google Test's values printers } template std::string ToString(const T& value) { std::ostringstream s; ToStream(value, s); return s.str(); } } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/detail/types.h ================================================ #ifndef WEBDRIVERXX_DETAIL_TYPES_H #define WEBDRIVERXX_DETAIL_TYPES_H #include "../conversions.h" #include "../capabilities.h" #include "../picojson.h" #include namespace webdriverxx { namespace detail { struct SessionRef { std::string id; Capabilities capabilities; }; struct ElementRef { std::string ref; }; inline picojson::value CustomToJson(const ElementRef& ref) { return JsonObject() .Set("ELEMENT", ref.ref) ; } inline void CustomFromJson(const picojson::value& value, ElementRef& result) { WEBDRIVERXX_CHECK(value.is(), "ElementRef is not an object"); result.ref = FromJson(value.get("ELEMENT")); if (result.ref == "null") { std::stringstream element(value.serialize()); std::string segment; std::vector strArr; while (std::getline(element, segment, ':')) { segment.erase(std::remove(segment.begin(), segment.end(), '{'), segment.end()); segment.erase(std::remove(segment.begin(), segment.end(), '"'), segment.end()); segment.erase(std::remove(segment.begin(), segment.end(), '}'), segment.end()); strArr.push_back(segment); } if (strArr.size() >= 1) { std::stringstream ss; ss << "{\"ELEMENT\":" << "\"" << strArr[1] << "\"}"; picojson::value jSon; picojson::parse(jSon, ss); result.ref = FromJson(jSon.get("ELEMENT")); } } } inline void CustomFromJson(const picojson::value& value, SessionRef& result) { WEBDRIVERXX_CHECK(value.is(), "Session information is not an object"); result.id = value.get("sessionId").to_str(); if (value.get("capabilities").is()) result.capabilities = Capabilities(value.get("capabilities").get()); } } // namespace detail } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/element.h ================================================ #ifndef WEBDRIVERXX_ELEMENT_H #define WEBDRIVERXX_ELEMENT_H #include "by.h" #include "types.h" #include "keys.h" #include "detail/shared.h" #include "detail/keyboard.h" #include "detail/resource.h" #include "detail/factories.h" #include #include namespace webdriverxx { // An element from DOM class Element { // copyable public: Element(); Element( const std::string& ref, const detail::Shared& resource, const detail::Shared& factory ); std::string GetRef() const; // Returns ID that is used by Webdriver to identify elements bool IsDisplayed() const; bool IsEnabled() const; bool IsSelected() const; Point GetLocation() const; Point GetLocationInView() const; Size GetSize() const; std::string GetAttribute(const std::string& name) const; std::string GetCssProperty(const std::string& name) const; std::string GetTagName() const; std::string GetText() const; Element FindElement(const By& by) const; std::vector FindElements(const By& by) const; const Element& Clear() const; const Element& Click() const; const Element& Submit() const; const Element& SendKeys(const std::string& keys) const; const Element& SendKeys(const Shortcut& shortcut) const; bool Equals(const Element& other) const; bool operator != (const Element& other) const; bool operator == (const Element& other) const; bool operator < (const Element& other) const; private: detail::Resource& GetResource() const; detail::Keyboard GetKeyboard() const; private: std::string ref_; detail::Shared resource_; detail::Shared factory_; }; } // namespace webdriverxx #include "element.inl" #endif ================================================ FILE: include/webdriverxx/element.inl ================================================ #include "conversions.h" #include "detail/finder.h" #include "detail/error_handling.h" namespace webdriverxx { inline picojson::value CustomToJson(const Element& element) { detail::ElementRef ref = { element.GetRef() }; return ToJson(ref); } inline Element::Element() {} inline Element::Element( const std::string& ref, const detail::Shared& resource, const detail::Shared& factory ) : ref_(ref) , resource_(resource) , factory_(factory) {} inline std::string Element::GetRef() const { return ref_; } inline bool Element::IsDisplayed() const { return GetResource().GetBool("displayed"); } inline bool Element::IsEnabled() const { return GetResource().GetBool("enabled"); } inline bool Element::IsSelected() const { return GetResource().GetBool("selected"); } inline Point Element::GetLocation() const { return GetResource().GetValue("location"); } inline Point Element::GetLocationInView() const { return GetResource().GetValue("location_in_view"); } inline Size Element::GetSize() const { return GetResource().GetValue("size"); } inline std::string Element::GetAttribute(const std::string& name) const { return GetResource().GetString(std::string("attribute/") + name); } inline std::string Element::GetCssProperty(const std::string& name) const { return GetResource().GetString(std::string("css/") + name); } inline std::string Element::GetTagName() const { return GetResource().GetString("name"); } inline std::string Element::GetText() const { return GetResource().GetString("text"); } inline Element Element::FindElement(const By& by) const { return factory_->MakeFinder(&GetResource()).FindElement(by); } inline std::vector Element::FindElements(const By& by) const { return factory_->MakeFinder(&GetResource()).FindElements(by); } inline const Element& Element::Clear() const { GetResource().Post("clear"); return *this; } inline const Element& Element::Click() const { GetResource().Post("click"); return *this; } inline const Element& Element::Submit() const { GetResource().Post("submit"); return *this; } inline const Element& Element::SendKeys(const std::string& keys) const { GetKeyboard().SendKeys(keys); return *this; } inline const Element& Element::SendKeys(const Shortcut& shortcut) const { GetKeyboard().SendKeys(shortcut); return *this; } inline bool Element::Equals(const Element& other) const { return GetResource().GetBool(std::string("equals/") + other.ref_); } inline bool Element::operator != (const Element& other) const { return ref_ != other.ref_; } inline bool Element::operator == (const Element& other) const { return ref_ == other.ref_; } inline bool Element::operator < (const Element& other) const { return ref_ < other.ref_; } inline detail::Resource& Element::GetResource() const { WEBDRIVERXX_CHECK(resource_, "Attempt to use empty element"); return *resource_; } inline detail::Keyboard Element::GetKeyboard() const { return detail::Keyboard(&GetResource(), "value"); } } // namespace webdriverxx ================================================ FILE: include/webdriverxx/errors.h ================================================ #ifndef WEBDRIVERXX_ERRORS_H #define WEBDRIVERXX_ERRORS_H #include #include namespace webdriverxx { struct WebDriverException : std::runtime_error { explicit WebDriverException(const std::string& message) : std::runtime_error(message) {} }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/js_args.h ================================================ #ifndef WEBDRIVERXX_JS_ARGS_H #define WEBDRIVERXX_JS_ARGS_H #include "conversions.h" #include "picojson.h" namespace webdriverxx { class Session; class JsArgs // copyable { public: JsArgs() : args_(picojson::array()) {} // This member works out of the box for scalar values, Elements and std::vector // with scalar values or Elements. Additional code is needed to use it // with custom objects and custom containers. Take a look // to the TestJsExecutor/CanPassArray, TestJsExecutor/CanPassCustomArray // and TestJsExecutor/CanPassCustomObjects tests to get details. template JsArgs& operator << (const T& arg) { args_.get().push_back(ToJson(arg)); return *this; } // Alternative backdoor for passing custom data structures as arguments. JsArgs& operator << (const picojson::value& arg) { args_.get().push_back(arg); return *this; } private: friend class Session; picojson::value args_; }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/keys.h ================================================ #ifndef WEBDRIVERXX_KEYS_H #define WEBDRIVERXX_KEYS_H #include #include namespace webdriverxx { namespace keys { const char *const Null = "\xee\x80\x80"; const char *const Cancel = "\xee\x80\x81"; const char *const Help = "\xee\x80\x82"; const char *const Backspace = "\xee\x80\x83"; const char *const Tab = "\xee\x80\x84"; const char *const Clear = "\xee\x80\x85"; const char *const Return = "\xee\x80\x86"; const char *const Enter = "\xee\x80\x87"; const char *const Shift = "\xee\x80\x88"; const char *const Control = "\xee\x80\x89"; const char *const Alt = "\xee\x80\x8a"; const char *const Pause = "\xee\x80\x8b"; const char *const Escape = "\xee\x80\x8c"; const char *const Space = "\xee\x80\x8d"; const char *const PageUp = "\xee\x80\x8e"; const char *const PageDown = "\xee\x80\x8f"; const char *const End = "\xee\x80\x90"; const char *const Home = "\xee\x80\x91"; const char *const Left = "\xee\x80\x92"; const char *const Up = "\xee\x80\x93"; const char *const Right = "\xee\x80\x94"; const char *const Down = "\xee\x80\x95"; const char *const Insert = "\xee\x80\x96"; const char *const Delete = "\xee\x80\x97"; const char *const Semicolon = "\xee\x80\x98"; const char *const Equals = "\xee\x80\x99"; const char *const Numpad0 = "\xee\x80\x9a"; const char *const Numpad1 = "\xee\x80\x9b"; const char *const Numpad2 = "\xee\x80\x9c"; const char *const Numpad3 = "\xee\x80\x9d"; const char *const Numpad4 = "\xee\x80\x9e"; const char *const Numpad5 = "\xee\x80\x9f"; const char *const Numpad6 = "\xee\x80\xa0"; const char *const Numpad7 = "\xee\x80\xa1"; const char *const Numpad8 = "\xee\x80\xa2"; const char *const Numpad9 = "\xee\x80\xa3"; const char *const Multiply = "\xee\x80\xa4"; const char *const Add = "\xee\x80\xa5"; const char *const Separator = "\xee\x80\xa6"; const char *const Subtract = "\xee\x80\xa7"; const char *const Decimal = "\xee\x80\xa8"; const char *const Divide = "\xee\x80\xa9"; const char *const F1 = "\xee\x80\xb1"; const char *const F2 = "\xee\x80\xb2"; const char *const F3 = "\xee\x80\xb3"; const char *const F4 = "\xee\x80\xb4"; const char *const F5 = "\xee\x80\xb5"; const char *const F6 = "\xee\x80\xb6"; const char *const F7 = "\xee\x80\xb7"; const char *const F8 = "\xee\x80\xb8"; const char *const F9 = "\xee\x80\xb9"; const char *const F10 = "\xee\x80\xba"; const char *const F11 = "\xee\x80\xbb"; const char *const F12 = "\xee\x80\xbc"; const char *const Command = "\xee\x80\xbd"; const char *const Meta = Command; } // namespace keys namespace detail { class Keyboard; } // namespace detail class Shortcut // copyable { public: Shortcut& operator << (const std::string& key) { keys_.push_back(key); return *this; } Shortcut& operator << (const char* key) { keys_.push_back(key); return *this; } private: friend class detail::Keyboard; std::vector keys_; }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/picojson.h ================================================ /* * Copyright 2009-2010 Cybozu Labs, Inc. * Copyright 2011-2014 Kazuho Oku * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef picojson_h #define picojson_h #include #include #include #include #include #include #include #include #include #include #include // for isnan/isinf #if __cplusplus>=201103L # include #else extern "C" { # ifdef _MSC_VER # include # elif defined(__INTEL_COMPILER) # include # else # include # endif } #endif // experimental support for int64_t (see README.mkdn for detail) #ifdef PICOJSON_USE_INT64 # define __STDC_FORMAT_MACROS # include # include #endif // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 #ifndef PICOJSON_USE_LOCALE # define PICOJSON_USE_LOCALE 1 #endif #if PICOJSON_USE_LOCALE extern "C" { # include } #endif #ifndef PICOJSON_ASSERT # define PICOJSON_ASSERT(e) do { if (! (e)) throw std::runtime_error(#e); } while (0) #endif #ifdef _MSC_VER #define SNPRINTF _snprintf_s #pragma warning(push) #pragma warning(disable : 4244) // conversion from int to char #pragma warning(disable : 4127) // conditional expression is constant #pragma warning(disable : 4702) // unreachable code #else #define SNPRINTF snprintf #endif namespace picojson { enum { null_type, boolean_type, number_type, string_type, array_type, object_type #ifdef PICOJSON_USE_INT64 , int64_type #endif }; enum { INDENT_WIDTH = 2 }; struct null {}; class value { public: typedef std::vector array; typedef std::map object; union _storage { bool boolean_; double number_; #ifdef PICOJSON_USE_INT64 int64_t int64_; #endif std::string* string_; array* array_; object* object_; }; protected: int type_; _storage u_; public: value(); value(int type, bool); explicit value(bool b); #ifdef PICOJSON_USE_INT64 explicit value(int64_t i); #endif explicit value(double n); explicit value(const std::string& s); explicit value(const array& a); explicit value(const object& o); explicit value(const char* s); value(const char* s, size_t len); ~value(); value(const value& x); value& operator=(const value& x); void swap(value& x); template bool is() const; template const T& get() const; template T& get(); bool evaluate_as_boolean() const; const value& get(size_t idx) const; const value& get(const std::string& key) const; value& get(size_t idx); value& get(const std::string& key); bool contains(size_t idx) const; bool contains(const std::string& key) const; std::string to_str() const; template void serialize(Iter os, bool prettify = false) const; std::string serialize(bool prettify = false) const; private: template value(const T*); // intentionally defined to block implicit conversion of pointer to bool template static void _indent(Iter os, int indent); template void _serialize(Iter os, int indent) const; std::string _serialize(int indent) const; }; typedef value::array array; typedef value::object object; inline value::value() : type_(null_type) {} inline value::value(int type, bool) : type_(type) { switch (type) { #define INIT(p, v) case p##type: u_.p = v; break INIT(boolean_, false); INIT(number_, 0.0); #ifdef PICOJSON_USE_INT64 INIT(int64_, 0); #endif INIT(string_, new std::string()); INIT(array_, new array()); INIT(object_, new object()); #undef INIT default: break; } } inline value::value(bool b) : type_(boolean_type) { u_.boolean_ = b; } #ifdef PICOJSON_USE_INT64 inline value::value(int64_t i) : type_(int64_type) { u_.int64_ = i; } #endif inline value::value(double n) : type_(number_type) { if ( #ifdef _MSC_VER ! _finite(n) #elif __cplusplus>=201103L || !(defined(isnan) && defined(isinf)) std::isnan(n) || std::isinf(n) #else isnan(n) || isinf(n) #endif ) { throw std::overflow_error(""); } u_.number_ = n; } inline value::value(const std::string& s) : type_(string_type) { u_.string_ = new std::string(s); } inline value::value(const array& a) : type_(array_type) { u_.array_ = new array(a); } inline value::value(const object& o) : type_(object_type) { u_.object_ = new object(o); } inline value::value(const char* s) : type_(string_type) { u_.string_ = new std::string(s); } inline value::value(const char* s, size_t len) : type_(string_type) { u_.string_ = new std::string(s, len); } inline value::~value() { switch (type_) { #define DEINIT(p) case p##type: delete u_.p; break DEINIT(string_); DEINIT(array_); DEINIT(object_); #undef DEINIT default: break; } } inline value::value(const value& x) : type_(x.type_) { switch (type_) { #define INIT(p, v) case p##type: u_.p = v; break INIT(string_, new std::string(*x.u_.string_)); INIT(array_, new array(*x.u_.array_)); INIT(object_, new object(*x.u_.object_)); #undef INIT default: u_ = x.u_; break; } } inline value& value::operator=(const value& x) { if (this != &x) { this->~value(); new (this) value(x); } return *this; } inline void value::swap(value& x) { std::swap(type_, x.type_); std::swap(u_, x.u_); } #define IS(ctype, jtype) \ template <> inline bool value::is() const { \ return type_ == jtype##_type; \ } IS(null, null) IS(bool, boolean) #ifdef PICOJSON_USE_INT64 IS(int64_t, int64) #endif IS(std::string, string) IS(array, array) IS(object, object) #undef IS template <> inline bool value::is() const { return type_ == number_type #ifdef PICOJSON_USE_INT64 || type_ == int64_type #endif ; } #define GET(ctype, var) \ template <> inline const ctype& value::get() const { \ PICOJSON_ASSERT("type mismatch! call is() before get()" \ && is()); \ return var; \ } \ template <> inline ctype& value::get() { \ PICOJSON_ASSERT("type mismatch! call is() before get()" \ && is()); \ return var; \ } GET(bool, u_.boolean_) GET(std::string, *u_.string_) GET(array, *u_.array_) GET(object, *u_.object_) #ifdef PICOJSON_USE_INT64 GET(double, (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), u_.number_)) GET(int64_t, u_.int64_) #else GET(double, u_.number_) #endif #undef GET inline bool value::evaluate_as_boolean() const { switch (type_) { case null_type: return false; case boolean_type: return u_.boolean_; case number_type: return u_.number_ != 0; case string_type: return ! u_.string_->empty(); default: return true; } } inline const value& value::get(size_t idx) const { static value s_null; PICOJSON_ASSERT(is()); return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; } inline value& value::get(size_t idx) { static value s_null; PICOJSON_ASSERT(is()); return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; } inline const value& value::get(const std::string& key) const { static value s_null; PICOJSON_ASSERT(is()); object::const_iterator i = u_.object_->find(key); return i != u_.object_->end() ? i->second : s_null; } inline value& value::get(const std::string& key) { static value s_null; PICOJSON_ASSERT(is()); object::iterator i = u_.object_->find(key); return i != u_.object_->end() ? i->second : s_null; } inline bool value::contains(size_t idx) const { PICOJSON_ASSERT(is()); return idx < u_.array_->size(); } inline bool value::contains(const std::string& key) const { PICOJSON_ASSERT(is()); object::const_iterator i = u_.object_->find(key); return i != u_.object_->end(); } inline std::string value::to_str() const { switch (type_) { case null_type: return "null"; case boolean_type: return u_.boolean_ ? "true" : "false"; #ifdef PICOJSON_USE_INT64 case int64_type: { char buf[sizeof("-9223372036854775808")]; SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); return buf; } #endif case number_type: { char buf[256]; double tmp; SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); #if PICOJSON_USE_LOCALE char *decimal_point = localeconv()->decimal_point; if (strcmp(decimal_point, ".") != 0) { size_t decimal_point_len = strlen(decimal_point); for (char *p = buf; *p != '\0'; ++p) { if (strncmp(p, decimal_point, decimal_point_len) == 0) { return std::string(buf, p) + "." + (p + decimal_point_len); } } } #endif return buf; } case string_type: return *u_.string_; case array_type: return "array"; case object_type: return "object"; default: PICOJSON_ASSERT(0); #ifdef _MSC_VER __assume(0); #endif } return std::string(); } template void copy(const std::string& s, Iter oi) { std::copy(s.begin(), s.end(), oi); } template void serialize_str(const std::string& s, Iter oi) { *oi++ = '"'; for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { switch (*i) { #define MAP(val, sym) case val: copy(sym, oi); break MAP('"', "\\\""); MAP('\\', "\\\\"); MAP('/', "\\/"); MAP('\b', "\\b"); MAP('\f', "\\f"); MAP('\n', "\\n"); MAP('\r', "\\r"); MAP('\t', "\\t"); #undef MAP default: if (static_cast(*i) < 0x20 || *i == 0x7f) { char buf[7]; SNPRINTF(buf, sizeof(buf), "\\u%04x", *i & 0xff); copy(buf, buf + 6, oi); } else { *oi++ = *i; } break; } } *oi++ = '"'; } template void value::serialize(Iter oi, bool prettify) const { return _serialize(oi, prettify ? 0 : -1); } inline std::string value::serialize(bool prettify) const { return _serialize(prettify ? 0 : -1); } template void value::_indent(Iter oi, int indent) { *oi++ = '\n'; for (int i = 0; i < indent * INDENT_WIDTH; ++i) { *oi++ = ' '; } } template void value::_serialize(Iter oi, int indent) const { switch (type_) { case string_type: serialize_str(*u_.string_, oi); break; case array_type: { *oi++ = '['; if (indent != -1) { ++indent; } for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { if (i != u_.array_->begin()) { *oi++ = ','; } if (indent != -1) { _indent(oi, indent); } i->_serialize(oi, indent); } if (indent != -1) { --indent; if (! u_.array_->empty()) { _indent(oi, indent); } } *oi++ = ']'; break; } case object_type: { *oi++ = '{'; if (indent != -1) { ++indent; } for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { if (i != u_.object_->begin()) { *oi++ = ','; } if (indent != -1) { _indent(oi, indent); } serialize_str(i->first, oi); *oi++ = ':'; if (indent != -1) { *oi++ = ' '; } i->second._serialize(oi, indent); } if (indent != -1) { --indent; if (! u_.object_->empty()) { _indent(oi, indent); } } *oi++ = '}'; break; } default: copy(to_str(), oi); break; } if (indent == 0) { *oi++ = '\n'; } } inline std::string value::_serialize(int indent) const { std::string s; _serialize(std::back_inserter(s), indent); return s; } template class input { protected: Iter cur_, end_; int last_ch_; bool ungot_; int line_; public: input(const Iter& first, const Iter& last) : cur_(first), end_(last), last_ch_(-1), ungot_(false), line_(1) {} int getc() { if (ungot_) { ungot_ = false; return last_ch_; } if (cur_ == end_) { last_ch_ = -1; return -1; } if (last_ch_ == '\n') { line_++; } last_ch_ = *cur_ & 0xff; ++cur_; return last_ch_; } void ungetc() { if (last_ch_ != -1) { PICOJSON_ASSERT(! ungot_); ungot_ = true; } } Iter cur() const { return cur_; } int line() const { return line_; } void skip_ws() { while (1) { int ch = getc(); if (! (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { ungetc(); break; } } } bool expect(int expect) { skip_ws(); if (getc() != expect) { ungetc(); return false; } return true; } bool match(const std::string& pattern) { for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { if (getc() != *pi) { ungetc(); return false; } } return true; } }; template inline int _parse_quadhex(input &in) { int uni_ch = 0, hex; for (int i = 0; i < 4; i++) { if ((hex = in.getc()) == -1) { return -1; } if ('0' <= hex && hex <= '9') { hex -= '0'; } else if ('A' <= hex && hex <= 'F') { hex -= 'A' - 0xa; } else if ('a' <= hex && hex <= 'f') { hex -= 'a' - 0xa; } else { in.ungetc(); return -1; } uni_ch = uni_ch * 16 + hex; } return uni_ch; } template inline bool _parse_codepoint(String& out, input& in) { int uni_ch; if ((uni_ch = _parse_quadhex(in)) == -1) { return false; } if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { if (0xdc00 <= uni_ch) { // a second 16-bit of a surrogate pair appeared return false; } // first 16-bit of surrogate pair, get the next one if (in.getc() != '\\' || in.getc() != 'u') { in.ungetc(); return false; } int second = _parse_quadhex(in); if (! (0xdc00 <= second && second <= 0xdfff)) { return false; } uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); uni_ch += 0x10000; } if (uni_ch < 0x80) { out.push_back(uni_ch); } else { if (uni_ch < 0x800) { out.push_back(0xc0 | (uni_ch >> 6)); } else { if (uni_ch < 0x10000) { out.push_back(0xe0 | (uni_ch >> 12)); } else { out.push_back(0xf0 | (uni_ch >> 18)); out.push_back(0x80 | ((uni_ch >> 12) & 0x3f)); } out.push_back(0x80 | ((uni_ch >> 6) & 0x3f)); } out.push_back(0x80 | (uni_ch & 0x3f)); } return true; } template inline bool _parse_string(String& out, input& in) { while (1) { int ch = in.getc(); if (ch < ' ') { in.ungetc(); return false; } else if (ch == '"') { return true; } else if (ch == '\\') { if ((ch = in.getc()) == -1) { return false; } switch (ch) { #define MAP(sym, val) case sym: out.push_back(val); break MAP('"', '\"'); MAP('\\', '\\'); MAP('/', '/'); MAP('b', '\b'); MAP('f', '\f'); MAP('n', '\n'); MAP('r', '\r'); MAP('t', '\t'); #undef MAP case 'u': if (! _parse_codepoint(out, in)) { return false; } break; default: return false; } } else { out.push_back(ch); } } return false; } template inline bool _parse_array(Context& ctx, input& in) { if (! ctx.parse_array_start()) { return false; } size_t idx = 0; if (in.expect(']')) { return ctx.parse_array_stop(idx); } do { if (! ctx.parse_array_item(in, idx)) { return false; } idx++; } while (in.expect(',')); return in.expect(']') && ctx.parse_array_stop(idx); } template inline bool _parse_object(Context& ctx, input& in) { if (! ctx.parse_object_start()) { return false; } if (in.expect('}')) { return true; } do { std::string key; if (! in.expect('"') || ! _parse_string(key, in) || ! in.expect(':')) { return false; } if (! ctx.parse_object_item(in, key)) { return false; } } while (in.expect(',')); return in.expect('}'); } template inline std::string _parse_number(input& in) { std::string num_str; while (1) { int ch = in.getc(); if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { num_str.push_back(ch); } else if (ch == '.') { #if PICOJSON_USE_LOCALE num_str += localeconv()->decimal_point; #else num_str.push_back('.'); #endif } else { in.ungetc(); break; } } return num_str; } template inline bool _parse(Context& ctx, input& in) { in.skip_ws(); int ch = in.getc(); switch (ch) { #define IS(ch, text, op) case ch: \ if (in.match(text) && op) { \ return true; \ } else { \ return false; \ } IS('n', "ull", ctx.set_null()); IS('f', "alse", ctx.set_bool(false)); IS('t', "rue", ctx.set_bool(true)); #undef IS case '"': return ctx.parse_string(in); case '[': return _parse_array(ctx, in); case '{': return _parse_object(ctx, in); default: if (('0' <= ch && ch <= '9') || ch == '-') { double f; char *endp; in.ungetc(); std::string num_str = _parse_number(in); if (num_str.empty()) { return false; } #ifdef PICOJSON_USE_INT64 { errno = 0; intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && endp == num_str.c_str() + num_str.size()) { ctx.set_int64(ival); return true; } } #endif f = strtod(num_str.c_str(), &endp); if (endp == num_str.c_str() + num_str.size()) { ctx.set_number(f); return true; } return false; } break; } in.ungetc(); return false; } class deny_parse_context { public: bool set_null() { return false; } bool set_bool(bool) { return false; } #ifdef PICOJSON_USE_INT64 bool set_int64(int64_t) { return false; } #endif bool set_number(double) { return false; } template bool parse_string(input&) { return false; } bool parse_array_start() { return false; } template bool parse_array_item(input&, size_t) { return false; } bool parse_array_stop(size_t) { return false; } bool parse_object_start() { return false; } template bool parse_object_item(input&, const std::string&) { return false; } }; class default_parse_context { protected: value* out_; public: default_parse_context(value* out) : out_(out) {} bool set_null() { *out_ = value(); return true; } bool set_bool(bool b) { *out_ = value(b); return true; } #ifdef PICOJSON_USE_INT64 bool set_int64(int64_t i) { *out_ = value(i); return true; } #endif bool set_number(double f) { *out_ = value(f); return true; } template bool parse_string(input& in) { *out_ = value(string_type, false); return _parse_string(out_->get(), in); } bool parse_array_start() { *out_ = value(array_type, false); return true; } template bool parse_array_item(input& in, size_t) { array& a = out_->get(); a.push_back(value()); default_parse_context ctx(&a.back()); return _parse(ctx, in); } bool parse_array_stop(size_t) { return true; } bool parse_object_start() { *out_ = value(object_type, false); return true; } template bool parse_object_item(input& in, const std::string& key) { object& o = out_->get(); default_parse_context ctx(&o[key]); return _parse(ctx, in); } private: default_parse_context(const default_parse_context&); default_parse_context& operator=(const default_parse_context&); }; class null_parse_context { public: struct dummy_str { void push_back(int) {} }; public: null_parse_context() {} bool set_null() { return true; } bool set_bool(bool) { return true; } #ifdef PICOJSON_USE_INT64 bool set_int64(int64_t) { return true; } #endif bool set_number(double) { return true; } template bool parse_string(input& in) { dummy_str s; return _parse_string(s, in); } bool parse_array_start() { return true; } template bool parse_array_item(input& in, size_t) { return _parse(*this, in); } bool parse_array_stop(size_t) { return true; } bool parse_object_start() { return true; } template bool parse_object_item(input& in, const std::string&) { return _parse(*this, in); } private: null_parse_context(const null_parse_context&); null_parse_context& operator=(const null_parse_context&); }; // obsolete, use the version below template inline std::string parse(value& out, Iter& pos, const Iter& last) { std::string err; pos = parse(out, pos, last, &err); return err; } template inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, std::string* err) { input in(first, last); if (! _parse(ctx, in) && err != NULL) { char buf[64]; SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); *err = buf; while (1) { int ch = in.getc(); if (ch == -1 || ch == '\n') { break; } else if (ch >= ' ') { err->push_back(ch); } } } return in.cur(); } template inline Iter parse(value& out, const Iter& first, const Iter& last, std::string* err) { default_parse_context ctx(&out); return _parse(ctx, first, last, err); } inline std::string parse(value& out, std::istream& is) { std::string err; parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); return err; } template struct last_error_t { static std::string s; }; template std::string last_error_t::s; inline void set_last_error(const std::string& s) { last_error_t::s = s; } inline const std::string& get_last_error() { return last_error_t::s; } inline bool operator==(const value& x, const value& y) { if (x.is()) return y.is(); #define PICOJSON_CMP(type) \ if (x.is()) \ return y.is() && x.get() == y.get() PICOJSON_CMP(bool); PICOJSON_CMP(double); PICOJSON_CMP(std::string); PICOJSON_CMP(array); PICOJSON_CMP(object); #undef PICOJSON_CMP PICOJSON_ASSERT(0); #ifdef _MSC_VER __assume(0); #endif return false; } inline bool operator!=(const value& x, const value& y) { return ! (x == y); } } namespace std { template<> inline void swap(picojson::value& x, picojson::value& y) { x.swap(y); } } inline std::istream& operator>>(std::istream& is, picojson::value& x) { picojson::set_last_error(std::string()); std::string err = picojson::parse(x, is); if (! err.empty()) { picojson::set_last_error(err); is.setstate(std::ios::failbit); } return is; } inline std::ostream& operator<<(std::ostream& os, const picojson::value& x) { x.serialize(std::ostream_iterator(os)); return os; } #ifdef _MSC_VER #pragma warning(pop) #endif #endif #ifdef TEST_PICOJSON #ifdef _MSC_VER #pragma warning(disable : 4127) // conditional expression is constant #endif using namespace std; static bool success = true; static int test_num = 0; static void ok(bool b, const char* name = "") { if (! b) success = false; printf("%s %d - %s\n", b ? "ok" : "ng", ++test_num, name); } static void done_testing() { printf("1..%d\n", test_num); } template void is(const T& x, const T& y, const char* name = "") { if (x == y) { ok(true, name); } else { ok(false, name); } } #include #include #include #include int main(void) { #if PICOJSON_USE_LOCALE setlocale(LC_ALL, ""); #endif // constructors #define TEST(expr, expected) \ is(picojson::value expr .serialize(), string(expected), "picojson::value" #expr) TEST( (true), "true"); TEST( (false), "false"); TEST( (42.0), "42"); TEST( (string("hello")), "\"hello\""); TEST( ("hello"), "\"hello\""); TEST( ("hello", 4), "\"hell\""); { double a = 1; for (int i = 0; i < 1024; i++) { picojson::value vi(a); std::stringstream ss; ss << vi; picojson::value vo; ss >> vo; double b = vo.get(); if ((i < 53 && a != b) || fabs(a - b) / b > 1e-8) { printf("ng i=%d a=%.18e b=%.18e\n", i, a, b); } a *= 2; } } #undef TEST #define TEST(in, type, cmp, serialize_test) { \ picojson::value v; \ const char* s = in; \ string err = picojson::parse(v, s, s + strlen(s)); \ ok(err.empty(), in " no error"); \ ok(v.is(), in " check type"); \ is(v.get(), cmp, in " correct output"); \ is(*s, '\0', in " read to eof"); \ if (serialize_test) { \ is(v.serialize(), string(in), in " serialize"); \ } \ } TEST("false", bool, false, true); TEST("true", bool, true, true); TEST("90.5", double, 90.5, false); TEST("1.7976931348623157e+308", double, DBL_MAX, false); TEST("\"hello\"", string, string("hello"), true); TEST("\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"", string, string("\"\\/\b\f\n\r\t"), true); TEST("\"\\u0061\\u30af\\u30ea\\u30b9\"", string, string("a\xe3\x82\xaf\xe3\x83\xaa\xe3\x82\xb9"), false); TEST("\"\\ud840\\udc0b\"", string, string("\xf0\xa0\x80\x8b"), false); #ifdef PICOJSON_USE_INT64 TEST("0", int64_t, 0, true); TEST("-9223372036854775808", int64_t, std::numeric_limits::min(), true); TEST("9223372036854775807", int64_t, std::numeric_limits::max(), true); #endif #undef TEST #define TEST(type, expr) { \ picojson::value v; \ const char *s = expr; \ string err = picojson::parse(v, s, s + strlen(s)); \ ok(err.empty(), "empty " #type " no error"); \ ok(v.is(), "empty " #type " check type"); \ ok(v.get().empty(), "check " #type " array size"); \ } TEST(array, "[]"); TEST(object, "{}"); #undef TEST { picojson::value v; const char *s = "[1,true,\"hello\"]"; string err = picojson::parse(v, s, s + strlen(s)); ok(err.empty(), "array no error"); ok(v.is(), "array check type"); is(v.get().size(), size_t(3), "check array size"); ok(v.contains(0), "check contains array[0]"); ok(v.get(0).is(), "check array[0] type"); is(v.get(0).get(), 1.0, "check array[0] value"); ok(v.contains(1), "check contains array[1]"); ok(v.get(1).is(), "check array[1] type"); ok(v.get(1).get(), "check array[1] value"); ok(v.contains(2), "check contains array[2]"); ok(v.get(2).is(), "check array[2] type"); is(v.get(2).get(), string("hello"), "check array[2] value"); ok(!v.contains(3), "check not contains array[3]"); } { picojson::value v; const char *s = "{ \"a\": true }"; string err = picojson::parse(v, s, s + strlen(s)); ok(err.empty(), "object no error"); ok(v.is(), "object check type"); is(v.get().size(), size_t(1), "check object size"); ok(v.contains("a"), "check contains property"); ok(v.get("a").is(), "check bool property exists"); is(v.get("a").get(), true, "check bool property value"); is(v.serialize(), string("{\"a\":true}"), "serialize object"); ok(!v.contains("z"), "check not contains property"); } #define TEST(json, msg) do { \ picojson::value v; \ const char *s = json; \ string err = picojson::parse(v, s, s + strlen(s)); \ is(err, string("syntax error at line " msg), msg); \ } while (0) TEST("falsoa", "1 near: oa"); TEST("{]", "1 near: ]"); TEST("\n\bbell", "2 near: bell"); TEST("\"abc\nd\"", "1 near: "); #undef TEST { picojson::value v1, v2; const char *s; string err; s = "{ \"b\": true, \"a\": [1,2,\"three\"], \"d\": 2 }"; err = picojson::parse(v1, s, s + strlen(s)); s = "{ \"d\": 2.0, \"b\": true, \"a\": [1,2,\"three\"] }"; err = picojson::parse(v2, s, s + strlen(s)); ok((v1 == v2), "check == operator in deep comparison"); } { picojson::value v1, v2; const char *s; string err; s = "{ \"b\": true, \"a\": [1,2,\"three\"], \"d\": 2 }"; err = picojson::parse(v1, s, s + strlen(s)); s = "{ \"d\": 2.0, \"a\": [1,\"three\"], \"b\": true }"; err = picojson::parse(v2, s, s + strlen(s)); ok((v1 != v2), "check != operator for array in deep comparison"); } { picojson::value v1, v2; const char *s; string err; s = "{ \"b\": true, \"a\": [1,2,\"three\"], \"d\": 2 }"; err = picojson::parse(v1, s, s + strlen(s)); s = "{ \"d\": 2.0, \"a\": [1,2,\"three\"], \"b\": false }"; err = picojson::parse(v2, s, s + strlen(s)); ok((v1 != v2), "check != operator for object in deep comparison"); } { picojson::value v1, v2; const char *s; string err; s = "{ \"b\": true, \"a\": [1,2,\"three\"], \"d\": 2 }"; err = picojson::parse(v1, s, s + strlen(s)); picojson::object& o = v1.get(); o.erase("b"); picojson::array& a = o["a"].get(); picojson::array::iterator i; i = std::remove(a.begin(), a.end(), picojson::value(std::string("three"))); a.erase(i, a.end()); s = "{ \"a\": [1,2], \"d\": 2 }"; err = picojson::parse(v2, s, s + strlen(s)); ok((v1 == v2), "check erase()"); } ok(picojson::value(3.0).serialize() == "3", "integral number should be serialized as a integer"); { const char* s = "{ \"a\": [1,2], \"d\": 2 }"; picojson::null_parse_context ctx; string err; picojson::_parse(ctx, s, s + strlen(s), &err); ok(err.empty(), "null_parse_context"); } { picojson::value v1, v2; v1 = picojson::value(true); swap(v1, v2); ok(v1.is(), "swap (null)"); ok(v2.get() == true, "swap (bool)"); v1 = picojson::value("a"); v2 = picojson::value(1.0); swap(v1, v2); ok(v1.get() == 1.0, "swap (dobule)"); ok(v2.get() == "a", "swap (string)"); v1 = picojson::value(picojson::object()); v2 = picojson::value(picojson::array()); swap(v1, v2); ok(v1.is(), "swap (array)"); ok(v2.is(), "swap (object)"); } { picojson::value v; const char *s = "{ \"a\": 1, \"b\": [ 2, { \"b1\": \"abc\" } ], \"c\": {}, \"d\": [] }"; string err; err = picojson::parse(v, s, s + strlen(s)); ok(err.empty(), "parse test data for prettifying output"); ok(v.serialize() == "{\"a\":1,\"b\":[2,{\"b1\":\"abc\"}],\"c\":{},\"d\":[]}", "non-prettifying output"); ok(v.serialize(true) == "{\n \"a\": 1,\n \"b\": [\n 2,\n {\n \"b1\": \"abc\"\n }\n ],\n \"c\": {},\n \"d\": []\n}\n", "prettifying output"); } try { picojson::value v(std::numeric_limits::quiet_NaN()); ok(false, "should not accept NaN"); } catch (std::overflow_error e) { ok(true, "should not accept NaN"); } try { picojson::value v(std::numeric_limits::infinity()); ok(false, "should not accept infinity"); } catch (std::overflow_error e) { ok(true, "should not accept infinity"); } try { picojson::value v(123.); ok(! v.is(), "is() should return false"); v.get(); ok(false, "get() should raise an error"); } catch (std::runtime_error e) { ok(true, "get() should raise an error"); } #ifdef PICOJSON_USE_INT64 { picojson::value v1((int64_t)123); ok(v1.is(), "is int64_t"); ok(v1.is(), "is double as well"); ok(v1.serialize() == "123", "serialize the value"); ok(v1.get() == 123, "value is correct as int64_t"); ok(v1.get(), "value is correct as double"); ok(! v1.is(), "is no more int64_type once get() is called"); ok(v1.is(), "and is still a double"); const char *s = "-9223372036854775809"; ok(picojson::parse(v1, s, s + strlen(s)).empty(), "parse underflowing int64_t"); ok(! v1.is(), "underflowing int is not int64_t"); ok(v1.is(), "underflowing int is double"); ok(v1.get() + 9.22337203685478e+18 < 65536, "double value is somewhat correct"); } #endif done_testing(); return success ? 0 : 1; } #endif ================================================ FILE: include/webdriverxx/response_status_code.h ================================================ #ifndef WEBDRIVERXX_RESPONSE_STATUS_CODE_H #define WEBDRIVERXX_RESPONSE_STATUS_CODE_H namespace webdriverxx { namespace response_status_code { enum Value { kSuccess = 0, kNoSuchDriver = 6, kNoSuchElement = 7, kNoSuchFrame = 8, kUnknownCommand = 9, kStaleElementReference = 10, kElementNotVisible = 11, kInvalidElementState = 12, kUnknownError = 13, kElementIsNotSelectable = 15, kJavaScriptError = 17, kXPathLookupError = 19, kTimeout = 21, kNoSuchWindow = 23, kInvalidCookieDomain = 24, kUnableToSetCookie = 25, kUnexpectedAlertOpen = 26, kNoAlertOpenError = 27, kScriptTimeout = 28, kInvalidElementCoordinates = 29, kIMENotAvailable = 30, kIMEEngineActivationFailed = 31, kInvalidSelector = 32, kSessionNotCreatedException = 33, kMoveTargetOutOfBounds = 34 }; inline const char* ToString(Value code) { switch(code) { case kSuccess: return "The command executed successfully."; case kNoSuchDriver: return "A session is either terminated or not started"; case kNoSuchElement: return "An element could not be located on the page using the given search parameters."; case kNoSuchFrame: return "A request to switch to a frame could not be satisfied because the frame could not be found."; 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."; case kStaleElementReference: return "An element command failed because the referenced element is no longer attached to the DOM."; case kElementNotVisible: return "An element command could not be completed because the element is not visible on the page."; 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)."; case kUnknownError: return "An unknown server-side error occurred while processing the command."; case kElementIsNotSelectable: return "An attempt was made to select an element that cannot be selected."; case kJavaScriptError: return "An error occurred while executing user supplied JavaScript."; case kXPathLookupError: return "An error occurred while searching for an element by XPath."; case kTimeout: return "An operation did not complete before its timeout expired."; case kNoSuchWindow: return "A request to switch to a different window could not be satisfied because the window could not be found."; case kInvalidCookieDomain: return "An illegal attempt was made to set a cookie under a different domain than the current page."; case kUnableToSetCookie: return "A request to set a cookie's value could not be satisfied."; case kUnexpectedAlertOpen: return "A modal dialog was open, blocking this operation"; case kNoAlertOpenError: return "An attempt was made to operate on a modal dialog when one was not open."; case kScriptTimeout: return "A script did not complete before its timeout expired."; case kInvalidElementCoordinates: return "The coordinates provided to an interactions operation are invalid."; case kIMENotAvailable: return "IME was not available."; case kIMEEngineActivationFailed: return "An IME engine could not be started."; case kInvalidSelector: return "Argument was an invalid selector (e.g. XPath/CSS)."; case kSessionNotCreatedException: return "A new session could not be created."; case kMoveTargetOutOfBounds: return "Target provided for a move action is out of bounds."; } return "Unknown"; } } // namespace response_status_code } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/session.h ================================================ #ifndef WEBDRIVERXX_SESSION_H #define WEBDRIVERXX_SESSION_H #include "element.h" #include "window.h" #include "by.h" #include "capabilities.h" #include "keys.h" #include "js_args.h" #include "detail/resource.h" #include "detail/keyboard.h" #include "detail/shared.h" #include "detail/factories_impl.h" #include "picojson.h" #include namespace webdriverxx { class Client; class Session { // copyable public: Capabilities GetCapabilities() const; std::string GetSource() const; std::string GetTitle() const; std::string GetUrl() const; std::string GetScreenshot() const; // Base64 PNG const Session& Navigate(const std::string& url) const; const Session& Get(const std::string& url) const; // Same as Navigate const Session& Forward() const; const Session& Back() const; const Session& Refresh() const; const Session& Execute(const std::string& script, const JsArgs& args = JsArgs()) const; template T Eval(const std::string& script, const JsArgs& args = JsArgs()) const; const Session& ExecuteAsync(const std::string& script, const JsArgs& args = JsArgs()) const; template T EvalAsync(const std::string& script, const JsArgs& args = JsArgs()) const; const Session& SetFocusToFrame(const Element& frame) const; const Session& SetFocusToFrame(const std::string& id) const; const Session& SetFocusToFrame(int number) const; const Session& SetFocusToDefaultFrame() const; const Session& SetFocusToParentFrame() const; std::vector GetWindows() const; Window GetCurrentWindow() const; const Session& CloseCurrentWindow() const; const Session& SetFocusToWindow(const std::string& window_name) const; const Session& SetFocusToWindow(const Window& window) const; Element GetActiveElement() const; Element FindElement(const By& by) const; std::vector FindElements(const By& by) const; std::vector GetCookies() const; const Session& SetCookie(const Cookie& cookie) const; const Session& DeleteCookies() const; const Session& DeleteCookie(const std::string& name) const; std::string GetAlertText() const; const Session& SendKeysToAlert(const std::string& text) const; const Session& AcceptAlert() const; const Session& DismissAlert() const; const Session& SendKeys(const std::string& keys) const; const Session& SendKeys(const Shortcut& shortcut) const; const Session& MoveToTopLeftOf(const Element&, const Offset& = Offset()) const; const Session& MoveToCenterOf(const Element&) const; const Session& MoveTo(const Offset&) const; const Session& Click(mouse::Button = mouse::LeftButton) const; const Session& DoubleClick() const; const Session& ButtonDown(mouse::Button = mouse::LeftButton) const; const Session& ButtonUp(mouse::Button = mouse::LeftButton) const; const Session& SetTimeoutMs(timeout::Type type, int milliseconds); const Session& SetImplicitTimeoutMs(int milliseconds); const Session& SetAsyncScriptTimeoutMs(int milliseconds); void DeleteSession() const; // No need to delete sessions created by WebDriver or Client virtual ~Session() {} private: friend class Client; // Only Client can create Sessions explicit Session(const detail::Shared& resource); Window MakeWindow(const std::string& handle) const; detail::Keyboard GetKeyboard() const; template void InternalEval(const std::string& webdriver_command, const std::string& script, const JsArgs& args, T& result) const; void InternalEval(const std::string& webdriver_command, const std::string& script, const JsArgs& args, Element& result) const; picojson::value InternalEvalJsonValue(const std::string& command, const std::string& script, const JsArgs& args) const; const Session& InternalSetFocusToFrame(const picojson::value& id) const; const Session& InternalMoveTo(const Element*, const Offset*) const; const Session& InternalMouseButtonCommand(const char* command, mouse::Button button) const; private: detail::Shared resource_; detail::Shared factory_; }; } // namespace webdriverxx #include "session.inl" #endif ================================================ FILE: include/webdriverxx/session.inl ================================================ #include "conversions.h" #include "detail/error_handling.h" #include "detail/types.h" #include namespace webdriverxx { inline Session::Session(const detail::Shared& resource) : resource_(resource) , factory_(new detail::SessionFactory(resource)) {} inline void Session::DeleteSession() const { resource_->Delete(); } inline Capabilities Session::GetCapabilities() const { return Capabilities(resource_->Get().get()); } inline std::string Session::GetSource() const { return resource_->GetString("source"); } inline std::string Session::GetTitle() const { return resource_->GetString("title"); } inline std::string Session::GetUrl() const { return resource_->GetString("url"); } inline std::string Session::GetScreenshot() const { return resource_->GetString("screenshot"); } inline const Session& Session::SetTimeoutMs(timeout::Type type, int milliseconds) { resource_->Post("timeouts", JsonObject() .Set("type", type) .Set("ms", milliseconds) ); return *this; } inline const Session& Session::SetImplicitTimeoutMs(int milliseconds) { resource_->Post("timeouts/implicit_wait", JsonObject().Set("ms", milliseconds)); return *this; } inline const Session& Session::SetAsyncScriptTimeoutMs(int milliseconds) { resource_->Post("timeouts/async_script", JsonObject().Set("ms", milliseconds)); return *this; } inline Window Session::GetCurrentWindow() const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return MakeWindow(resource_->GetString("window_handle")); WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline const Session& Session::CloseCurrentWindow() const { resource_->Delete("window"); return *this; } inline const Session& Session::Navigate(const std::string& url) const { resource_->Post("url", "url", url); return *this; } inline const Session& Session::Get(const std::string& url) const { return Navigate(url); } inline const Session& Session::Forward() const { resource_->Post("forward"); return *this; } inline const Session& Session::Back() const { resource_->Post("back"); return *this; } inline const Session& Session::Refresh() const { resource_->Post("refresh"); return *this; } inline const Session& Session::Execute(const std::string& script, const JsArgs& args) const { InternalEvalJsonValue("execute", script, args); return *this; } template T Session::Eval(const std::string& script, const JsArgs& args) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() T result = T(); InternalEval("execute", script, args, result); return result; WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() << "script: " << script ) } inline const Session& Session::ExecuteAsync(const std::string& script, const JsArgs& args) const { InternalEvalJsonValue("execute_async", script, args); return *this; } template T Session::EvalAsync(const std::string& script, const JsArgs& args) const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() T result; InternalEval("execute_async", script, args, result); return result; WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() << "script: " << script ) } inline const Session& Session::SetFocusToWindow(const std::string& window_name_or_handle) const { resource_->Post("window", "name", window_name_or_handle); return *this; } inline const Session& Session::SetFocusToWindow(const Window& window) const { SetFocusToWindow(window.GetHandle()); return *this; } inline const Session& Session::SetFocusToFrame(const Element& frame) const { return InternalSetFocusToFrame(ToJson(frame)); } inline const Session& Session::SetFocusToFrame(const std::string& id) const { return InternalSetFocusToFrame(ToJson(id)); } inline const Session& Session::SetFocusToFrame(int number) const { return InternalSetFocusToFrame(ToJson(number)); } inline const Session& Session::SetFocusToDefaultFrame() const { return InternalSetFocusToFrame(picojson::value()); } inline const Session& Session::SetFocusToParentFrame() const { resource_->Post("frame/parent"); return *this; } inline const Session& Session::InternalSetFocusToFrame(const picojson::value& id) const { resource_->Post("frame", JsonObject().Set("id", id)); return *this; } inline std::vector Session::GetWindows() const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() const auto handles = FromJson>( resource_->Get("window_handles") ); std::vector result; result.reserve(handles.size()); std::transform(handles.begin(), handles.end(), std::back_inserter(result), [this](const std::string& window_handle){ return MakeWindow(window_handle); }); return result; WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline Element Session::GetActiveElement() const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return factory_->MakeElement(FromJson(resource_->Post("element/active")).ref); WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline Element Session::FindElement(const By& by) const { return factory_->MakeFinder(resource_).FindElement(by); } inline std::vector Session::FindElements(const By& by) const { return factory_->MakeFinder(resource_).FindElements(by); } inline std::vector Session::GetCookies() const { WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() return FromJson>(resource_->Get("cookie")); WEBDRIVERXX_FUNCTION_CONTEXT_END() } inline const Session& Session::SetCookie(const Cookie& cookie) const { resource_->Post("cookie", JsonObject() .Set("cookie", ToJson(cookie))); return *this; } inline const Session& Session::DeleteCookies() const { resource_->Delete("cookie"); return *this; } inline const Session& Session::DeleteCookie(const std::string& name) const { resource_->Delete(std::string("cookie/") + name); return *this; } inline std::string Session::GetAlertText() const { return resource_->GetString("alert_text"); } inline const Session& Session::SendKeysToAlert(const std::string& text) const { resource_->Post("alert_text", "text", text); return *this; } inline const Session& Session::AcceptAlert() const { resource_->Post("accept_alert"); return *this; } inline const Session& Session::DismissAlert() const { resource_->Post("dismiss_alert"); return *this; } inline const Session& Session::SendKeys(const std::string& keys) const { GetKeyboard().SendKeys(keys); return *this; } inline const Session& Session::SendKeys(const Shortcut& shortcut) const { GetKeyboard().SendKeys(shortcut); return *this; } inline const Session& Session::MoveToTopLeftOf(const Element& element, const Offset& offset) const { return InternalMoveTo(&element, &offset); } inline const Session& Session::MoveToCenterOf(const Element& element) const { return InternalMoveTo(&element, nullptr); } inline const Session& Session::MoveTo(const Offset& offset) const { return InternalMoveTo(nullptr, &offset); } inline const Session& Session::InternalMoveTo( const Element* element, const Offset* offset ) const { JsonObject args; if (element) args.Set("element", element->GetRef()); if (offset) { args.Set("xoffset", offset->x); args.Set("yoffset", offset->y); } resource_->Post("moveto", args); return *this; } inline const Session& Session::Click(mouse::Button button) const { return InternalMouseButtonCommand("click", button); } inline const Session& Session::DoubleClick() const { resource_->Post("doubleclick"); return *this; } inline const Session& Session::ButtonDown(mouse::Button button) const { return InternalMouseButtonCommand("buttondown", button); } inline const Session& Session::ButtonUp(mouse::Button button) const { return InternalMouseButtonCommand("buttonup", button); } inline const Session& Session::InternalMouseButtonCommand(const char* command, mouse::Button button) const { resource_->Post(command, "button", static_cast(button)); return *this; } inline Window Session::MakeWindow(const std::string& handle) const { return Window(handle, detail::MakeSubResource(resource_, "window", handle) ); } inline detail::Keyboard Session::GetKeyboard() const { return detail::Keyboard(resource_, "keys"); } template void Session::InternalEval(const std::string& webdriver_command, const std::string& script, const JsArgs& args, T& result) const { result = FromJson(InternalEvalJsonValue(webdriver_command, script, args)); } inline void Session::InternalEval(const std::string& webdriver_command, const std::string& script, const JsArgs& args, Element& result) const { detail::ElementRef element_ref; InternalEval(webdriver_command, script, args, element_ref); result = factory_->MakeElement(element_ref.ref); } inline picojson::value Session::InternalEvalJsonValue( const std::string& webdriver_command, const std::string& script, const JsArgs& args ) const { return resource_->Post(webdriver_command, JsonObject() .Set("script", script) .Set("args", args.args_) ); } } // namespace webdriverxx ================================================ FILE: include/webdriverxx/types.h ================================================ #ifndef WEBDRIVERXX_TYPES_H #define WEBDRIVERXX_TYPES_H #include namespace webdriverxx { typedef unsigned long long TimePoint; typedef unsigned Duration; struct Size { int width; int height; Size() : width(0), height(0) {} }; struct Point { int x; int y; Point() : x(0), y(0) {} Point(int x, int y) : x(x), y(y) {} }; typedef Point Offset; struct Cookie { enum { NoExpiry = 0 }; std::string name; std::string value; std::string path; std::string domain; bool secure; bool http_only; int expiry; // seconds since midnight, January 1, 1970 UTC Cookie() : secure(false), http_only(false), expiry(NoExpiry) {} Cookie( const std::string& name, const std::string& value, const std::string& path = std::string(), const std::string& domain = std::string(), bool secure = false, bool http_only = false, int expiry = NoExpiry ) : name(name) , value(value) , path(path) , domain(domain) , secure(secure) , http_only(http_only) , expiry(expiry) {} bool operator == (const Cookie& c) const { return name == c.name && value == c.value && path == c.path && domain == c.domain && secure == c.secure && http_only == c.http_only && expiry == c.expiry ; } }; namespace timeout { typedef const char* Type; Type const Implicit = "implicit"; Type const PageLoad = "page load"; Type const Script = "script"; } // namespace timeout namespace mouse { enum Button { LeftButton = 0, MiddleButton = 1, RightButton = 2 }; } // namespace mouse } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/wait.h ================================================ #ifndef WEBDRIVERXX_WAIT_H #define WEBDRIVERXX_WAIT_H #include "detail/error_handling.h" #include "detail/time.h" #include "detail/to_string.h" #include #include namespace webdriverxx { namespace detail { template Value Wait( DescriptiveGetter getter, Duration timeoutMs = 5000, Duration intervalMs = 50 ) { const TimePoint timeout = detail::Now() + timeoutMs; for (;;) { const auto value_ptr = getter(nullptr); if (value_ptr) return *value_ptr; if (detail::Now() >= timeout) { std::string description; const auto value_ptr = getter(&description); if (value_ptr) return *value_ptr; throw WebDriverException(detail::Fmt() << "Timeout after " << timeoutMs << "ms of waiting, last attempt returned: " << description ); } detail::Sleep(intervalMs); } } template std::unique_ptr TryToCallGetter(Getter getter, std::string* description) { std::unique_ptr value_ptr; try { value_ptr.reset(new Value(getter())); } catch (const std::exception& e) { if (description) *description = e.what(); } return value_ptr; } } // namespace detail // Waits for a value returned by a supplied getter. // Returns that value or throws exception on timeout. // Getter is a function or function-like object that returns some copyable value. template auto WaitForValue( Getter getter, Duration timeoutMs = 5000, Duration intervalMs = 50 ) -> decltype(getter()) { typedef decltype(getter()) Value; return detail::Wait( [&getter](std::string* description) { return detail::TryToCallGetter(getter, description); }, timeoutMs, intervalMs); } // Waits until a truthy value is returned by a supplied getter. // Returns that value or throws exception on timeout. // Getter is a function or function-like object that returns some copyable value. template auto WaitUntil( Getter getter, Duration timeoutMs = 5000, Duration intervalMs = 50 ) -> decltype(getter()) { typedef decltype(getter()) Value; return detail::Wait( [&getter](std::string* description) -> std::unique_ptr { auto value_ptr = detail::TryToCallGetter(getter, description); if (!value_ptr || !!*value_ptr) return value_ptr; if (description) *description = "Value is falsy"; value_ptr.reset(); return value_ptr; }, timeoutMs, intervalMs); } } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/wait_match.h ================================================ #ifndef WEBDRIVERXX_WAIT_MATCH_H #define WEBDRIVERXX_WAIT_MATCH_H #include "wait.h" #include "detail/to_string.h" #include #ifdef WEBDRIVERXX_ENABLE_GMOCK_MATCHERS #include #include namespace webdriverxx { namespace detail { template class GMockMatcherAdapter { public: explicit GMockMatcherAdapter(::testing::Matcher matcher) : matcher_(matcher) {} bool Apply(const T& value) const { return matcher_.Matches(value); } std::string DescribeMismatch(const T& value) const { std::ostringstream s; s << "Expected: "; matcher_.DescribeTo(&s); s << ", actual: "; ToStream(value, s); const auto mismatch_details = GetMismatchDetails(value); if (!mismatch_details.empty()) s << ", " << mismatch_details; return s.str(); } private: std::string GetMismatchDetails(const T& value) const { std::ostringstream s; matcher_.ExplainMatchResultTo(value, &s); return s.str(); } private: ::testing::Matcher matcher_; }; } // detail template detail::GMockMatcherAdapter MakeMatcherAdapter(const M& matcher, typename std::enable_if>::value>::type* = nullptr) { return detail::GMockMatcherAdapter(matcher); }; } // namespace webdriverxx #endif // WEBDRIVERXX_ENABLE_GMOCK_MATCHERS namespace webdriverxx { namespace detail { template class PredicateMatcherAdapter { public: explicit PredicateMatcherAdapter(P& predicate) : predicate_(&predicate) {} bool Apply(const T& value) const { return (*predicate_)(value); } std::string DescribeMismatch(const T& value) const { return detail::Fmt() << "Value " << ToString(value) << " does not match predicate"; } private: P* predicate_; }; } // detail template void MakeMatcherAdapter(...); namespace detail { template PredicateMatcherAdapter SelectMakeMatcherAdapter(M& matcher, std::true_type /*no_custom_adapters*/) { return PredicateMatcherAdapter(matcher); } template auto SelectMakeMatcherAdapter(const M& matcher, std::false_type /*no_custom_adapters*/) -> decltype(MakeMatcherAdapter(matcher)) { return MakeMatcherAdapter(matcher); } } // detail // Waits until a value returned by a getter satisfies a supplied matcher. // Returns that value or throws exception on timeout. // Getter is a function or function-like object that returns some copyable value. // Matcher can be a predicate or a Google Mock matcher (if Google Mock matchers are enabled). template auto WaitForMatch( Getter getter, Matcher matcher, Duration timeoutMs = 5000, Duration intervalMs = 50 ) -> decltype(getter()) { typedef decltype(getter()) Value; const auto& adapter = detail::SelectMakeMatcherAdapter(matcher, typename std::is_same(matcher))>::type()); return detail::Wait([&getter, &adapter](std::string* description) -> std::unique_ptr { auto value_ptr = detail::TryToCallGetter(getter, description); if (value_ptr && !adapter.Apply(*value_ptr)) { if (description) *description = adapter.DescribeMismatch(*value_ptr); value_ptr.reset(); } return value_ptr; }, timeoutMs, intervalMs); } } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/webdriver.h ================================================ #ifndef WEBDRIVERXX_WEBDRIVER_H #define WEBDRIVERXX_WEBDRIVER_H #include "client.h" #include "session.h" namespace webdriverxx { // The main class for interactions with a server. Automatically connects to a server, // creates and deletes a session and gives access to session's API. class WebDriver // copyable : public Client , public Session { public: explicit WebDriver( const Capabilities& desired = Capabilities(), const Capabilities& required = Capabilities(), const std::string& url = kDefaultWebDriverUrl ) : Client(url) , Session(CreateSession(desired, required)) {} }; inline WebDriver Start( const Capabilities& desired, const Capabilities& required = Capabilities(), const std::string& url = kDefaultWebDriverUrl ) { return WebDriver(desired, required, url); } inline WebDriver Start(const Capabilities& desired, const std::string& url) { return Start(desired, Capabilities(), url); } } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx/window.h ================================================ #ifndef WEBDRIVERXX_WINDOW_H #define WEBDRIVERXX_WINDOW_H #include "types.h" #include "conversions.h" #include "detail/resource.h" #include namespace webdriverxx { class Window { // copyable public: Window(const std::string& handle, const detail::Shared& resource) : handle_(handle) , resource_(resource) {} std::string GetHandle() const { return handle_; } Size GetSize() const { return resource_->GetValue("size"); } const Window& SetSize(const Size& size) const { resource_->PostValue("size", size); return *this; } Point GetPosition() const { return resource_->GetValue("position"); } const Window& SetPosition(const Point& position) const { resource_->PostValue("position", position); return *this; } const Window& Maximize() const { resource_->Post("maximize"); return *this; } private: std::string handle_; detail::Shared resource_; }; } // namespace webdriverxx #endif ================================================ FILE: include/webdriverxx.h ================================================ #ifndef WEBDRIVERXX_H #define WEBDRIVERXX_H #include "webdriverxx/webdriver.h" #include "webdriverxx/browsers/chrome.h" #include "webdriverxx/browsers/firefox.h" #include "webdriverxx/browsers/ie.h" #include "webdriverxx/browsers/phantom.h" #include "webdriverxx/wait.h" #include "webdriverxx/wait_match.h" #endif ================================================ FILE: test/CMakeLists.txt ================================================ set(HEADER_FILES ../include/webdriverxx.h ../include/webdriverxx/by.h ../include/webdriverxx/capabilities.h ../include/webdriverxx/client.h ../include/webdriverxx/client.inl ../include/webdriverxx/conversions.h ../include/webdriverxx/element.h ../include/webdriverxx/element.inl ../include/webdriverxx/errors.h ../include/webdriverxx/js_args.h ../include/webdriverxx/keys.h ../include/webdriverxx/response_status_code.h ../include/webdriverxx/session.h ../include/webdriverxx/session.inl ../include/webdriverxx/types.h ../include/webdriverxx/wait.h ../include/webdriverxx/wait_match.h ../include/webdriverxx/webdriver.h ../include/webdriverxx/window.h ../include/webdriverxx/picojson.h ../include/webdriverxx/browsers/chrome.h ../include/webdriverxx/browsers/firefox.h ../include/webdriverxx/browsers/ie.h ../include/webdriverxx/browsers/phantom.h ../include/webdriverxx/detail/error_handling.h ../include/webdriverxx/detail/factories.h ../include/webdriverxx/detail/factories_impl.h ../include/webdriverxx/detail/finder.h ../include/webdriverxx/detail/finder.inl ../include/webdriverxx/detail/http_client.h ../include/webdriverxx/detail/http_connection.h ../include/webdriverxx/detail/http_request.h ../include/webdriverxx/detail/keyboard.h ../include/webdriverxx/detail/meta_tools.h ../include/webdriverxx/detail/resource.h ../include/webdriverxx/detail/shared.h ../include/webdriverxx/detail/time.h ../include/webdriverxx/detail/to_string.h ../include/webdriverxx/detail/types.h ) set(SOURCE_FILES alerts_test.cpp browsers_test.cpp capabilities_test.cpp conversions_test.cpp client_test.cpp element_test.cpp environment.h examples_test.cpp finder_test.cpp frames_test.cpp http_connection_test.cpp js_test.cpp keyboard_test.cpp main.cpp mouse_test.cpp resource_test.cpp session_test.cpp shared_test.cpp to_string_test.cpp wait_match_test.cpp wait_test.cpp webdriver_test.cpp ) file(COPY pages DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) add_definitions(-DWEBDRIVERXX_ENABLE_GMOCK_MATCHERS) if (MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX") elseif ( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") if (NOT ${APPLE}) set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-fsanitize=address -g -O1 -fno-omit-frame-pointer") endif() endif() # Coverage if (UNIX) set(CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES} Coverage) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "Coverage") set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage") set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_DEBUG} --coverage") find_program(LCOV_EXECUTABLE lcov) find_program(GENHTML_EXECUTABLE genhtml) add_custom_target(coverage ${LCOV_EXECUTABLE} --directory=. --zerocounters COMMAND ${CMAKE_CTEST_COMMAND} -V COMMAND ${LCOV_EXECUTABLE} --capture --directory=. --output-file coverage.info COMMAND ${GENHTML_EXECUTABLE} coverage.info --output-directory coverage DEPENDS ${PROJECT_NAME} ) endif() include(ExternalProject) # CURL if (WIN32) externalproject_add(curl_project URL http://curl.haxx.se/download/curl-7.55.0.tar.gz PREFIX curl 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 INSTALL_COMMAND "" UPDATE_COMMAND "" ) externalproject_get_property(curl_project source_dir binary_dir) include_directories("${source_dir}/include") link_directories("${binary_dir}/lib") list(APPEND LIBS libcurl wsock32 ws2_32) list(APPEND DEPS curl_project) add_definitions(-DCURL_STATICLIB) else() find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIR}) list(APPEND LIBS ${CURL_LIBRARIES}) endif() # Google test externalproject_add(googletest PREFIX googletest GIT_REPOSITORY https://github.com/google/googletest UPDATE_COMMAND "" ) externalproject_get_property(googletest source_dir) include_directories("${source_dir}") list(APPEND DEPS googletest) list(APPEND LIBS gtest gmock) # pthread if (UNIX) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) list(APPEND LIBS ${CMAKE_THREAD_LIBS_INIT}) endif() add_library(${PROJECT_NAME} ${SOURCE_FILES} ${HEADER_FILES}) add_dependencies(${PROJECT_NAME} ${DEPS}) target_link_libraries(${PROJECT_NAME} ${LIBS}) add_executable(${PROJECT_NAME}_test ${SOURCE_FILES} ${HEADER_FILES}) add_dependencies(${PROJECT_NAME}_test ${DEPS}) target_link_libraries(${PROJECT_NAME}_test ${LIBS}) add_test(${PROJECT_NAME}_test ${PROJECT_NAME}) if (APPLE) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} SUFFIX ".dylib") endif() if (UNIX AND NOT APPLE) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} SUFFIX ".so") endif() if (WIN32) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} SUFFIX ".dll") endif() install(TARGETS ${PROJECT_NAME} DESTINATION /usr/local/lib) ================================================ FILE: test/alerts_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; class TestAlerts : public ::testing::Test { protected: static void SetUpTestCase() { GetDriver().Navigate(GetTestPageUrl("alerts.html")); } TestAlerts() : driver(GetDriver()) {} WebDriver driver; }; TEST_F(TestAlerts, AcceptsAlert) { if (IsPhantom()) return; driver.Execute("alert('abc')"); driver.AcceptAlert(); } TEST_F(TestAlerts, DismissesAlert) { if (IsPhantom()) return; driver.Execute("alert('abc')"); driver.DismissAlert(); } TEST_F(TestAlerts, GetsAlertText) { if (IsPhantom()) return; driver.Execute("alert('abc')"); ASSERT_EQ("abc", driver.GetAlertText()); driver.DismissAlert(); } TEST_F(TestAlerts, SendsKeysToAlert) { if (IsPhantom()) return; driver.Execute("result = prompt('abc')"); driver.SendKeysToAlert("def"); driver.AcceptAlert(); ASSERT_EQ("def", driver.Eval("return result")); } TEST_F(TestAlerts, DismissesSendedKeys) { if (IsPhantom()) return; driver.Execute("result = prompt('abc')"); driver.SendKeysToAlert("def"); driver.DismissAlert(); ASSERT_FALSE(driver.Eval("return result")); } } // namespace test ================================================ FILE: test/browsers_test.cpp ================================================ #include "environment.h" #include #include #include #include #include namespace test { using namespace webdriverxx; TEST(Firefox, WithTheSimplestSyntax) { if (!TestRealBrowsers()) return; auto ff = Start(Firefox()); } TEST(Firefox, WithCustomUrl) { if (!TestRealBrowsers()) return; auto ff = Start(Firefox(), kDefaultWebDriverUrl); } TEST(Firefox, WithDefaultCapabilities) { if (!TestRealBrowsers()) return; auto defaults = Capabilities().SetProxy(DirectConnection()); auto ff = Start(Firefox(defaults)); } TEST(Firefox, HasCapabilitiesProperties) { if (!TestRealBrowsers()) return; auto ff = Start(Firefox().SetProxy(DirectConnection())); } TEST(Firefox, ConvertsToJson) { auto ff = Firefox() .SetLoggingPrefs(LoggingPrefs().SetLevel(log_level::Warning)) .SetFirefoxBinary("abc"); const auto json = ToJson(ff); const auto c = FromJson(json); const auto logging = c.Get("loggingPrefs"); ASSERT_EQ(browser::Firefox, c.GetBrowserName()); ASSERT_EQ("WARNING", logging.Get("driver")); ASSERT_EQ("abc", c.Get("firefox_binary")); } TEST(InternetExplorer, ConvertsToJson) { auto ie = InternetExplorer(); const auto json = ToJson(ie); const auto c = FromJson(json); ASSERT_EQ(browser::InternetExplorer, c.GetBrowserName()); } TEST(Chrome, ConvertsToJson) { auto gc = Chrome(); const auto json = ToJson(gc); const auto c = FromJson(json); ASSERT_EQ(browser::Chrome, c.GetBrowserName()); } } // namespace test ================================================ FILE: test/capabilities_test.cpp ================================================ #include #include namespace test { using namespace webdriverxx; TEST(Capabilities, AllowsToSetAndGetCustomValues) { Capabilities c; c.Set("int", 123); c.Set("double", 456.7); c.Set("string", "abc"); c.Set("bool", true); ASSERT_EQ(123, c.Get("int")); ASSERT_EQ(456.7, c.Get("double")); ASSERT_EQ("abc", c.Get("string")); ASSERT_EQ(true, c.Get("bool")); } TEST(Capabilities, ConvertibleToJson) { Capabilities c; c.Set("int", 123); c.Set("string", "abc"); const auto json = ToJson(c); const auto c_copy = FromJson(json); ASSERT_EQ(123, c_copy.Get("int")); ASSERT_EQ("abc", c_copy.Get("string")); } TEST(Capabilities, AllowsToSetProxy) { Capabilities().SetProxy(DirectConnection()); Capabilities().SetProxy(AutodetectProxy()); Capabilities().SetProxy(SystemProxy()); Capabilities().SetProxy(FtpProxy("127.0.0.1:3128").SetNoProxyFor("custom.host")); Capabilities().SetProxy(HttpProxy("127.0.0.1:3128").SetNoProxyFor("custom.host")); Capabilities().SetProxy(SslProxy("127.0.0.1:3128").SetNoProxyFor("custom.host")); Capabilities().SetProxy(SocksProxy("127.0.0.1:3128") .SetUsername("user").SetPassword("12345").SetNoProxyFor("custom.host") ); Capabilities().SetProxy(AutomaticProxyFromUrl("http://some.url")); } TEST(Capabilities, ConvertsProxyToJson) { Capabilities c; c.SetProxy(SocksProxy("127.0.0.1:3128").SetUsername("user") .SetPassword("12345").SetNoProxyFor("custom.host")); const auto json = ToJson(c); const auto c_copy = FromJson(json); const auto proxy = FromJson(c_copy.Get("proxy")); ASSERT_EQ("manual", proxy.Get("proxyType")); ASSERT_EQ("127.0.0.1:3128", proxy.Get("socksProxy")); ASSERT_EQ("user", proxy.Get("socksUsername")); ASSERT_EQ("12345", proxy.Get("socksPassword")); ASSERT_EQ("custom.host", proxy.Get("noProxy")); } } // namespace test ================================================ FILE: test/client_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; class TestClient : public ::testing::Test { protected: static void SetUpTestCase() { client = new Client(GetWebDriverUrl()); } static void TearDownTestCase() { delete client; client = 0; } static Client* client; }; Client* TestClient::client = 0; TEST_F(TestClient, GetsStatus) { picojson::object status = client->GetStatus(); ASSERT_TRUE(status["build"].is()); ASSERT_TRUE(status["os"].is()); } TEST_F(TestClient, GetsSessions) { client->GetSessions(); } TEST_F(TestClient, CreatesSession) { Parameters params = GetParameters(); client->CreateSession(params.desired, params.required); } } // namespace test ================================================ FILE: test/conversions_test.cpp ================================================ #include #include #include #include namespace test { using namespace webdriverxx; TEST(ToJson, ConvertsIntegralTypes) { ASSERT_EQ(123, ToJson(123).get()); int i = 123; ASSERT_EQ(123, ToJson(i).get()); ASSERT_EQ(123, ToJson(static_cast(123)).get()); ASSERT_EQ(123.5, ToJson(123.5).get()); ASSERT_TRUE(ToJson(true).get()); ASSERT_FALSE(ToJson(false).get()); } TEST(ToJson, ConvertsStrings) { ASSERT_EQ("abc", ToJson("abc").get()); std::string s("abc"); ASSERT_EQ("abc", ToJson(s).get()); ASSERT_EQ("abc", ToJson(s.c_str()).get()); ASSERT_EQ("abc", ToJson(const_cast(s.c_str())).get()); char a[] = "abc"; ASSERT_EQ("abc", ToJson(a).get()); } TEST(ToJson, ConvertsIterables) { int i[] = { 123, 456, 789 }; const auto ji = ToJson(i); ASSERT_TRUE(ji.is()); ASSERT_EQ(3u, ji.get().size()); ASSERT_EQ(789, ji.get()[2].get()); const char* s[] = { "abc", "def", "ghi" }; const auto js = ToJson(s); ASSERT_TRUE(js.is()); ASSERT_EQ(3u, js.get().size()); ASSERT_EQ("ghi", js.get()[2].get()); std::vector v(std::begin(i), std::end(i)); const auto jv = ToJson(v); ASSERT_TRUE(jv.is()); ASSERT_EQ(3u, jv.get().size()); ASSERT_EQ(789, jv.get()[2].get()); std::list l(std::begin(i), std::end(i)); const auto jl = ToJson(l); ASSERT_TRUE(jl.is()); ASSERT_EQ(3u, jl.get().size()); ASSERT_EQ(789, jl.get()[2].get()); int ii[2][3] = { { 1, 2, 3 }, { 4, 5, 123 } }; const auto jii = ToJson(ii); ASSERT_TRUE(jii.is()); ASSERT_EQ(2u, jii.get().size()); ASSERT_TRUE(jii.get()[0].is()); ASSERT_TRUE(jii.get()[1].is()); ASSERT_EQ(3u, jii.get()[0].get().size()); ASSERT_EQ(3u, jii.get()[1].get().size()); ASSERT_EQ(123, jii.get()[1].get()[2].get()); std::vector e; const auto je = ToJson(e); ASSERT_TRUE(je.is()); ASSERT_EQ(0u, je.get().size()); } namespace { namespace custom { struct Object { std::string string; int number; }; picojson::value CustomToJson(const Object& value) { return JsonObject() .Set("string", value.string) .Set("number", value.number); } void CustomFromJson(const picojson::value& value, Object& result) { WEBDRIVERXX_CHECK(value.is(), "custom::Object is not an object"); result.string = FromJson(value.get("string")); result.number = FromJson(value.get("number")); } } // namespace custom } // namespace TEST(ToJson, ConvertsCustomObjects) { custom::Object o = { "abc", 123 }; const auto jo = ToJson(o); ASSERT_TRUE(jo.is()); ASSERT_EQ(123, jo.get("number").get()); ASSERT_EQ("abc", jo.get("string").get()); const custom::Object os[] = { { "abc", 123 }, { "def", 456 } }; const auto jos = ToJson(os); ASSERT_TRUE(jos.is()); ASSERT_EQ(2u, jos.get().size()); ASSERT_TRUE(jos.get()[0].is()); ASSERT_EQ(456, jos.get()[1].get("number").get()); } picojson::value J(const std::string& json) { picojson::value result; picojson::parse(result, json.begin(), json.end(), nullptr); return result; } TEST(FromJson, ConvertsIntegralTypes) { ASSERT_EQ(123, FromJson(J("123"))); ASSERT_EQ(123.5, FromJson(J("123.5"))); ASSERT_EQ(123u, FromJson(J("123"))); ASSERT_FALSE(FromJson(J("false"))); ASSERT_TRUE(FromJson(J("true"))); } TEST(FromJson, ConvertsStrings) { ASSERT_EQ("abc", FromJson(J("\"abc\""))); } TEST(FromJson, ConvertsIterables) { const auto i = FromJson>(J("[ 123, 456, 789 ]")); ASSERT_EQ(3u, i.size()); ASSERT_EQ(123, i[0]); ASSERT_EQ(789, i[2]); const auto s = FromJson>(J("[ \"abc\", \"def\" ]")); ASSERT_EQ(2u, s.size()); ASSERT_EQ("abc", s.front()); ASSERT_EQ("def", s.back()); const auto e = FromJson>(J("[]")); ASSERT_EQ(0u, e.size()); const auto ii = FromJson>>(J("[ [ 123, 456 ], [ 789 ] ]")); ASSERT_EQ(2u, ii.size()); ASSERT_EQ(2u, ii[0].size()); ASSERT_EQ(1u, ii[1].size()); ASSERT_EQ(456, ii[0].back()); ASSERT_EQ(789, ii[1].front()); } TEST(FromJson, ConvertsCustomObjects) { const auto o = FromJson(J("{ \"number\": 123, \"string\": \"abc\" }")); ASSERT_EQ(123, o.number); ASSERT_EQ("abc", o.string); const auto os = FromJson>(J( "[ { \"number\": 123, \"string\": \"abc\" }," "{ \"number\": 456, \"string\": \"def\" } ]" )); ASSERT_EQ(2u, os.size()); ASSERT_EQ(123, os[0].number); ASSERT_EQ("abc", os[0].string); ASSERT_EQ(456, os[1].number); ASSERT_EQ("def", os[1].string); } } // namespace test ================================================ FILE: test/element_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; class TestElement : public ::testing::Test { protected: static void SetUpTestCase() { GetDriver().Navigate(GetTestPageUrl("element.html")); } TestElement() : driver(GetDriver()) {} WebDriver driver; }; TEST_F(TestElement, CanBeClicked) { driver.FindElement(ByTag("input")).Click(); } // TODO: Submit TEST_F(TestElement, GetsText) { ASSERT_EQ("Some text", driver.FindElement(ById("element_with_text")).GetText()); } TEST_F(TestElement, CanBeCleared) { Element e = driver.FindElement(ByTag("input")); e.SendKeys("abc"); ASSERT_NE("", e.GetAttribute("value")); e.Clear(); ASSERT_EQ("", e.GetAttribute("value")); } TEST_F(TestElement, GetsTagName) { ASSERT_EQ("div", driver.FindElement(ByTag("div")).GetTagName()); } // TODO: IsEnabled // TODO: IsSelected TEST_F(TestElement, GetsAttributes) { Element e = driver.FindElement(ById("div_with_attributes")); ASSERT_EQ("div_with_attributes", e.GetAttribute("id")); ASSERT_EQ("test value", e.GetAttribute("test")); } TEST_F(TestElement, IsEqualToOtherElement) { Element e = driver.FindElement(ById("first_div")); Element other = driver.FindElement(ById("first_div")); ASSERT_TRUE(e.Equals(other)); ASSERT_TRUE(e == other); } TEST_F(TestElement, IsNotEqualToOtherElement) { Element e = driver.FindElement(ById("first_div")); Element other = driver.FindElement(ById("second_div")); ASSERT_TRUE(!e.Equals(other)); ASSERT_TRUE(e != other); } TEST_F(TestElement, HasStrictWeakOrdering) { Element a = driver.FindElement(ById("first_div")); Element b = driver.FindElement(ById("second_div")); Element c = driver.FindElement(ById("third_div")); Element a2 = a; if (c < b) std::swap(b, c); if (b < a) std::swap(a, b); if (c < b) std::swap(b, c); ASSERT_FALSE(a < a2); ASSERT_FALSE(a2 < a); ASSERT_TRUE(a < b && !(b < a)); ASSERT_TRUE(a < b && b < c && a < c); } TEST_F(TestElement, GetsIsDisplayed) { ASSERT_TRUE(driver.FindElement(ById("visible")).IsDisplayed()); ASSERT_FALSE(driver.FindElement(ById("hidden")).IsDisplayed()); } TEST_F(TestElement, GetsLocation) { driver.FindElement(ById("visible")).GetLocation(); driver.FindElement(ById("visible")).GetLocationInView(); } TEST_F(TestElement, GetsSize) { Size size = driver.FindElement(ById("visible")).GetSize(); ASSERT_NE(0, size.width); ASSERT_NE(0, size.height); } TEST_F(TestElement, GetsCssProperty) { ASSERT_EQ("none", driver.FindElement(ById("hidden")).GetCssProperty("display")); } } // namespace test ================================================ FILE: test/environment.h ================================================ #ifndef WEBDRIVERXX_ENVIRONMENT_H #define WEBDRIVERXX_ENVIRONMENT_H #include #include #include #include #include namespace test { const char* const kDefaultTestWebDriverUrl = "http://localhost:7777/"; const char* const kDefaultTestPagesUrl = "http://localhost:8080/"; struct Parameters { std::string web_driver_url; webdriverxx::Capabilities required; webdriverxx::Capabilities desired; std::string test_pages_url; bool test_real_browsers; Parameters() : web_driver_url(kDefaultTestWebDriverUrl) , test_pages_url(kDefaultTestPagesUrl) , test_real_browsers(false) {} }; class Environment : public ::testing::Environment { public: static Environment& Instance() { return *instance_; } explicit Environment(const Parameters& parameters) : driver_(0) , parameters_(parameters) {} webdriverxx::WebDriver& GetDriver() { return driver_ ? *driver_ : GetFreshDriver(); } webdriverxx::WebDriver& GetFreshDriver() { DeleteDriver(); driver_ = new webdriverxx::WebDriver(CreateDriver()); return *driver_; } webdriverxx::WebDriver CreateDriver() { return webdriverxx::WebDriver( parameters_.desired, parameters_.required, parameters_.web_driver_url ); } std::string GetWebDriverUrl() const { return parameters_.web_driver_url; } Parameters GetParameters() const { return parameters_; } std::string GetTestPageUrl(const std::string& page_name) const { std::string url = parameters_.test_pages_url; if (!url.empty() && url[url.length() - 1] != '/') url += "/"; url += page_name; return url; } private: void SetUp() { instance_ = this; } void TearDown() { instance_ = 0; DeleteDriver(); } void DeleteDriver() { delete driver_; driver_ = 0; } private: static Environment* instance_; webdriverxx::WebDriver* driver_; Parameters parameters_; }; inline Parameters GetParameters() { return Environment::Instance().GetParameters(); } inline std::string GetWebDriverUrl() { return Environment::Instance().GetWebDriverUrl(); } inline std::string GetTestPageUrl(const std::string& page_name) { return Environment::Instance().GetTestPageUrl(page_name); } inline webdriverxx::WebDriver& GetDriver() { return Environment::Instance().GetDriver(); } inline webdriverxx::WebDriver& GetFreshDriver() { return Environment::Instance().GetFreshDriver(); } inline webdriverxx::WebDriver CreateDriver() { return Environment::Instance().CreateDriver(); } inline bool TestRealBrowsers() { return GetParameters().test_real_browsers; } inline std::string GetBrowserName() { return GetDriver().GetCapabilities().GetBrowserName(); } inline bool IsFirefox() { return GetBrowserName() == webdriverxx::browser::Firefox; } inline bool IsPhantom() { return GetBrowserName() == webdriverxx::browser::Phantom; } } // namespace test #endif ================================================ FILE: test/examples_test.cpp ================================================ #include "environment.h" #include #ifndef WEBDRIVERXX_ENABLE_GMOCK_MATCHERS #define WEBDRIVERXX_ENABLE_GMOCK_MATCHERS #endif #include #include namespace test { using namespace webdriverxx; class TestExamples : public ::testing::Test { protected: TestExamples() : driver(GetDriver()) {} void StopNavigation() { WaitForMatch([this] { return driver.Navigate(GetTestPageUrl("non_existing.html")).GetUrl(); }, ::testing::HasSubstr("non_existing")); } WebDriver driver; }; TEST_F(TestExamples, QuickExample) { driver .Navigate("http://google.com") .FindElement(ByCss("input[name=q]")) .SendKeys("Hello, world!") .Submit(); StopNavigation(); // Firefox doesn't perform navigation right after Submit. } TEST_F(TestExamples, ImplicitWait) { driver.SetImplicitTimeoutMs(0); try { Element element = driver.FindElement(ByName("akela")); FAIL(); } catch (const std::exception&) {} } TEST_F(TestExamples, ExplicitWait1) { auto find_element = [&]{ return driver.FindElement(ById("async_loaded")); }; try { int timeout = 0; Element element = WaitForValue(find_element, timeout); FAIL(); } catch (const std::exception&) {} } TEST_F(TestExamples, ExplicitWait2) { auto element_is_selected = [&]{ return driver.FindElement(ById("async_loaded")).IsSelected(); }; try { int timeout = 0; WaitUntil(element_is_selected, timeout); FAIL(); } catch (const std::exception&) {} } TEST_F(TestExamples, UseGmockMatchers) { driver.Navigate(GetTestPageUrl("redirect.html")); auto url = [&]{ return driver.GetUrl(); }; using namespace ::testing; WaitForMatch(url, HasSubstr("target")); StopNavigation(); } } // namespace test ================================================ FILE: test/finder_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; class TestFinder : public ::testing::Test { protected: static void SetUpTestCase() { WebDriver& driver = GetDriver(); driver.Navigate(GetTestPageUrl("finder.html")); driver.FindElement(ById("finder_loaded")); driver.SetImplicitTimeoutMs(0); } static void TearDownTestCase() { WebDriver& driver = GetDriver(); driver.SetImplicitTimeoutMs(1000); } TestFinder() : driver(GetDriver()) {} WebDriver driver; }; TEST_F(TestFinder, CanFindElement) { driver.FindElement(ById("test_id")); } TEST_F(TestFinder, ThrowsIfElementNotFound) { ASSERT_THROW(driver.FindElement(ById("non_existing")), WebDriverException); } TEST_F(TestFinder, CanFindMoreThanOneElement) { ASSERT_TRUE(0u < driver.FindElements(ByTag("div")).size()); } TEST_F(TestFinder, ReturnsZeroIfElementsNotFound) { ASSERT_EQ(0u, driver.FindElements(ById("non_existing")).size()); } TEST_F(TestFinder, FindsElementById) { driver.FindElement(ById("test_id")); ASSERT_EQ(0u, driver.FindElements(ById("non_existing")).size()); } TEST_F(TestFinder, FindsElementByClassName) { driver.FindElement(ByClass("test_class")); ASSERT_EQ(0u, driver.FindElements(ByClass("non_existing")).size()); } TEST_F(TestFinder, FindsElementByCssSelector) { driver.FindElement(ByCss("body div#css_selectable")); ASSERT_EQ(0u, driver.FindElements(ByCss("non_existing")).size()); } TEST_F(TestFinder, FindsElementByName) { driver.FindElement(ByName("test_name")); ASSERT_EQ(0u, driver.FindElements(ByName("non_existing")).size()); } TEST_F(TestFinder, FindsElementByLinkText) { driver.FindElement(ByLinkText("test link text")); ASSERT_EQ(0u, driver.FindElements(ByLinkText("non_existing")).size()); } TEST_F(TestFinder, FindsElementByPartialLinkText) { driver.FindElement(ByPartialLinkText("link text")); ASSERT_EQ(0u, driver.FindElements(ByPartialLinkText("non_existing")).size()); } TEST_F(TestFinder, FindsElementByTagName) { driver.FindElement(ByTag("body")); ASSERT_EQ(0u, driver.FindElements(ByTag("non_existing")).size()); } TEST_F(TestFinder, FindsElementByXPath) { driver.FindElement(ByXPath("//div")); ASSERT_EQ(0u, driver.FindElements(ByXPath("//non_existing")).size()); } TEST_F(TestFinder, OfElementFindsInnerElement) { Element outer = driver.FindElement(ById("outer")); outer.FindElement(ById("inner")); } TEST_F(TestFinder, OfElementDoesNotFindItself) { Element outer = driver.FindElement(ById("outer")); ASSERT_EQ(0u, outer.FindElements(ById("outer")).size()); } TEST_F(TestFinder, OfElementDoesNotFindNonExistingInnerElements) { Element outer = driver.FindElement(ById("outer")); driver.FindElement(ById("next_after_outer")); ASSERT_THROW(outer.FindElement(ById("next_after_outer")), WebDriverException); ASSERT_EQ(0u, outer.FindElements(ById("next_after_outer")).size()); } TEST_F(TestFinder, OfElementFindsMoreThanOneInnerElement) { ASSERT_EQ(2u, driver.FindElement(ById("outer")).FindElements(ByTag("div")).size()); } } // namespace test ================================================ FILE: test/frames_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; class TestFrames : public ::testing::Test { protected: TestFrames() : driver(GetDriver()) , url(GetTestPageUrl("frames.html")) {} void SetUp() { driver.Navigate(url); } WebDriver driver; std::string url; }; TEST_F(TestFrames, OnTopFrameByDefault) { ASSERT_EQ("top_frame", driver.FindElement(ById("tag")).GetAttribute("value")); } TEST_F(TestFrames, CanSwitchToFrameByNumber) { driver.SetFocusToFrame(1); ASSERT_EQ("frame3", driver.FindElement(ById("tag")).GetAttribute("value")); } TEST_F(TestFrames, CanSwitchToFrameByName) { driver.SetFocusToFrame("frame3_name"); ASSERT_EQ("frame3", driver.FindElement(ById("tag")).GetAttribute("value")); } TEST_F(TestFrames, CanSwitchToFrameByElement) { std::vector frames = driver.FindElements(ByTag("iframe")); ASSERT_EQ(2u, frames.size()); driver.SetFocusToFrame(frames[1]); ASSERT_EQ("frame3", driver.FindElement(ById("tag")).GetAttribute("value")); } TEST_F(TestFrames, CanSwitchToDefaultFrame) { driver.SetFocusToFrame(1); driver.SetFocusToDefaultFrame(); ASSERT_EQ("top_frame", driver.FindElement(ById("tag")).GetAttribute("value")); } TEST_F(TestFrames, CanSwitchToDeepFrames) { driver.SetFocusToFrame(0).SetFocusToFrame(1); ASSERT_EQ("frame2", driver.FindElement(ById("tag")).GetAttribute("value")); } TEST_F(TestFrames, CanSwitchToParentFrame) { if (IsPhantom()) return; // Not supported in PhantomJS 1.9.7 driver.SetFocusToFrame(0).SetFocusToFrame(1) .SetFocusToParentFrame().SetFocusToParentFrame(); ASSERT_EQ("top_frame", driver.FindElement(ById("tag")).GetAttribute("value")); } } // namespace test ================================================ FILE: test/http_connection_test.cpp ================================================ #include "environment.h" #include #include #include namespace test { using namespace webdriverxx; using namespace webdriverxx::detail; TEST(HttpConnection, CanBeCreated) { HttpConnection connection; } TEST(HttpConnection, GetsPage) { HttpConnection connection; HttpResponse response = connection.Get(GetWebDriverUrl() + "status"); ASSERT_EQ(200, response.http_code); ASSERT_TRUE(!response.body.empty()); } TEST(HttpConnection, ThrowsExceptionIfPortIsClosed) { HttpConnection connection; const char *const kUrlWithClosedPort = "http://127.0.0.1:7778/"; ASSERT_THROW(connection.Get(kUrlWithClosedPort), WebDriverException); } } // namespace test ================================================ FILE: test/js_test.cpp ================================================ #include "environment.h" #include #include #include namespace test { using namespace webdriverxx; class TestJsExecutor : public ::testing::Test { protected: static void SetUpTestCase() { GetDriver().Navigate(GetTestPageUrl("js.html")); } TestJsExecutor() : driver(GetDriver()) {} WebDriver driver; }; TEST_F(TestJsExecutor, ExecutesSimpleScript) { driver.Execute("document.title = 'abc'"); ASSERT_EQ("abc", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassStringArgument) { driver.Execute("document.title = arguments[0]", JsArgs() << std::string("abc")); ASSERT_EQ("abc", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassStringLiteralArgument) { driver.Execute("document.title = arguments[0]", JsArgs() << "abc"); ASSERT_EQ("abc", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassNumberArgument) { driver.Execute("document.title = String(arguments[0] + 21)", JsArgs() << 21); ASSERT_EQ("42", driver.GetTitle()); driver.Execute("document.title = String(arguments[0] + 21)", JsArgs() << 21.5); ASSERT_EQ("42.5", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassBooleanArgument) { driver.Execute("a = arguments; document.title = [ typeof(a[0]), a[0], typeof(a[1]), a[1] ].join(',')", JsArgs() << true << false); ASSERT_EQ("boolean,true,boolean,false", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassMoreThanOneArgument) { driver.Execute("document.title = arguments[0] + ',' + arguments[1]", JsArgs() << "abc" << "def"); ASSERT_EQ("abc,def", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassElement) { Element e = driver.FindElement(ByTag("input")).Clear(); ASSERT_EQ("", e.GetAttribute("value")); driver.Execute("arguments[0].value = arguments[1]", JsArgs() << e << "abc"); ASSERT_EQ("abc", e.GetAttribute("value")); } TEST_F(TestJsExecutor, CanPassArray) { std::vector numbers; numbers.push_back(123); numbers.push_back(321); driver.Execute("document.title = arguments[0][0] + arguments[0][1]", JsArgs() << numbers); ASSERT_EQ("444", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassOtherContainers) { std::list numbers; numbers.push_back(123); numbers.push_back(321); driver.Execute("document.title = arguments[0][0] + arguments[0][1]", JsArgs() << numbers); ASSERT_EQ("444", driver.GetTitle()); } TEST_F(TestJsExecutor, CanPassCArray) { const char* colors[] = { "red", "green", "blue" }; driver.Execute("document.title = arguments[0].reverse().join(', ')", JsArgs() << colors); ASSERT_EQ("blue, green, red", driver.GetTitle()); } namespace { namespace custom { struct Object { std::string string; int number; }; picojson::value CustomToJson(const Object& value) { return JsonObject() .Set("string", value.string) .Set("number", value.number); } void CustomFromJson(const picojson::value& value, Object& result) { WEBDRIVERXX_CHECK(value.is(), "custom::Object is not an object"); result.string = FromJson(value.get("string")); result.number = FromJson(value.get("number")); } } // namespace custom } // namespace TEST_F(TestJsExecutor, CanPassCustomObject) { custom::Object o = { "abc", 123 }; driver.Execute("o = arguments[0]; document.title = (o.string + 'def') + (o.number + 1)", JsArgs() << o); ASSERT_EQ("abcdef124", driver.GetTitle()); } /////////////////////////////////////////////////////////////////////////// TEST_F(TestJsExecutor, EvalsString) { ASSERT_EQ("abc", driver.Eval("return 'abc'")); } TEST_F(TestJsExecutor, EvalsNumber) { ASSERT_EQ(123, driver.Eval("return 123")); ASSERT_EQ(123.5, driver.Eval("return 123.5")); } TEST_F(TestJsExecutor, EvalsBoolean) { ASSERT_TRUE(true == driver.Eval("return true")); ASSERT_TRUE(false == driver.Eval("return false")); } TEST_F(TestJsExecutor, EvalsElement) { Element e = driver.FindElement(ByTag("input")); ASSERT_EQ(e, driver.Eval("return document.getElementsByTagName('input')[0]")); } TEST_F(TestJsExecutor, EvalsCustomObject) { custom::Object o = driver.Eval("return { string: 'abc', number: 123 }"); ASSERT_EQ("abc", o.string); ASSERT_EQ(123, o.number); } TEST_F(TestJsExecutor, EvalsArrayOfStrings) { std::vector v = driver.Eval>( "return [ 'abc', 'def' ]" ); ASSERT_EQ(2u, v.size()); ASSERT_EQ("abc", v[0]); ASSERT_EQ("def", v[1]); } TEST_F(TestJsExecutor, EvalsArrayOfNumbers) { std::vector v = driver.Eval>( "return [ 123, 456 ]" ); ASSERT_EQ(2u, v.size()); ASSERT_EQ(123, v[0]); ASSERT_EQ(456, v[1]); } /////////////////////////////////////////////////////////////////////////// // Makes asynchronous script from synchronous one std::string AsyncScript(const std::string& script) { return std::string() + "var args = Array.prototype.slice.call(arguments, 0);" + "var callback = args.pop();" + "setTimeout(function(){" + "var result = (function(){" + script + "}).apply(this, args);" + "callback(result)" + "}, 0);" ; } TEST_F(TestJsExecutor, ExecutesSimpleAsyncScript) { if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 driver.ExecuteAsync(AsyncScript("document.title = 'abc'")); ASSERT_EQ("abc", driver.GetTitle()); } TEST_F(TestJsExecutor, PassesArgumentsToAsyncScript) { if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 driver.ExecuteAsync(AsyncScript("document.title = JSON.stringify(Array.prototype.slice.call(arguments, 0))"), JsArgs() << std::string("abc") << 123 << true); ASSERT_EQ("[\"abc\",123,true]", driver.GetTitle()); } TEST_F(TestJsExecutor, ReturnsValueFromAsyncScript) { if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 ASSERT_EQ(123, driver.EvalAsync(AsyncScript("return 123"))); } TEST_F(TestJsExecutor, ReturnsElementFromAsyncScript) { if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 Element e = driver.FindElement(ByTag("input")); ASSERT_EQ(e, driver.EvalAsync(AsyncScript( "return document.getElementsByTagName('input')[0]"))); } } // namespace test ================================================ FILE: test/keyboard_test.cpp ================================================ #include "environment.h" #include #include #include namespace test { using namespace webdriverxx; class TestKeyboard : public ::testing::Test { protected: static void SetUpTestCase() { GetDriver().Navigate(GetTestPageUrl("keyboard.html")); } TestKeyboard() : driver(GetDriver()) {} WebDriver driver; }; TEST_F(TestKeyboard, SendsKeysToElement) { Element e = driver.FindElement(ByName("first")); e.Clear() .SendKeys("abc") .SendKeys(keys::Left).SendKeys(keys::Left).SendKeys(keys::Left) .SendKeys("def") ; ASSERT_EQ("abcdef", e.GetAttribute("value")); } TEST_F(TestKeyboard, SendsShortcuts) { Element e = driver.FindElement(ByName("first")); e.Clear() .SendKeys(Shortcut() << keys::Shift << "a" << "bc") .SendKeys("def") ; ASSERT_EQ("ABCdef", e.GetAttribute("value")); } TEST_F(TestKeyboard, SendsKeysToActiveElement) { Element first = driver.FindElement(ByName("first")); Element second = driver.FindElement(ByName("second")); first.Click().Clear(); driver.SendKeys("abc"); second.Click().Clear(); driver.SendKeys("def"); ASSERT_EQ("abc", first.GetAttribute("value")); ASSERT_EQ("def", second.GetAttribute("value")); } } // namespace test ================================================ FILE: test/main.cpp ================================================ #include "environment.h" #include #include namespace test { Environment* Environment::instance_ = 0; bool IsCommandLineArgument(const std::string& arg, const char* name) { return arg.find(std::string("--") + name) == 0; } std::string GetCommandLineArgumentValue(const std::string& arg) { const size_t pos = arg.find('='); return pos == std::string::npos ? std::string() : arg.substr(pos + 1); } Parameters ParseParameters(int argc, char **argv) { Parameters result; for(int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (IsCommandLineArgument(arg, "browser")) { result.web_driver_url = webdriverxx::kDefaultWebDriverUrl; const std::string browser_name = GetCommandLineArgumentValue(arg); result.desired.Set("browserName", browser_name); } else if (IsCommandLineArgument(arg, "pages")) { result.test_pages_url = GetCommandLineArgumentValue(arg); } else if (IsCommandLineArgument(arg, "webdriver")) { result.web_driver_url = GetCommandLineArgumentValue(arg); } else if (IsCommandLineArgument(arg, "test_real_browsers")) { result.test_real_browsers = true; } } return result; } } // namespace test int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::AddGlobalTestEnvironment( new test::Environment(test::ParseParameters(argc, argv)) ); return RUN_ALL_TESTS(); } ================================================ FILE: test/mouse_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; class TestMouse : public ::testing::Test { protected: static void SetUpTestCase() { GetDriver().Navigate(GetTestPageUrl("mouse.html")); } TestMouse() : driver(GetDriver()) {} void SetUp() { target = driver.FindElement(ById("target")); click_type = driver.FindElement(ById("click_type")).Clear(); updown_type = driver.FindElement(ById("updown_type")).Clear(); updown_button = driver.FindElement(ById("updown_button")).Clear(); } std::string GetValue(const Element& element) { return element.GetAttribute("value"); } WebDriver driver; Element target; Element click_type; Element updown_type; Element updown_button; }; TEST_F(TestMouse, SendsClick) { driver.MoveToCenterOf(target).Click(); ASSERT_EQ("click", GetValue(click_type)); } TEST_F(TestMouse, MovesPointerInsideTarget) { driver.MoveToCenterOf(target).MoveTo(Offset(-45,-45)).Click(); ASSERT_EQ("click", GetValue(click_type)); } TEST_F(TestMouse, MovesPointerOutsideTarget) { if (IsFirefox()) return; driver.MoveToCenterOf(target).MoveTo(Offset(55,55)).Click(); driver.MoveToCenterOf(target).MoveTo(Offset(-55,55)).Click(); driver.MoveToCenterOf(target).MoveTo(Offset(-55,-55)).Click(); driver.MoveToCenterOf(target).MoveTo(Offset(55,-55)).Click(); ASSERT_EQ("", GetValue(click_type)); } TEST_F(TestMouse, MovesPointerToTopLeftCorner) { driver.MoveToTopLeftOf(target).Click(); ASSERT_EQ("click", GetValue(click_type)); } TEST_F(TestMouse, MovesPointerToTopLeftCornerWithOffset) { driver.MoveToTopLeftOf(target, Offset(95,95)).Click(); ASSERT_EQ("click", GetValue(click_type)); } TEST_F(TestMouse, MovesPointerToTopLeftCornerWithOffset2) { if (IsFirefox()) return; driver.MoveToTopLeftOf(target, Offset(-5,-5)).Click(); driver.MoveToTopLeftOf(target, Offset(105,105)).Click(); ASSERT_EQ("", GetValue(click_type)); } TEST_F(TestMouse, SendsDoubleclicks) { driver.MoveToCenterOf(target).DoubleClick(); ASSERT_EQ("dblclick", GetValue(click_type)); } TEST_F(TestMouse, SendsButtonDown) { driver.MoveToCenterOf(target).ButtonDown(); ASSERT_EQ("mousedown", GetValue(updown_type)); driver.ButtonUp(); } TEST_F(TestMouse, SendsButtonUp) { driver.MoveToCenterOf(target).ButtonDown().ButtonUp(); ASSERT_EQ("mouseup", GetValue(updown_type)); } TEST_F(TestMouse, SendsDifferentButtons) { if (IsFirefox()) return; driver.MoveToCenterOf(target).ButtonDown(mouse::LeftButton).ButtonUp(mouse::LeftButton); const auto left_button = GetValue(updown_button); driver.ButtonDown(mouse::RightButton).ButtonUp(mouse::RightButton); const auto right_button = GetValue(updown_button); driver.ButtonDown(mouse::MiddleButton).ButtonUp(mouse::MiddleButton); const auto middle_button = GetValue(updown_button); ASSERT_NE(left_button, right_button); ASSERT_NE(left_button, middle_button); ASSERT_NE(right_button, middle_button); } } // namespace test ================================================ FILE: test/pages/alerts.html ================================================ ================================================ FILE: test/pages/element.html ================================================
Some text
visible text
================================================ FILE: test/pages/finder.html ================================================
test link text
================================================ FILE: test/pages/frame1.html ================================================ ================================================ FILE: test/pages/frame2.html ================================================ ================================================ FILE: test/pages/frame3.html ================================================ ================================================ FILE: test/pages/frames.html ================================================ ================================================ FILE: test/pages/frameset.html ================================================ ================================================ FILE: test/pages/js.html ================================================ ================================================ FILE: test/pages/keyboard.html ================================================ ================================================ FILE: test/pages/mouse.html ================================================
================================================ FILE: test/pages/navigation1.html ================================================ Navigation 1 ================================================ FILE: test/pages/navigation2.html ================================================ Navigation 2 ================================================ FILE: test/pages/redirect.html ================================================ ================================================ FILE: test/pages/session.html ================================================ Test title ================================================ FILE: test/pages/webdriver.html ================================================ ================================================ FILE: test/resource_test.cpp ================================================ #include #include #include #include #include namespace test { using namespace webdriverxx; using namespace webdriverxx::detail; const char *const kTestUrl = "http://test/"; struct MockHttpClient : IHttpClient, SharedObjectBase { MOCK_CONST_METHOD1(Get, HttpResponse(const std::string& url)); MOCK_CONST_METHOD2(Post, HttpResponse(const std::string& url, const std::string& data)); MOCK_CONST_METHOD1(Delete, HttpResponse(const std::string& url)); }; using namespace ::testing; struct TestResource : Test { void SetUp() { http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; http_client = Shared(new MockHttpClient); DefaultValue::Set(HttpResponse()); ON_CALL(*http_client, Get(_)).WillByDefault(ReturnPointee(&http_response)); ON_CALL(*http_client, Post(_,_)).WillByDefault(ReturnPointee(&http_response)); ON_CALL(*http_client, Delete(_)).WillByDefault(ReturnPointee(&http_response)); EXPECT_CALL(*http_client, Get(_)).Times(AnyNumber()); EXPECT_CALL(*http_client, Post(_,_)).Times(AnyNumber()); EXPECT_CALL(*http_client, Delete(_)).Times(AnyNumber()); } Shared http_client; HttpResponse http_response; }; // Positive tests TEST_F(TestResource, CanBeCreated) { Resource resource(kTestUrl, http_client); } TEST_F(TestResource, ReturnsUrl) { Resource resource(kTestUrl, http_client); ASSERT_EQ(kTestUrl, resource.GetUrl()); } TEST_F(TestResource, CanBeUsedToMakeSubResource) { Shared a(new Resource("a", http_client)); ASSERT_EQ("a/c", MakeSubResource(a, "c")->GetUrl()); ASSERT_EQ("a/c",MakeSubResource(a, "/c")->GetUrl()); Shared b(new Resource("b/", http_client)); ASSERT_EQ("b/c", MakeSubResource(b, "c")->GetUrl()); ASSERT_EQ("b/c", MakeSubResource(b, "/c")->GetUrl()); } TEST_F(TestResource, DoesNotDeleteResourceByDefault) { EXPECT_CALL(*http_client, Delete(_)).Times(0); Resource resource(kTestUrl, http_client); } TEST_F(TestResource, DeletesResourceIfOwnershipIsEnabled) { EXPECT_CALL(*http_client, Delete(kTestUrl)); Resource resource(kTestUrl, http_client, Resource::IsOwner); } TEST_F(TestResource, SharesOwnershipOfParentResource) { EXPECT_CALL(*http_client, Delete(_)).Times(0); Shared parent(new Resource("parent", http_client, Resource::IsOwner)); Shared child = MakeSubResource(parent, "child", Resource::IsOwner); parent = Shared(); Mock::VerifyAndClear(http_client); // Parent shouldn't be deleted at this point because child is alive InSequence check_delete_order; EXPECT_CALL(*http_client, Delete("parent/child")); EXPECT_CALL(*http_client, Delete("parent")); child = Shared(); } TEST_F(TestResource, RootResourceReturnsJsonObject) { RootResource resource(kTestUrl, http_client); http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; ASSERT_TRUE(resource.Get("command").is()); } TEST_F(TestResource, RootResourceReturnsSessionId) { RootResource resource(kTestUrl, http_client); http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; ASSERT_TRUE(resource.Get("command").contains("sessionId")); ASSERT_EQ("123", resource.Get("command").get("sessionId").to_str()); } TEST_F(TestResource, RootResourceReturnsNullSessionId) { RootResource resource(kTestUrl, http_client); http_response.body = "{\"sessionId\":null,\"status\":0,\"value\":12345}"; ASSERT_TRUE(resource.Get("command").contains("sessionId")); ASSERT_TRUE(resource.Get("command").get("sessionId").is()); } TEST_F(TestResource, RootResourceReturnsScalarValueFromPositiveResponse) { RootResource resource(kTestUrl, http_client); http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; picojson::value value = resource.Get("command").get("value"); ASSERT_TRUE(value.is()); ASSERT_EQ(12345, value.get()); } TEST_F(TestResource, RootResourceReturnsObjectValueFromPositiveResponse) { RootResource resource(kTestUrl, http_client); http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":{\"member\":12345}}"; picojson::value value = resource.Get("command").get("value"); ASSERT_TRUE(value.is()); ASSERT_TRUE(value.contains("member")); ASSERT_TRUE(value.get("member").is()); ASSERT_EQ(12345, value.get("member").get()); } TEST_F(TestResource, ReturnsScalarValueFromPositiveResponse) { Resource resource(kTestUrl, http_client); http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; picojson::value value = resource.Get("command"); ASSERT_TRUE(value.is()); ASSERT_EQ(12345, value.get()); } TEST_F(TestResource, ReturnsObjectValueFromPositiveResponse) { Resource resource(kTestUrl, http_client); http_response.http_code = 200; http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":{\"member\":12345}}"; picojson::value value = resource.Get("command"); ASSERT_TRUE(value.is()); ASSERT_TRUE(value.contains("member")); ASSERT_TRUE(value.get("member").is()); ASSERT_EQ(12345, value.get("member").get()); } // Negative tests TEST_F(TestResource, ThrowsOnHttp404) { http_response.http_code = 404; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp400) { http_response.http_code = 400; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp499) { http_response.http_code = 499; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp501) { http_response.http_code = 501; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, DoesNotHideHttpExceptions) { EXPECT_CALL(*http_client, Get(_)).WillOnce(Throw(WebDriverException("HTTP failed"))); Resource resource(kTestUrl, http_client); try { resource.Get("command"); FAIL(); // Shouldn't get here } catch (const std::exception& e) { const std::string message = e.what(); ASSERT_NE(std::string::npos, message.find("HTTP failed")); } } TEST_F(TestResource, AddsContextToExceptions) { EXPECT_CALL(*http_client, Get(_)).WillOnce(Throw(WebDriverException("HTTP failed"))); Resource resource(kTestUrl, http_client); try { resource.Get("pinky"); FAIL(); // Shouldn't get here } catch (const std::exception& e) { const std::string message = e.what(); ASSERT_NE(std::string::npos, message.find("pinky")); } } TEST_F(TestResource, WebDriverExceptionContainsCommandAndHttpCodeAndBody) { http_response.http_code = 501; http_response.body = "--oops--"; Resource resource(kTestUrl, http_client); try { resource.Get("pinky"); FAIL(); // Shouldn't get here } catch (const std::exception& e) { const std::string message = e.what(); ASSERT_NE(std::string::npos, message.find("pinky")); ASSERT_NE(std::string::npos, message.find("--oops--")); ASSERT_NE(std::string::npos, message.find("501")); } } TEST_F(TestResource, WebDriverExceptionContainsStatusAndStatusDescription) { http_response.http_code = 500; http_response.body = Fmt() << "{\"status\":" << response_status_code::kNoSuchWindow << ",\"value\":{\"message\":\"12345\"}}"; Resource resource(kTestUrl, http_client); try { resource.Get("pinky"); FAIL(); // Shouldn't get here } catch (const std::exception& e) { const std::string message = e.what(); ASSERT_NE(std::string::npos, message.find(Fmt() << response_status_code::kNoSuchWindow)); ASSERT_NE(std::string::npos, message.find(response_status_code::ToString(response_status_code::kNoSuchWindow))); } } TEST_F(TestResource, ThrowsOnHttp500) { http_response.http_code = 500; http_response.body = "{\"status\":12,\"value\":{\"message\":\"12345\"}}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp500AndMissingStatus) { http_response.http_code = 500; http_response.body = "{\"value\":{\"message\":\"12345\"}}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp500AndInvalidStatus) { http_response.http_code = 500; http_response.body = "{\"status\":\"xxx\",\"value\":{\"message\":\"12345\"}}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp500AndMissingValue) { http_response.http_code = 500; http_response.body = "{\"status\":12}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp500AndInvalidValue) { http_response.http_code = 500; http_response.body = "{\"status\":\"xxx\",\"value\":\"12345\"}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp500AndMissingMessage) { http_response.http_code = 500; http_response.body = "{\"status\":12,\"value\":{\"xxx\":\"12345\"}}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp500AndInvalidMessage) { http_response.http_code = 500; http_response.body = "{\"status\":12,\"value\":{\"message\":12345}}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp399) { http_response.http_code = 399; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnHttp502) { http_response.http_code = 502; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnEmptyResponse) { Resource resource(kTestUrl, http_client); http_response.body = ""; ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnMalformedResponse) { Resource resource(kTestUrl, http_client); http_response.body = "Blah blah blah"; ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsIfResponseIsNotAnObject) { Resource resource(kTestUrl, http_client); http_response.body = "\"value\":123"; ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnMissingStatus) { http_response.body = "{\"sessionId\":\"123\",\"value\":12345}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnInvalidStatus) { http_response.body = "{\"sessionId\":\"123\",\"status\":\"5\",\"value\":12345}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnNonZeroStatus) { Resource resource(kTestUrl, http_client); http_response.body = "{\"sessionId\":\"123\",\"status\":5,\"value\":12345}"; ASSERT_THROW(resource.Get("command"), WebDriverException); } TEST_F(TestResource, ThrowsOnMissingValue) { http_response.body = "{\"sessionId\":\"123\",\"status\":0}"; Resource resource(kTestUrl, http_client); ASSERT_THROW(resource.Get("command"), WebDriverException); } } // namespace test ================================================ FILE: test/session_test.cpp ================================================ #include "environment.h" #include #include #include namespace test { using namespace webdriverxx; class TestSession : public ::testing::Test { protected: TestSession() : driver(GetDriver()) {} void ReplaceSpoiledSession() { driver = GetFreshDriver(); } WebDriver driver; }; TEST_F(TestSession, GetsCapabilities) { Capabilities c = driver.GetCapabilities(); ASSERT_TRUE(c.Has("browserName")); ASSERT_TRUE(c.Has("version")); ASSERT_TRUE(c.Has("platform")); ASSERT_NE("", c.GetBrowserName()); } TEST_F(TestSession, StartsSecondBrowser) { WebDriver second = CreateDriver(); } TEST_F(TestSession, GetsCurrentWindow) { driver.GetCurrentWindow(); } TEST_F(TestSession, GetsWindowHandle) { ASSERT_NE("", driver.GetCurrentWindow().GetHandle()); } TEST_F(TestSession, SetsFocusToWindow) { driver.SetFocusToWindow(driver.GetCurrentWindow().GetHandle()); } TEST_F(TestSession, ClosesCurrentWindow) { driver.CloseCurrentWindow(); ReplaceSpoiledSession(); } TEST_F(TestSession, GetsWindowSize) { Window window = driver.GetCurrentWindow(); window.GetSize(); } TEST_F(TestSession, SetsWindowSize) { Window window = driver.GetCurrentWindow(); Size size1; size1.width = 601; size1.height = 602; window.SetSize(size1); Size size2 = window.GetSize(); ASSERT_EQ(601, size2.width); ASSERT_EQ(602, size2.height); } TEST_F(TestSession, GetsWindowPosition) { Window window = driver.GetCurrentWindow(); window.GetPosition(); } TEST_F(TestSession, SetsWindowPosition) { if (IsPhantom()) return; Window window = driver.GetCurrentWindow(); Point position1; position1.x = 101; position1.y = 102; window.SetPosition(position1); Point position2 = window.GetPosition(); ASSERT_EQ(101, position2.x); ASSERT_EQ(102, position2.y); } TEST_F(TestSession, MaximizesWindow) { Window window = driver.GetCurrentWindow(); window.Maximize(); } TEST_F(TestSession, GetsWindows) { driver.GetWindows(); } TEST_F(TestSession, Navigates) { std::string url = GetWebDriverUrl() + "status"; driver.Navigate(url); ASSERT_EQ(url, driver.GetUrl()); } TEST_F(TestSession, NavigatesToTestPage) { const std::string url = GetTestPageUrl("session.html"); driver.Navigate(url); ASSERT_EQ(url, driver.GetUrl()); } TEST_F(TestSession, GoesBack) { const std::string page1 = GetTestPageUrl("navigation1.html"); const std::string page2 = GetTestPageUrl("navigation2.html"); driver.Navigate(page1).Navigate(page2).Back(); ASSERT_EQ(page1, driver.GetUrl()); } TEST_F(TestSession, GoesForward) { const std::string page1 = GetTestPageUrl("navigation1.html"); const std::string page2 = GetTestPageUrl("navigation2.html"); driver.Navigate(page1).Navigate(page2).Back().Forward(); ASSERT_EQ(page2, driver.GetUrl()); } TEST_F(TestSession, DoesRefresh) { const std::string page = GetTestPageUrl("navigation1.html"); driver.Navigate(page).FindElement(ByTag("input")).Click().SendKeys("abc"); ASSERT_EQ("abc", driver.FindElement(ByTag("input")).GetAttribute("value")); driver.Refresh(); ASSERT_EQ(page, driver.GetUrl()); ASSERT_EQ("", driver.FindElement(ByTag("input")).GetAttribute("value")); } TEST_F(TestSession, GetsPageSource) { driver.Navigate(GetTestPageUrl("session.html")); std::string source = driver.GetSource(); ASSERT_NE(std::string::npos, source.find("")); } TEST_F(TestSession, GetsPageTitle) { driver.Navigate(GetTestPageUrl("session.html")); ASSERT_EQ("Test title", driver.GetTitle()); } TEST_F(TestSession, GetsScreenshot) { driver.Navigate(GetTestPageUrl("session.html")); ASSERT_TRUE(!driver.GetScreenshot().empty()); } TEST_F(TestSession, SetsTimeouts) { driver.SetTimeoutMs(timeout::Implicit, 1000); driver.SetTimeoutMs(timeout::PageLoad, 1000); driver.SetTimeoutMs(timeout::Script, 1000); } TEST_F(TestSession, SetsAsyncScriptTimeout) { driver.SetAsyncScriptTimeoutMs(1000); } TEST_F(TestSession, SetsImplicitTimeout) { driver.SetImplicitTimeoutMs(1000); } TEST_F(TestSession, GetsActiveElement) { driver.Navigate(GetTestPageUrl("session.html")); Element e = driver.FindElement(ByTag("input")); e.Click(); ASSERT_EQ(e, driver.GetActiveElement()); } Cookie FindCookie( const std::vector& cookies, const std::string& name, const Cookie& default_value = Cookie() ) { const auto it = std::find_if(cookies.begin(), cookies.end(), [&name](const Cookie& cookie){ return cookie.name == name; }); return it != cookies.end() ? *it : default_value; } namespace webdriverxx { void PrintTo(const Cookie& c, ::std::ostream* os) { *os << ToJson(c).serialize(); } } // namespace webdriverxx TEST_F(TestSession, SetsAndGetsCookies) { driver.Navigate(GetTestPageUrl("session.html")); driver.SetCookie(Cookie("name1", "value1")).SetCookie(Cookie("name2", "value2")); std::vector cookies = driver.GetCookies(); ASSERT_TRUE(cookies.size() >= 2u); ASSERT_EQ("value1", FindCookie(cookies, "name1").value); ASSERT_EQ("value2", FindCookie(cookies, "name2").value); } TEST_F(TestSession, SetsAllFieldsOfACookie) { const Cookie c1("name1", "value1", "/path1", "domain1.com", true, true, 123); const Cookie c2("name2", "value2", "/path2", "domain2.com", false, false, 124); ASSERT_EQ(c1, FromJson(ToJson(c1))); ASSERT_EQ(c2, FromJson(ToJson(c2))); } TEST_F(TestSession, DeletesCookies) { driver.Navigate(GetTestPageUrl("session.html")); driver.SetCookie(Cookie("name1", "value1")).SetCookie(Cookie("name2", "value2")); driver.DeleteCookies(); ASSERT_EQ(0u, driver.GetCookies().size()); } TEST_F(TestSession, DeletesACookie) { driver.Navigate(GetTestPageUrl("session.html")); driver.SetCookie(Cookie("name1", "value1")).SetCookie(Cookie("name2", "value2")); driver.DeleteCookie("name1"); std::vector cookies = driver.GetCookies(); ASSERT_EQ("", FindCookie(cookies, "name1").value); ASSERT_EQ("value2", FindCookie(cookies, "name2").value); } } // namespace test ================================================ FILE: test/shared_test.cpp ================================================ #include #include #include namespace test { using namespace webdriverxx::detail; struct WidgetMonitor { WidgetMonitor() : created(0) , copied(0) , deleted(0) {} int created; int copied; int deleted; }; struct Simple : SharedObjectBase { int n; Simple() : n() {} }; class Widget : public SharedObjectBase { public: Widget(WidgetMonitor& monitor) : monitor(monitor) { ++monitor.created; } Widget(const Widget& other) : monitor(other.monitor) { ++monitor.created; } Widget& operator = (const Widget& other) { assert(&monitor == &other.monitor); ++monitor.copied; return *this; } virtual ~Widget() { ++monitor.deleted; } private: WidgetMonitor& monitor; }; class WidgetSubclass : public Widget { public: WidgetSubclass(WidgetMonitor& monitor) : Widget(monitor) {} }; TEST(WidgetMonitor, ZeroByDefault) { WidgetMonitor m; ASSERT_EQ(0, m.created); ASSERT_EQ(0, m.copied); ASSERT_EQ(0, m.deleted); } TEST(WidgetMonitor, ShowsCreateAndDelete) { WidgetMonitor m; ((void)Widget(m)); ASSERT_EQ(1, m.created); ASSERT_EQ(0, m.copied); ASSERT_EQ(1, m.deleted); } TEST(WidgetMonitor, ShowsCopy) { WidgetMonitor m; Widget w1(m); Widget w2(m); w2 = w1; ASSERT_EQ(1, m.copied); } TEST(WidgetMonitor, MonitorsWidgetSubclass) { WidgetMonitor m; ((void)WidgetSubclass(m)); ASSERT_EQ(1, m.created); ASSERT_EQ(0, m.copied); ASSERT_EQ(1, m.deleted); } TEST(Shared, DeletesWidget) { WidgetMonitor m; Shared(new Widget(m)); ASSERT_EQ(1, m.created); ASSERT_EQ(1, m.deleted); } TEST(Shared, ShareSingleWidget) { WidgetMonitor m; { Shared a(new Widget(m)); Shared b = a; Shared c; c = b; } ASSERT_EQ(1, m.created); ASSERT_EQ(0, m.copied); ASSERT_EQ(1, m.deleted); } TEST(Shared, SupportsImplicitTypecasts) { WidgetMonitor m; { Shared c; Shared a(new WidgetSubclass(m)); Shared b = a; c = b; } ASSERT_EQ(1, m.created); ASSERT_EQ(0, m.copied); ASSERT_EQ(1, m.deleted); } TEST(Shared, CanBeUsedInBoolContext) { Shared s1; Shared s2(new Simple); ASSERT_TRUE(!s1); ASSERT_TRUE(!!s2); } TEST(Shared, CanBeDereferenced) { Simple* p = new Simple; Shared s(p); p->n = 123; ASSERT_EQ(123, (*s).n); ASSERT_EQ(123, s->n); } TEST(Shared, CanBeCompared) { Shared s1; Shared s2(new Simple); Shared s3 = s2; Shared s4(new Simple); ASSERT_TRUE(s1 != s2); ASSERT_TRUE(s1 != s3); ASSERT_TRUE(s2 == s3); ASSERT_TRUE(s2 != s4); ASSERT_TRUE(s1 == s1); ASSERT_TRUE(s2 == s2); ASSERT_TRUE(s3 == s3); ASSERT_TRUE(s4 == s4); ASSERT_TRUE(s1 == 0); ASSERT_TRUE(s2 != 0); } } // namespace test ================================================ FILE: test/to_string_test.cpp ================================================ #include #include namespace test { using namespace webdriverxx; using namespace webdriverxx::detail; TEST(ToString, ConvertsIntegralTypes) { ASSERT_EQ("123", ToString(123)); int i = 123; ASSERT_EQ("123", ToString(i)); ASSERT_EQ("123", ToString(static_cast(i))); ASSERT_EQ("'z'", ToString('z')); char c = 'z'; ASSERT_EQ("'z'", ToString(c)); ASSERT_EQ("'z'", ToString(static_cast(c))); } namespace custom { struct A {}; struct B {}; std::ostream& operator << (std::ostream& s, const B&) { return s << "B"; } struct C {}; void ToStream(const C&, std::ostream& s) { s << "C"; } struct G {}; void PrintTo(const G&, std::ostream* s) { *s << "G"; } } // namespace custom TEST(ToString, ConvertsCustomTypes) { ASSERT_EQ("", ToString(custom::A())); ASSERT_EQ("B", ToString(custom::B())); ASSERT_EQ("C", ToString(custom::C())); ASSERT_EQ("G", ToString(custom::G())); } TEST(ToString, ConvertsStrings) { ASSERT_EQ("\"abc\"", ToString("abc")); char a[] = "abc"; ASSERT_EQ("\"abc\"", ToString(a)); const char ca[] = "abc"; ASSERT_EQ("\"abc\"", ToString(ca)); char* pc = a; ASSERT_EQ("\"abc\"", ToString(pc)); const char* pcc = "abc"; ASSERT_EQ("\"abc\"", ToString(pcc)); ASSERT_EQ("\"abc\"", ToString(std::string("abc"))); std::string s("abc"); ASSERT_EQ("\"abc\"", ToString(s)); const std::string cs("abc"); ASSERT_EQ("\"abc\"", ToString(cs)); } TEST(ToString, ConvertsPointers) { int i = 123; ASSERT_EQ("*123", ToString(&i)); const int* cpi = &i; ASSERT_EQ("**123", ToString(&cpi)); const std::string s = "abc"; ASSERT_EQ("*\"abc\"", ToString(&s)); } TEST(ToString, ConvertsContainersOfIntegralTypes) { ASSERT_EQ("[]", ToString(std::vector())); std::vector v; v.push_back(123); v.push_back(456); ASSERT_EQ("*[123, 456]", ToString(&v)); int a[3] = { 123, 456, 789 }; ASSERT_EQ("[123, 456, 789]", ToString(a)); } TEST(ToString, ConvertsContainersOfCustomTypes) { std::vector as(1); ASSERT_EQ("[]", ToString(as)); std::vector bs(1); ASSERT_EQ("[B]", ToString(bs)); std::vector cs(1); ASSERT_EQ("[C]", ToString(cs)); std::vector gs(1); ASSERT_EQ("[G]", ToString(gs)); } TEST(ToString, ConvertsContainersOfStrings) { const char* ss[] = { "abc", "def" }; ASSERT_EQ("[\"abc\", \"def\"]", ToString(ss)); ASSERT_EQ("*[\"abc\", \"def\"]", ToString(&ss)); } } // namespace test ================================================ FILE: test/wait_match_test.cpp ================================================ #include #include namespace test { using namespace webdriverxx; using namespace webdriverxx::detail; bool FunctionMatcher(int) { return true; } struct FunctorMatcher { bool operator () (int) const { return true; } }; TEST(WaitForMatch, CanBeUsedWithFunctionFunctorAndLambda) { ASSERT_EQ(123, WaitForMatch([]{ return 123; }, FunctionMatcher)); ASSERT_EQ(123, WaitForMatch([]{ return 123; }, FunctorMatcher())); ASSERT_EQ(123, WaitForMatch([]{ return 123; }, [](int){ return true; })); } TEST(WaitForMatch, ReturnsMatchedValue) { ASSERT_EQ(123, WaitForMatch([]{ return 123; }, [](int){ return true; })); } TEST(WaitForMatch, DoesNotWaitIfValueIsMatched) { Duration timeout = 1000; const TimePoint start = Now(); WaitForMatch([]{ return 0; }, [](int){ return true; }, timeout); ASSERT_TRUE(Now() - start < timeout/2); } TEST(WaitForMatch, WaitsUntilValueIsMatched) { Duration timeout = 1000; Duration interval = 0; int counter = 0; WaitForMatch([]{ return 0; }, [&counter](int){ return ++counter == 10; }, timeout, interval); ASSERT_EQ(10, counter); } TEST(WaitForMatch, ThrowsExceptionOnTimeout) { Duration timeout = 0; ASSERT_THROW(WaitForMatch([]{ return 0; }, [](int){ return false; }, timeout), WebDriverException); } TEST(WaitForMatch, ExplainsTimeout) { try { Duration timeout = 0; WaitForMatch([]{ return 0; }, [](int){ return false; }, timeout); FAIL(); } catch (const std::exception& e) { std::string message = e.what(); const auto npos = std::string::npos; ASSERT_NE(npos, message.find("imeout")); } } TEST(WaitForMatch, CanUseGMockMatchers) { using namespace ::testing; ASSERT_EQ(123, WaitForMatch([]{ return 123; }, Eq(123))); ASSERT_EQ(123, WaitForMatch([]{ return 123; }, 123)); ASSERT_EQ("abc", WaitForMatch([]{ return std::string("abc"); }, "abc")); ASSERT_EQ("abc", WaitForMatch([]{ return std::string("abc"); }, Eq("abc"))); ASSERT_EQ(123, WaitForMatch([]{ return 123; }, _)); ASSERT_EQ(123, WaitForMatch([]{ return 123; }, An())); std::vector v(1, 123); ASSERT_EQ(v, WaitForMatch([&v]{ return v; }, Contains(123))); ASSERT_EQ(v, WaitForMatch([&v]{ return v; }, Not(Contains(456)))); Duration timeout = 0; ASSERT_THROW(WaitForMatch([&v]{ return v; }, Not(Contains(123)), timeout), WebDriverException); } TEST(WaitForMatch, ExplainsGMockMatcherMismatch) { try { Duration timeout = 0; using namespace ::testing; WaitForMatch([]{ return 123; }, Eq(456), timeout); FAIL(); } catch (const std::exception& e) { std::string message = e.what(); const auto npos = std::string::npos; ASSERT_NE(npos, message.find("123")); ASSERT_NE(npos, message.find("456")); ASSERT_NE(npos, message.find("imeout")); } } } // namespace test ================================================ FILE: test/wait_test.cpp ================================================ #include #include namespace test { using namespace webdriverxx; using namespace webdriverxx::detail; int FunctionGetter() { return 123; } struct FunctorGetter { int operator () () const { return 456; } }; TEST(WaitForValue, CanBeUsedWithFunctionFunctorAndLambda) { ASSERT_EQ(123, WaitForValue(FunctionGetter)); ASSERT_EQ(456, WaitForValue(FunctorGetter())); ASSERT_EQ(789, WaitForValue([]{ return 789; })); } TEST(WaitForValue, DoesNotWaitIfValueIsReturned) { Duration timeout = 1000; const TimePoint start = Now(); WaitForValue(FunctionGetter, timeout); ASSERT_TRUE(Now() - start < timeout/2); } TEST(WaitForValue, CallsGetterOnce) { int counter = 0; WaitForValue([&counter]{ return ++counter; }); ASSERT_EQ(1, counter); } TEST(WaitForValue, ThrowsExceptionOnTimeout) { Duration timeout = 0; ASSERT_THROW(WaitForValue([]() -> int { throw std::exception(); }, timeout), WebDriverException); } TEST(WaitForValue, CallsGetterUntilItSucceeds) { Duration timeout = 1000; Duration interval = 0; int counter = 0; WaitForValue([&counter]() -> int { if (++counter < 10) throw std::exception(); return counter; }, timeout, interval); ASSERT_EQ(10, counter); } TEST(WaitForValue, PassesErrorMessageFromGetter) { Duration timeout = 0; try { WaitForValue([]() -> int { throw std::runtime_error("abc"); }, timeout); FAIL(); } catch (const WebDriverException& e) { std::string message = e.what(); const auto npos = std::string::npos; ASSERT_NE(npos, message.find("abc")); ASSERT_NE(npos, message.find("imeout")); } } TEST(WaitUntil, DoesNotWaitIfValueNotFalsy) { Duration timeout = 1000; const TimePoint start = Now(); WaitUntil([]{ return true; }, timeout); ASSERT_TRUE(Now() - start < timeout/2); } TEST(WaitUntil, CallsGetterOnce) { int counter = 0; WaitUntil([&counter]{ return ++counter; }); ASSERT_EQ(1, counter); } TEST(WaitUntil, ThrowsExceptionOnTimeout) { Duration timeout = 0; ASSERT_THROW(WaitUntil([]() -> bool { throw std::exception(); }, timeout), WebDriverException); } TEST(WaitUntil, ThrowsExceptionOnTimeout2) { Duration timeout = 0; ASSERT_THROW(WaitUntil([]{ return false; }, timeout), WebDriverException); } } // namespace test ================================================ FILE: test/webdriver_test.cpp ================================================ #include "environment.h" #include #include namespace test { using namespace webdriverxx; TEST(WebDriver, CreatesSession) { Client client(GetWebDriverUrl()); size_t number_of_sessions_before = client.GetSessions().size(); WebDriver testee = CreateDriver(); size_t number_of_sessions_after = client.GetSessions().size(); ASSERT_EQ(number_of_sessions_before + 1, number_of_sessions_after); } TEST(WebDriver, DeletesSessionOnDestruction) { Client client(GetWebDriverUrl()); size_t number_of_sessions_before = 0; { WebDriver testee = CreateDriver(); number_of_sessions_before = client.GetSessions().size(); } size_t number_of_sessions_after = client.GetSessions().size(); ASSERT_EQ(number_of_sessions_before - 1, number_of_sessions_after); } TEST(WebDriver, IsCopyable) { WebDriver driver1(GetDriver()); const WebDriver driver2 = driver1; WebDriver driver3 = driver1; driver3 = driver2; ASSERT_NO_THROW(GetDriver().GetSessions()); ASSERT_NO_THROW(driver1.GetSessions()); ASSERT_NO_THROW(driver2.GetSessions()); ASSERT_NO_THROW(driver3.GetSessions()); } TEST(WebDriver, CopyableToClient) { WebDriver driver = GetDriver(); Client client = driver; ASSERT_NO_THROW(client.GetSessions()); ASSERT_NO_THROW(driver.GetSessions()); } TEST(WebDriver, CopyableToSession) { WebDriver driver = GetDriver(); Session session = driver; ASSERT_NO_THROW(session.GetWindows()); ASSERT_NO_THROW(driver.GetWindows()); } TEST(WebDriver, AndSatelliteObjectsHasNoLifetimeIssues) { // It is too expensive to restart the driver -> // try to test all objects in one test. WebDriver& driver = GetDriver(); driver.Navigate(GetTestPageUrl("webdriver.html")); Element body = driver.FindElement(ByTag("body")); { Window window = driver.GetCurrentWindow(); { Session session = driver; { Client client = driver; { WebDriver local_driver = driver; GetFreshDriver(); // Destroy global instance ASSERT_NO_THROW(local_driver.GetSessions()); ASSERT_NO_THROW(local_driver.GetWindows()); ASSERT_NO_THROW(local_driver.FindElement(ByTag("input"))); } ASSERT_NO_THROW(client.GetSessions()); } ASSERT_NO_THROW(session.GetWindows()); ASSERT_NO_THROW(session.FindElement(ByTag("input"))); } ASSERT_NO_THROW(window.GetSize()); } ASSERT_NO_THROW(body.FindElement(ByTag("input"))); } } // namespace test