diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-20 17:59:04 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-20 17:59:04 +0000 |
commit | 64b8c616b3750acc4fd22cb5e6682e389a73c46e (patch) | |
tree | 6b7a177e89ef07ea0dba0a3eb8f04a71442d3784 | |
parent | e62768fec279649b786e1fe6f42a898cdc84914b (diff) | |
download | chromium_src-64b8c616b3750acc4fd22cb5e6682e389a73c46e.zip chromium_src-64b8c616b3750acc4fd22cb5e6682e389a73c46e.tar.gz chromium_src-64b8c616b3750acc4fd22cb5e6682e389a73c46e.tar.bz2 |
Implement log command and prefs for chromedriver. Have separate logs per
session. Log every command by default.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/8997019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@115147 0039d316-1c4b-4281-b951-d872f2087c98
29 files changed, 657 insertions, 253 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 24c5919..9a155b5 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -984,6 +984,8 @@ 'test/webdriver/commands/execute_command.h', 'test/webdriver/commands/find_element_commands.cc', 'test/webdriver/commands/find_element_commands.h', + 'test/webdriver/commands/log_command.cc', + 'test/webdriver/commands/log_command.h', 'test/webdriver/commands/navigate_commands.cc', 'test/webdriver/commands/navigate_commands.h', 'test/webdriver/commands/mouse_commands.cc', diff --git a/chrome/test/webdriver/commands/command.cc b/chrome/test/webdriver/commands/command.cc index 3b1ea07..7f4686d 100644 --- a/chrome/test/webdriver/commands/command.cc +++ b/chrome/test/webdriver/commands/command.cc @@ -29,7 +29,7 @@ bool Command::Init(Response* const response) { return true; } -void Command::Finish() {} +void Command::Finish(Response* const response) {} std::string Command::GetPathVariable(const size_t i) const { return i < path_segments_.size() ? path_segments_.at(i) : ""; diff --git a/chrome/test/webdriver/commands/command.h b/chrome/test/webdriver/commands/command.h index 94dee0f..97f0259 100644 --- a/chrome/test/webdriver/commands/command.h +++ b/chrome/test/webdriver/commands/command.h @@ -45,7 +45,7 @@ class Command { // Called after this command is executed. Returns NULL if no error occurs. // This is only called if |Init| is successful and regardless of whether // the execution results in a |Error|. - virtual void Finish(); + virtual void Finish(Response* const response); // Executes the corresponding variant of this command URL. // Always called after |Init()| and called from the Execute function. @@ -101,10 +101,10 @@ class Command { // false if there is no such parameter, or if it is not a list. bool GetListParameter(const std::string& key, ListValue** out) const; - private: const std::vector<std::string> path_segments_; const scoped_ptr<const DictionaryValue> parameters_; + private: #if defined(OS_MACOSX) // An autorelease pool must exist on any thread where Objective C is used, // even implicitly. Otherwise the warning: diff --git a/chrome/test/webdriver/commands/create_session.cc b/chrome/test/webdriver/commands/create_session.cc index 4f07f3a..7e80ec6 100644 --- a/chrome/test/webdriver/commands/create_session.cc +++ b/chrome/test/webdriver/commands/create_session.cc @@ -8,11 +8,9 @@ #include "base/command_line.h" #include "base/file_path.h" -#include "base/logging.h" #include "base/scoped_temp_dir.h" #include "base/values.h" #include "chrome/test/webdriver/commands/response.h" -#include "chrome/test/webdriver/webdriver_capabilities_parser.h" #include "chrome/test/webdriver/webdriver_error.h" #include "chrome/test/webdriver/webdriver_session.h" #include "chrome/test/webdriver/webdriver_session_manager.h" @@ -34,48 +32,15 @@ void CreateSession::ExecutePost(Response* const response) { kBadRequest, "Missing or invalid 'desiredCapabilities'")); return; } - ScopedTempDir temp_dir; - if (!temp_dir.CreateUniqueTempDir()) { - response->SetError(new Error( - kUnknownError, "Unable to create temp directory for unpacking")); - return; - } - Capabilities caps; - CapabilitiesParser parser(dict, temp_dir.path(), &caps); - Error* error = parser.Parse(); - if (error) { - response->SetError(error); - return; - } - - // Since logging is shared among sessions, if any session requests verbose - // logging, verbose logging will be enabled for all sessions. It is not - // possible to turn it off. - if (caps.verbose) - logging::SetMinLogLevel(logging::LOG_INFO); - - Session::Options session_options; - session_options.load_async = caps.load_async; - session_options.use_native_events = caps.native_events; - session_options.no_website_testing_defaults = - caps.no_website_testing_defaults; - session_options.extensions = caps.extensions; - - Automation::BrowserOptions browser_options; - browser_options.command = caps.command; - browser_options.channel_id = caps.channel; - browser_options.detach_process = caps.detach; - browser_options.user_data_dir = caps.profile; // Session manages its own liftime, so do not call delete. - Session* session = new Session(session_options); - error = session->Init(browser_options); + Session* session = new Session(); + Error* error = session->Init(dict); if (error) { response->SetError(error); return; } - LOG(INFO) << "Created session " << session->id(); // Redirect to a relative URI. Although prohibited by the HTTP standard, // this is what the IEDriver does. Finding the actual IP address is // difficult, and returning the hostname causes perf problems with the python diff --git a/chrome/test/webdriver/commands/log_command.cc b/chrome/test/webdriver/commands/log_command.cc new file mode 100644 index 0000000..738a585 --- /dev/null +++ b/chrome/test/webdriver/commands/log_command.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/test/webdriver/commands/log_command.h" + +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/test/webdriver/commands/response.h" +#include "chrome/test/webdriver/webdriver_error.h" +#include "chrome/test/webdriver/webdriver_session.h" + +namespace webdriver { + +LogCommand::LogCommand( + const std::vector<std::string>& path_segments, + DictionaryValue* parameters) + : WebDriverCommand(path_segments, parameters) { +} + +LogCommand::~LogCommand() { +} + +bool LogCommand::DoesPost() { + return true; +} + +void LogCommand::ExecutePost(Response* const response) { + std::string type; + if (!GetStringParameter("type", &type)) { + response->SetError(new Error(kUnknownError, "Missing or invalid 'type'")); + return; + } + + LogType log_type; + if (!LogType::FromString(type, &log_type)) { + response->SetError(new Error(kUnknownError, "Unknown log type: " + type)); + return; + } + + if (log_type.type() == LogType::kDriver) { + response->SetValue(session_->GetLog()); + } else { + response->SetError(new Error(kUnknownError, "Unrecognized type: " + type)); + return; + } +} + +} // namespace webdriver diff --git a/chrome/test/webdriver/commands/log_command.h b/chrome/test/webdriver/commands/log_command.h new file mode 100644 index 0000000..e9ce310 --- /dev/null +++ b/chrome/test/webdriver/commands/log_command.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_TEST_WEBDRIVER_COMMANDS_LOG_COMMAND_H_ +#define CHROME_TEST_WEBDRIVER_COMMANDS_LOG_COMMAND_H_ + +#include <string> +#include <vector> + +#include "chrome/test/webdriver/commands/webdriver_command.h" + +namespace base { +class DictionaryValue; +} + +namespace webdriver { + +class Response; + +class LogCommand : public WebDriverCommand { + public: + LogCommand(const std::vector<std::string>& path_segments, + base::DictionaryValue* parameters); + virtual ~LogCommand(); + + virtual bool DoesPost() OVERRIDE; + virtual void ExecutePost(Response* const response) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LogCommand); +}; + +} // namespace webdriver + +#endif // CHROME_TEST_WEBDRIVER_COMMANDS_LOG_COMMAND_H_ diff --git a/chrome/test/webdriver/commands/response.cc b/chrome/test/webdriver/commands/response.cc index 7239f71..bef1386 100644 --- a/chrome/test/webdriver/commands/response.cc +++ b/chrome/test/webdriver/commands/response.cc @@ -9,6 +9,9 @@ #include "base/logging.h" #include "base/values.h" +using base::DictionaryValue; +using base::Value; + namespace webdriver { namespace { @@ -41,8 +44,7 @@ void Response::SetStatus(ErrorCode status) { const Value* Response::GetValue() const { Value* out = NULL; - LOG_IF(WARNING, !data_.Get(kValueKey, &out)) - << "Accessing unset response value."; // Should never happen. + data_.Get(kValueKey, &out); return out; } @@ -63,6 +65,10 @@ void Response::SetField(const std::string& key, Value* value) { data_.Set(key, value); } +const Value* Response::GetDictionary() const { + return &data_; +} + std::string Response::ToJSON() const { std::string json; base::JSONWriter::Write(&data_, false, &json); diff --git a/chrome/test/webdriver/commands/response.h b/chrome/test/webdriver/commands/response.h index 9c44e60..3545081 100644 --- a/chrome/test/webdriver/commands/response.h +++ b/chrome/test/webdriver/commands/response.h @@ -28,11 +28,11 @@ class Response { void SetStatus(ErrorCode status); // Ownership of the returned pointer is kept by this object. - const Value* GetValue() const; + const base::Value* GetValue() const; // Sets the |value| of this response, assuming ownership of the object in the // process. - void SetValue(Value* value); + void SetValue(base::Value* value); // Configures this response to report the given error. Ownership of the error // is taken from the caller. @@ -42,13 +42,16 @@ class Response { // string to indicate the value should be set in a nested object. Any // previously set value for the |key| will be deleted. // This object assumes ownership of |value|. - void SetField(const std::string& key, Value* value); + void SetField(const std::string& key, base::Value* value); + + // Returns a pointer to the actual response dictionary. + const base::Value* GetDictionary() const; // Returns this response as a JSON string. std::string ToJSON() const; private: - DictionaryValue data_; + base::DictionaryValue data_; DISALLOW_COPY_AND_ASSIGN(Response); }; diff --git a/chrome/test/webdriver/commands/session_with_id.cc b/chrome/test/webdriver/commands/session_with_id.cc index 3148fa0..d14236b 100644 --- a/chrome/test/webdriver/commands/session_with_id.cc +++ b/chrome/test/webdriver/commands/session_with_id.cc @@ -70,7 +70,7 @@ void SessionWithID::ExecuteGet(Response* const response) { Value::CreateStringValue(chrome::kChromeVersion)); temp_value->SetWithoutPathExpansion( "chrome.nativeEvents", - Value::CreateBooleanValue(session_->options().use_native_events)); + Value::CreateBooleanValue(session_->capabilities().native_events)); response->SetValue(temp_value); } diff --git a/chrome/test/webdriver/commands/set_timeout_commands_unittest.cc b/chrome/test/webdriver/commands/set_timeout_commands_unittest.cc index ff8f5ac..748738e 100644 --- a/chrome/test/webdriver/commands/set_timeout_commands_unittest.cc +++ b/chrome/test/webdriver/commands/set_timeout_commands_unittest.cc @@ -50,8 +50,7 @@ void AssertTimeoutSet(const Session& test_session, int expected_timeout, } // namespace TEST(ImplicitWaitCommandTest, SettingImplicitWaits) { - Session::Options options = Session::Options(); - Session test_session(options); + Session test_session; ASSERT_EQ(0, test_session.implicit_wait()) << "Sanity check failed"; std::vector<std::string> path_segments; @@ -75,10 +74,10 @@ TEST(ImplicitWaitCommandTest, SettingImplicitWaits) { AssertError(kBadRequest, "ms parameter is not a number", &command); parameters->SetInteger("ms", -1); - AssertError(kBadRequest, "timeout must be non-negative: -1", &command); + AssertError(kBadRequest, "Timeout must be non-negative", &command); parameters->SetDouble("ms", -3.0); - AssertError(kBadRequest, "timeout must be non-negative: -3", &command); + AssertError(kBadRequest, "Timeout must be non-negative", &command); parameters->SetInteger("ms", 1); AssertTimeoutSet(test_session, 1, &command); diff --git a/chrome/test/webdriver/commands/url_command.cc b/chrome/test/webdriver/commands/url_command.cc index 05a138a..f66494c 100644 --- a/chrome/test/webdriver/commands/url_command.cc +++ b/chrome/test/webdriver/commands/url_command.cc @@ -49,7 +49,6 @@ void URLCommand::ExecutePost(Response* const response) { response->SetError(error); return; } - response->SetValue(new StringValue(url)); } diff --git a/chrome/test/webdriver/commands/webdriver_command.cc b/chrome/test/webdriver/commands/webdriver_command.cc index 7d626f6..3fb9e41 100644 --- a/chrome/test/webdriver/commands/webdriver_command.cc +++ b/chrome/test/webdriver/commands/webdriver_command.cc @@ -6,14 +6,17 @@ #include <string> -#include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/singleton.h" +#include "base/string_util.h" +#include "base/stringprintf.h" #include "base/values.h" #include "chrome/test/webdriver/commands/response.h" #include "chrome/test/webdriver/webdriver_error.h" +#include "chrome/test/webdriver/webdriver_logging.h" #include "chrome/test/webdriver/webdriver_session.h" #include "chrome/test/webdriver/webdriver_session_manager.h" +#include "chrome/test/webdriver/webdriver_util.h" namespace webdriver { @@ -41,6 +44,12 @@ bool WebDriverCommand::Init(Response* const response) { return false; } + std::string message = base::StringPrintf( + "Command received (%s)", JoinString(path_segments_, '/').c_str()); + if (parameters_.get()) + message += " with params " + JsonStringifyForDisplay(parameters_.get()); + session_->logger().Log(kFineLogLevel, message); + if (ShouldRunPreAndPostCommandHandlers()) { Error* error = session_->BeforeExecuteCommand(); if (error) { @@ -52,17 +61,27 @@ bool WebDriverCommand::Init(Response* const response) { return true; } -void WebDriverCommand::Finish() { +void WebDriverCommand::Finish(Response* const response) { // The session may have been terminated as a result of the command. if (!SessionManager::GetInstance()->Has(session_id_)) return; + if (ShouldRunPreAndPostCommandHandlers()) { scoped_ptr<Error> error(session_->AfterExecuteCommand()); if (error.get()) { - LOG(WARNING) << "Command did not finish successfully: " - << error->details(); + session_->logger().Log(kWarningLogLevel, + "AfterExecuteCommand failed: " + error->details()); } } + + LogLevel level = kWarningLogLevel; + if (response->GetStatus() == kSuccess) + level = kFineLogLevel; + session_->logger().Log( + level, base::StringPrintf( + "Command finished (%s) with response %s", + JoinString(path_segments_, '/').c_str(), + JsonStringifyForDisplay(response->GetDictionary()).c_str())); } bool WebDriverCommand::ShouldRunPreAndPostCommandHandlers() { diff --git a/chrome/test/webdriver/commands/webdriver_command.h b/chrome/test/webdriver/commands/webdriver_command.h index bbef57b..56ba60f 100644 --- a/chrome/test/webdriver/commands/webdriver_command.h +++ b/chrome/test/webdriver/commands/webdriver_command.h @@ -34,7 +34,7 @@ class WebDriverCommand : public Command { // Initializes this webdriver command by fetching the command session. virtual bool Init(Response* const response) OVERRIDE; - virtual void Finish() OVERRIDE; + virtual void Finish(Response* const response) OVERRIDE; // Returns whether this command should run the session pre and post // command handlers. These handlers include waiting for the page to load. diff --git a/chrome/test/webdriver/webdriver_automation.cc b/chrome/test/webdriver/webdriver_automation.cc index 80e25c5..787c4e6 100644 --- a/chrome/test/webdriver/webdriver_automation.cc +++ b/chrome/test/webdriver/webdriver_automation.cc @@ -15,7 +15,6 @@ #include "base/file_path.h" #include "base/file_util.h" #include "base/json/json_writer.h" -#include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/path_service.h" #include "base/string_number_conversions.h" @@ -190,7 +189,7 @@ Automation::BrowserOptions::BrowserOptions() Automation::BrowserOptions::~BrowserOptions() {} -Automation::Automation() {} +Automation::Automation(const Logger& logger) : logger_(logger) {} Automation::~Automation() {} @@ -229,7 +228,6 @@ void Automation::Init(const BrowserOptions& options, Error** error) { std::string chrome_details = base::StringPrintf( "Using Chrome binary at: %" PRFilePath, command.GetProgram().value().c_str()); - LOG(INFO) << chrome_details; // Create the ProxyLauncher and launch Chrome. // In Chrome 13/14, the only way to detach the browser process is to use a @@ -257,9 +255,16 @@ void Automation::Init(const BrowserOptions& options, Error** error) { #endif } if (channel_id.empty()) { + std::string command_line_str; +#if defined(OS_WIN) + command_line_str = WideToUTF8(command.GetCommandLineString()); +#elif defined(OS_POSIX) + command_line_str = command.GetCommandLineString(); +#endif + logger_.Log(kInfoLogLevel, "Launching chrome: " + command_line_str); launcher_.reset(new AnonymousProxyLauncher(false)); } else { - LOG(INFO) << "Using named testing interface"; + logger_.Log(kInfoLogLevel, "Using named testing interface"); launcher_.reset(new NamedProxyLauncher(channel_id, launch_browser, false)); } ProxyLauncher::LaunchState launch_props = { @@ -271,7 +276,7 @@ void Automation::Init(const BrowserOptions& options, Error** error) { true // show_window }; if (!launcher_->InitializeConnection(launch_props, true)) { - LOG(ERROR) << "Failed to initialize connection"; + logger_.Log(kSevereLogLevel, "Failed to initialize connection"); *error = new Error( kUnknownError, "Unable to either launch or connect to Chrome. Please check that " @@ -280,8 +285,8 @@ void Automation::Init(const BrowserOptions& options, Error** error) { } launcher_->automation()->set_action_timeout_ms(base::kNoTimeout); - LOG(INFO) << "Chrome launched successfully. Version: " - << automation()->server_version(); + logger_.Log(kInfoLogLevel, "Connected to Chrome successfully. Version: " + + automation()->server_version()); // Check the version of Chrome is compatible with this ChromeDriver. chrome_details += ", version (" + automation()->server_version() + ")"; diff --git a/chrome/test/webdriver/webdriver_automation.h b/chrome/test/webdriver/webdriver_automation.h index 0328871..9b2036c 100644 --- a/chrome/test/webdriver/webdriver_automation.h +++ b/chrome/test/webdriver/webdriver_automation.h @@ -15,6 +15,7 @@ #include "base/memory/scoped_ptr.h" #include "base/task.h" #include "chrome/common/automation_constants.h" +#include "chrome/test/webdriver/webdriver_logging.h" #include "ui/base/keycodes/keyboard_codes.h" class AutomationId; @@ -65,7 +66,7 @@ class Automation { bool detach_process; }; - Automation(); + explicit Automation(const Logger& logger); virtual ~Automation(); // Start the system's default Chrome binary. @@ -232,6 +233,7 @@ class Automation { Error* CheckAdvancedInteractionsSupported(); Error* CheckNewExtensionInterfaceSupported(); + const Logger& logger_; scoped_ptr<ProxyLauncher> launcher_; DISALLOW_COPY_AND_ASSIGN(Automation); diff --git a/chrome/test/webdriver/webdriver_capabilities_parser.cc b/chrome/test/webdriver/webdriver_capabilities_parser.cc index 9ac398a..b9555ab 100644 --- a/chrome/test/webdriver/webdriver_capabilities_parser.cc +++ b/chrome/test/webdriver/webdriver_capabilities_parser.cc @@ -7,7 +7,6 @@ #include "base/base64.h" #include "base/file_util.h" #include "base/format_macros.h" -#include "base/logging.h" #include "base/stringprintf.h" #include "base/string_util.h" #include "base/values.h" @@ -16,6 +15,7 @@ #include "chrome/test/webdriver/webdriver_error.h" #include "chrome/test/webdriver/webdriver_util.h" +using base::DictionaryValue; using base::Value; namespace webdriver { @@ -39,39 +39,49 @@ Capabilities::Capabilities() detach(false), load_async(false), native_events(false), - no_website_testing_defaults(false), - verbose(false) { } + no_website_testing_defaults(false) { + log_levels[LogType::kDriver] = kAllLogLevel; +} Capabilities::~Capabilities() { } CapabilitiesParser::CapabilitiesParser( - const base::DictionaryValue* capabilities_dict, + const DictionaryValue* capabilities_dict, const FilePath& root_path, + const Logger& logger, Capabilities* capabilities) : dict_(capabilities_dict), root_(root_path), + logger_(logger), caps_(capabilities) { } CapabilitiesParser::~CapabilitiesParser() { } Error* CapabilitiesParser::Parse() { - // Parse WebDriver proxy capabilities first. - const char kProxyKey[] = "proxy"; - Value* proxy_options_value; - if (dict_->Get(kProxyKey, &proxy_options_value)) { - const base::DictionaryValue* proxy_options; - if (!proxy_options_value->GetAsDictionary(&proxy_options)) { - return CreateBadInputError( - kProxyKey, Value::TYPE_DICTIONARY, proxy_options_value); + // Parse WebDriver standard capabilities. + typedef Error* (CapabilitiesParser::*Parser)(const Value*); + + struct NameAndParser { + const char* name; + Parser parser; + }; + NameAndParser name_and_parser[] = { + { "proxy", &CapabilitiesParser::ParseProxy }, + { "loggingPrefs", &CapabilitiesParser::ParseLoggingPrefs } + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(name_and_parser); ++i) { + Value* value; + if (dict_->Get(name_and_parser[i].name, &value)) { + Error* error = (this->*name_and_parser[i].parser)(value); + if (error) + return error; } - Error* error = ParseProxyCapabilities(proxy_options); - if (error) - return error; } + // Parse Chrome custom capabilities (a.k.a., ChromeOptions). const char kOptionsKey[] = "chromeOptions"; - const base::DictionaryValue* options = dict_; + const DictionaryValue* options = dict_; bool legacy_options = true; Value* options_value; if (dict_->Get(kOptionsKey, &options_value)) { @@ -84,7 +94,6 @@ Error* CapabilitiesParser::Parse() { } } - typedef Error* (CapabilitiesParser::*Parser)(const Value*); std::map<std::string, Parser> parser_map; if (legacy_options) { parser_map["chrome.binary"] = &CapabilitiesParser::ParseBinary; @@ -97,7 +106,6 @@ Error* CapabilitiesParser::Parse() { parser_map["chrome.switches"] = &CapabilitiesParser::ParseArgs; parser_map["chrome.noWebsiteTestingDefaults"] = &CapabilitiesParser::ParseNoWebsiteTestingDefaults; - parser_map["chrome.verbose"] = &CapabilitiesParser::ParseVerbose; } else { parser_map["args"] = &CapabilitiesParser::ParseArgs; parser_map["binary"] = &CapabilitiesParser::ParseBinary; @@ -109,10 +117,9 @@ Error* CapabilitiesParser::Parse() { parser_map["profile"] = &CapabilitiesParser::ParseProfile; parser_map["noWebsiteTestingDefaults"] = &CapabilitiesParser::ParseNoWebsiteTestingDefaults; - parser_map["verbose"] = &CapabilitiesParser::ParseVerbose; } - base::DictionaryValue::key_iterator key_iter = options->begin_keys(); + DictionaryValue::key_iterator key_iter = options->begin_keys(); for (; key_iter != options->end_keys(); ++key_iter) { if (parser_map.find(*key_iter) == parser_map.end()) { if (!legacy_options) @@ -207,6 +214,31 @@ Error* CapabilitiesParser::ParseLoadAsync(const Value* option) { return NULL; } +Error* CapabilitiesParser::ParseLoggingPrefs(const base::Value* option) { + const DictionaryValue* logging_prefs; + if (!option->GetAsDictionary(&logging_prefs)) + return CreateBadInputError("loggingPrefs", Value::TYPE_DICTIONARY, option); + + DictionaryValue::key_iterator key_iter = logging_prefs->begin_keys(); + for (; key_iter != logging_prefs->end_keys(); ++key_iter) { + LogType log_type; + if (!LogType::FromString(*key_iter, &log_type)) + continue; + + Value* level_value; + logging_prefs->Get(*key_iter, &level_value); + int level; + if (!level_value->GetAsInteger(&level)) { + return CreateBadInputError( + std::string("loggingPrefs.") + *key_iter, + Value::TYPE_INTEGER, + level_value); + } + caps_->log_levels[log_type.type()] = static_cast<LogLevel>(level); + } + return NULL; +} + Error* CapabilitiesParser::ParseNativeEvents(const Value* option) { if (!option->GetAsBoolean(&caps_->native_events)) return CreateBadInputError("nativeEvents", Value::TYPE_BOOLEAN, option); @@ -225,8 +257,11 @@ Error* CapabilitiesParser::ParseProfile(const Value* option) { return NULL; } -Error* CapabilitiesParser::ParseProxyCapabilities( - const base::DictionaryValue* options) { +Error* CapabilitiesParser::ParseProxy(const base::Value* option) { + const DictionaryValue* options; + if (!option->GetAsDictionary(&options)) + return CreateBadInputError("proxy", Value::TYPE_DICTIONARY, option); + // Quick check of proxy capabilities. std::set<std::string> proxy_options; proxy_options.insert("autodetect"); @@ -238,10 +273,10 @@ Error* CapabilitiesParser::ParseProxyCapabilities( proxy_options.insert("sslProxy"); proxy_options.insert("class"); // Created by BeanToJSONConverter. - base::DictionaryValue::key_iterator key_iter = options->begin_keys(); + DictionaryValue::key_iterator key_iter = options->begin_keys(); for (; key_iter != options->end_keys(); ++key_iter) { if (proxy_options.find(*key_iter) == proxy_options.end()) { - LOG(WARNING) << "Unrecognized proxy capability: " << *key_iter; + logger_.Log(kInfoLogLevel, "Unrecognized proxy capability: " + *key_iter); } } @@ -255,13 +290,14 @@ Error* CapabilitiesParser::ParseProxyCapabilities( proxy_type_parser_map["direct"] = NULL; proxy_type_parser_map["system"] = NULL; - Value* option; - if (!options->Get("proxyType", &option)) + Value* proxy_type_value; + if (!options->Get("proxyType", &proxy_type_value)) return new Error(kBadRequest, "Missing 'proxyType' capability."); std::string proxy_type; - if (!option->GetAsString(&proxy_type)) - return CreateBadInputError("proxyType", Value::TYPE_STRING, option); + if (!proxy_type_value->GetAsString(&proxy_type)) + return CreateBadInputError("proxyType", Value::TYPE_STRING, + proxy_type_value); proxy_type = StringToLowerASCII(proxy_type); if (proxy_type_parser_map.find(proxy_type) == proxy_type_parser_map.end()) @@ -283,7 +319,7 @@ Error* CapabilitiesParser::ParseProxyCapabilities( } Error* CapabilitiesParser::ParseProxyAutoDetect( - const base::DictionaryValue* options){ + const DictionaryValue* options){ const char kProxyAutoDetectKey[] = "autodetect"; bool proxy_auto_detect = false; if (!options->GetBoolean(kProxyAutoDetectKey, &proxy_auto_detect)) @@ -295,7 +331,7 @@ Error* CapabilitiesParser::ParseProxyAutoDetect( } Error* CapabilitiesParser::ParseProxyAutoconfigUrl( - const base::DictionaryValue* options){ + const DictionaryValue* options){ const char kProxyAutoconfigUrlKey[] = "proxyAutoconfigUrl"; CommandLine::StringType proxy_pac_url; if (!options->GetString(kProxyAutoconfigUrlKey, &proxy_pac_url)) @@ -306,7 +342,7 @@ Error* CapabilitiesParser::ParseProxyAutoconfigUrl( } Error* CapabilitiesParser::ParseProxyServers( - const base::DictionaryValue* options) { + const DictionaryValue* options) { const char kNoProxy[] = "noProxy"; const char kFtpProxy[] = "ftpProxy"; const char kHttpProxy[] = "httpProxy"; @@ -374,12 +410,6 @@ Error* CapabilitiesParser::ParseNoWebsiteTestingDefaults(const Value* option) { return NULL; } -Error* CapabilitiesParser::ParseVerbose(const Value* option) { - if (!option->GetAsBoolean(&caps_->verbose)) - return CreateBadInputError("verbose", Value::TYPE_BOOLEAN, option); - return NULL; -} - bool CapabilitiesParser::DecodeAndWriteFile( const FilePath& path, const std::string& base64, diff --git a/chrome/test/webdriver/webdriver_capabilities_parser.h b/chrome/test/webdriver/webdriver_capabilities_parser.h index 861bcdd..c292512 100644 --- a/chrome/test/webdriver/webdriver_capabilities_parser.h +++ b/chrome/test/webdriver/webdriver_capabilities_parser.h @@ -12,6 +12,7 @@ #include "base/basictypes.h" #include "base/command_line.h" #include "base/file_path.h" +#include "chrome/test/webdriver/webdriver_logging.h" namespace base { class DictionaryValue; @@ -45,6 +46,9 @@ struct Capabilities { // Whether Chrome should not block when loading. bool load_async; + // The minimum level to log for each log type. + LogLevel log_levels[LogType::kNum]; + // Whether Chrome should simulate input events using OS APIs instead of // WebKit APIs. bool native_events; @@ -58,9 +62,6 @@ struct Capabilities { // Path to a custom profile to use. FilePath profile; - - // Whether ChromeDriver should log verbosely. - bool verbose; }; // Parses the given capabilities dictionary to produce a |Capabilities| @@ -75,6 +76,7 @@ class CapabilitiesParser { // this directory. CapabilitiesParser(const base::DictionaryValue* capabilities_dict, const FilePath& root_path, + const Logger& logger, Capabilities* capabilities); ~CapabilitiesParser(); @@ -88,15 +90,15 @@ class CapabilitiesParser { Error* ParseDetach(const base::Value* option); Error* ParseExtensions(const base::Value* option); Error* ParseLoadAsync(const base::Value* option); + Error* ParseLoggingPrefs(const base::Value* option); Error* ParseNativeEvents(const base::Value* option); Error* ParseNoProxy(const base::Value* option); Error* ParseProfile(const base::Value* option); + Error* ParseProxy(const base::Value* option); Error* ParseProxyAutoDetect(const base::DictionaryValue* options); Error* ParseProxyAutoconfigUrl(const base::DictionaryValue* options); - Error* ParseProxyCapabilities(const base::DictionaryValue* options); Error* ParseProxyServers(const base::DictionaryValue* options); Error* ParseNoWebsiteTestingDefaults(const base::Value* option); - Error* ParseVerbose(const base::Value* option); // Decodes the given base64-encoded string, optionally unzips it, and // writes the result to |path|. // On error, false will be returned and |error_msg| will be set. @@ -111,6 +113,9 @@ class CapabilitiesParser { // The root directory under which to write all files. const FilePath root_; + // Reference to the logger to use. + const Logger& logger_; + // A pointer to the capabilities to modify while parsing. Capabilities* caps_; diff --git a/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc b/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc index 12ae069..085c0b9 100644 --- a/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc +++ b/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc @@ -21,7 +21,7 @@ namespace webdriver { TEST(CapabilitiesParser, NoCaps) { Capabilities caps; DictionaryValue dict; - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); } @@ -35,19 +35,17 @@ TEST(CapabilitiesParser, SimpleCaps) { options->SetBoolean("detach", true); options->SetBoolean("loadAsync", true); options->SetBoolean("nativeEvents", true); - options->SetBoolean("verbose", true); Capabilities caps; ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - CapabilitiesParser parser(&dict, temp_dir.path(), &caps); + CapabilitiesParser parser(&dict, temp_dir.path(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_EQ(FILE_PATH_LITERAL("binary"), caps.command.GetProgram().value()); EXPECT_STREQ("channel", caps.channel.c_str()); EXPECT_TRUE(caps.detach); EXPECT_TRUE(caps.load_async); EXPECT_TRUE(caps.native_events); - EXPECT_TRUE(caps.verbose); } TEST(CapabilitiesParser, Args) { @@ -64,7 +62,7 @@ TEST(CapabilitiesParser, Args) { Capabilities caps; ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - CapabilitiesParser parser(&dict, temp_dir.path(), &caps); + CapabilitiesParser parser(&dict, temp_dir.path(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_TRUE(caps.command.HasSwitch("arg1")); EXPECT_STREQ("val", caps.command.GetSwitchValueASCII("arg2").c_str()); @@ -84,7 +82,7 @@ TEST(CapabilitiesParser, Extensions) { Capabilities caps; ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - CapabilitiesParser parser(&dict, temp_dir.path(), &caps); + CapabilitiesParser parser(&dict, temp_dir.path(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); ASSERT_EQ(2u, caps.extensions.size()); std::string contents; @@ -115,7 +113,7 @@ TEST(CapabilitiesParser, Profile) { options->SetString("profile", base64); Capabilities caps; - CapabilitiesParser parser(&dict, temp_dir.path(), &caps); + CapabilitiesParser parser(&dict, temp_dir.path(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); std::string new_contents; ASSERT_TRUE(file_util::ReadFileToString( @@ -127,15 +125,7 @@ TEST(CapabilitiesParser, UnknownCap) { Capabilities caps; DictionaryValue dict; dict.SetString("chromeOptions.nosuchcap", "none"); - CapabilitiesParser parser(&dict, FilePath(), &caps); - ASSERT_TRUE(parser.Parse()); -} - -TEST(CapabilitiesParser, BadInput) { - Capabilities caps; - DictionaryValue dict; - dict.SetString("chromeOptions.verbose", "false"); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_TRUE(parser.Parse()); } @@ -149,7 +139,7 @@ TEST(CapabilitiesParser, ProxyCap) { options->SetString("proxyType", "PAC"); options->SetString("proxyAutoconfigUrl", kPacUrl); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_STREQ(kPacUrl, caps.command.GetSwitchValueASCII(switches::kProxyPacUrl).c_str()); @@ -164,7 +154,7 @@ TEST(CapabilitiesParser, ProxyTypeCapIncompatiblePac) { options->SetString("proxyType", "pac"); options->SetString("httpProxy", "http://localhost:8001"); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_TRUE(parser.Parse()); } @@ -176,7 +166,7 @@ TEST(CapabilitiesParser, ProxyTypeCapIncompatibleManual) { options->SetString("proxyType", "manual"); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_TRUE(parser.Parse()); } @@ -188,7 +178,7 @@ TEST(CapabilitiesParser, ProxyTypeCapNullValue) { options->Set("proxyType", Value::CreateNullValue()); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_TRUE(parser.Parse()); } @@ -203,7 +193,7 @@ TEST(CapabilitiesParser, ProxyTypeManualCap) { options->SetString("httpProxy", "localhost:8001"); options->SetString("ftpProxy", "localhost:9001"); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_STREQ(kProxyServers, caps.command.GetSwitchValueASCII(switches::kProxyServer).c_str()); @@ -219,7 +209,7 @@ TEST(CapabilitiesParser, ProxyBypassListCap) { options->SetString("proxyType", "manual"); options->SetString("noProxy", kBypassList); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_STREQ(kBypassList, caps.command.GetSwitchValueASCII(switches::kProxyBypassList).c_str()); @@ -235,7 +225,7 @@ TEST(CapabilitiesParser, ProxyBypassListCapNullValue) { options->Set("noProxy", Value::CreateNullValue()); options->SetString("httpProxy", "localhost:8001"); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_FALSE(caps.command.HasSwitch(switches::kProxyBypassList)); } @@ -249,7 +239,7 @@ TEST(CapabilitiesParser, UnknownProxyCap) { options->SetString("proxyType", "DIRECT"); options->SetString("badProxyCap", "error"); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); } @@ -263,7 +253,7 @@ TEST(CapabilitiesParser, ProxyFtpServerCapNullValue) { options->SetString("httpProxy", "localhost:8001"); options->Set("ftpProxy", Value::CreateNullValue()); - CapabilitiesParser parser(&dict, FilePath(), &caps); + CapabilitiesParser parser(&dict, FilePath(), Logger(), &caps); ASSERT_FALSE(parser.Parse()); EXPECT_STREQ("http=localhost:8001", caps.command.GetSwitchValueASCII(switches::kProxyServer).c_str()); diff --git a/chrome/test/webdriver/webdriver_dispatch.cc b/chrome/test/webdriver/webdriver_dispatch.cc index 42299a9..aa925ac 100644 --- a/chrome/test/webdriver/webdriver_dispatch.cc +++ b/chrome/test/webdriver/webdriver_dispatch.cc @@ -82,7 +82,7 @@ void DispatchCommand(Command* const command, } else { NOTREACHED(); } - command->Finish(); + command->Finish(response); } void SendOkWithBody(struct mg_connection* connection, @@ -114,7 +114,7 @@ void SendLog(struct mg_connection* connection, const struct mg_request_info* request_info, void* user_data) { std::string content, log; - if (GetLogContents(&log)) { + if (FileLog::Get()->GetLogContents(&log)) { content = "START ChromeDriver log"; const size_t kMaxSizeWithoutHeaders = kMaxHttpMessageSize - 10000; if (log.size() > kMaxSizeWithoutHeaders) { diff --git a/chrome/test/webdriver/webdriver_dispatch.h b/chrome/test/webdriver/webdriver_dispatch.h index a0cb40a..5480cde 100644 --- a/chrome/test/webdriver/webdriver_dispatch.h +++ b/chrome/test/webdriver/webdriver_dispatch.h @@ -9,7 +9,6 @@ #include <vector> #include "base/basictypes.h" -#include "base/logging.h" #include "chrome/test/webdriver/commands/response.h" #include "third_party/mongoose/mongoose.h" @@ -95,8 +94,6 @@ void Dispatch(struct mg_connection* connection, &path_segments, ¶meters, &response)) { - LOG(INFO) << "Received command, url: " << request_info->uri - << ", method: " << request_info->request_method; internal::DispatchHelper( new CommandType(path_segments, parameters), method, @@ -105,7 +102,6 @@ void Dispatch(struct mg_connection* connection, internal::SendResponse(connection, request_info->request_method, response); - LOG(INFO) << "Sent command response, url: " << request_info->uri; } class Dispatcher { diff --git a/chrome/test/webdriver/webdriver_element_id.cc b/chrome/test/webdriver/webdriver_element_id.cc index d618c07..db24d1d 100644 --- a/chrome/test/webdriver/webdriver_element_id.cc +++ b/chrome/test/webdriver/webdriver_element_id.cc @@ -4,7 +4,6 @@ #include "chrome/test/webdriver/webdriver_element_id.h" -#include "base/logging.h" #include "base/values.h" #include "chrome/test/automation/javascript_message_utils.h" diff --git a/chrome/test/webdriver/webdriver_key_converter.cc b/chrome/test/webdriver/webdriver_key_converter.cc index 33f9f390..53ee4ed 100644 --- a/chrome/test/webdriver/webdriver_key_converter.cc +++ b/chrome/test/webdriver/webdriver_key_converter.cc @@ -10,6 +10,7 @@ #include "chrome/common/automation_constants.h" #include "chrome/test/automation/automation_json_requests.h" #include "chrome/test/webdriver/keycode_text_conversion.h" +#include "chrome/test/webdriver/webdriver_logging.h" namespace { @@ -178,6 +179,7 @@ WebKeyEvent CreateCharEvent(const std::string& unmodified_text, } bool ConvertKeysToWebKeyEvents(const string16& client_keys, + const Logger& logger, std::vector<WebKeyEvent>* client_key_events, std::string* error_msg) { std::vector<WebKeyEvent> key_events; @@ -268,8 +270,10 @@ bool ConvertKeysToWebKeyEvents(const string16& client_keys, } if (unmodified_text.empty() || modified_text.empty()) { // Do a best effort and use the raw key we were given. - LOG(WARNING) << "No translation for key code. Code point: " - << static_cast<int>(key); + logger.Log( + kWarningLogLevel, + base::StringPrintf("No translation for key code. Code point: %d", + static_cast<int>(key))); if (unmodified_text.empty()) unmodified_text = UTF16ToUTF8(keys.substr(i, 1)); if (modified_text.empty()) diff --git a/chrome/test/webdriver/webdriver_key_converter.h b/chrome/test/webdriver/webdriver_key_converter.h index 940ec43..410fa96 100644 --- a/chrome/test/webdriver/webdriver_key_converter.h +++ b/chrome/test/webdriver/webdriver_key_converter.h @@ -15,6 +15,8 @@ namespace webdriver { +class Logger; + // Convenience functions for creating |WebKeyEvent|s. Used by unittests. WebKeyEvent CreateKeyDownEvent(ui::KeyboardCode key_code, int modifiers); WebKeyEvent CreateKeyUpEvent(ui::KeyboardCode key_code, int modifiers); @@ -26,6 +28,7 @@ WebKeyEvent CreateCharEvent(const std::string& unmodified_text, // conversion. However, if the input is invalid it will return false and set // an error message. bool ConvertKeysToWebKeyEvents(const string16& keys, + const Logger& logger, std::vector<WebKeyEvent>* key_events, std::string* error_msg); diff --git a/chrome/test/webdriver/webdriver_key_converter_unittest.cc b/chrome/test/webdriver/webdriver_key_converter_unittest.cc index 1fe3e5e..b0fdb77 100644 --- a/chrome/test/webdriver/webdriver_key_converter_unittest.cc +++ b/chrome/test/webdriver/webdriver_key_converter_unittest.cc @@ -19,7 +19,7 @@ void CheckEvents(const string16& keys, size_t expected_size) { std::vector<WebKeyEvent> events; std::string error_msg; - EXPECT_TRUE(ConvertKeysToWebKeyEvents(keys, &events, &error_msg)); + EXPECT_TRUE(ConvertKeysToWebKeyEvents(keys, Logger(), &events, &error_msg)); EXPECT_EQ(expected_size, events.size()); for (size_t i = 0; i < events.size() && i < expected_size; ++i) { EXPECT_EQ(expected_events[i].type, events[i].type); @@ -41,8 +41,8 @@ void CheckNonShiftChar(ui::KeyboardCode key_code, char character) { char_string.push_back(character); std::vector<WebKeyEvent> events; std::string error_msg; - EXPECT_TRUE(ConvertKeysToWebKeyEvents(ASCIIToUTF16(char_string), &events, - &error_msg)); + EXPECT_TRUE(ConvertKeysToWebKeyEvents(ASCIIToUTF16(char_string), Logger(), + &events, &error_msg)); ASSERT_EQ(3u, events.size()) << "Char: " << character; EXPECT_EQ(key_code, events[0].key_code) << "Char: " << character; ASSERT_EQ(1u, events[1].modified_text.length()) << "Char: " << character; @@ -57,8 +57,8 @@ void CheckShiftChar(ui::KeyboardCode key_code, char character, char lower) { char_string.push_back(character); std::vector<WebKeyEvent> events; std::string error_msg; - EXPECT_TRUE(ConvertKeysToWebKeyEvents(ASCIIToUTF16(char_string), &events, - &error_msg)); + EXPECT_TRUE(ConvertKeysToWebKeyEvents(ASCIIToUTF16(char_string), Logger(), + &events, &error_msg)); ASSERT_EQ(5u, events.size()) << "Char: " << character; EXPECT_EQ(ui::VKEY_SHIFT, events[0].key_code) << "Char: " << character; EXPECT_EQ(key_code, events[1].key_code) << "Char: " << character; @@ -281,11 +281,13 @@ TEST(WebDriverKeyConverter, AllSpecialWebDriverKeysOnEnglishKeyboard) { std::vector<WebKeyEvent> events; std::string error_msg; if (i == 1) { - EXPECT_FALSE(ConvertKeysToWebKeyEvents(keys, &events, &error_msg)) + EXPECT_FALSE(ConvertKeysToWebKeyEvents(keys, Logger(), &events, + &error_msg)) << "Index: " << i; EXPECT_EQ(0u, events.size()) << "Index: " << i; } else { - EXPECT_TRUE(ConvertKeysToWebKeyEvents(keys, &events, &error_msg)) + EXPECT_TRUE(ConvertKeysToWebKeyEvents(keys, Logger(), + &events, &error_msg)) << "Index: " << i; if (i == 0) { EXPECT_EQ(0u, events.size()) << "Index: " << i; diff --git a/chrome/test/webdriver/webdriver_logging.cc b/chrome/test/webdriver/webdriver_logging.cc index 2a0b3c5..e52d1ec 100644 --- a/chrome/test/webdriver/webdriver_logging.cc +++ b/chrome/test/webdriver/webdriver_logging.cc @@ -4,24 +4,194 @@ #include "chrome/test/webdriver/webdriver_logging.h" +#include <cmath> + #include "base/file_path.h" #include "base/file_util.h" #include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/time.h" + +using base::DictionaryValue; +using base::ListValue; +using base::Value; -namespace { +namespace webdriver { -// Path to the WebDriver log file. -const FilePath::CharType kLogPath[] = FILE_PATH_LITERAL("chromedriver.log"); +FileLog* FileLog::singleton_ = NULL; -} // namespace +double start_time = 0; -namespace webdriver { +// static +bool LogType::FromString(const std::string& name, LogType* log_type) { + if (name == "driver") { + *log_type = LogType(kDriver); + } else { + return false; + } + return true; +} + +LogType::LogType() : type_(kInvalid) { } + +LogType::LogType(Type type) : type_(type) { } + +LogType::~LogType() { } + +std::string LogType::ToString() const { + switch (type_) { + case kDriver: + return "driver"; + default: + return "unknown"; + }; +} + +LogType::Type LogType::type() const { + return type_; +} + +LogHandler::LogHandler() { } + +LogHandler::~LogHandler() { } + +// static +void FileLog::InitGlobalLog(LogLevel level) { + singleton_ = new FileLog(FilePath(FILE_PATH_LITERAL("chromedriver.log")), + level); +} + +// static +FileLog* FileLog::Get() { + return singleton_; +} + +FileLog::FileLog(const FilePath& path, LogLevel level) + : path_(path), + min_log_level_(level) { + file_.reset(file_util::OpenFile(path, "w")); +} + +FileLog::~FileLog() { } + +void FileLog::Log(LogLevel level, const base::Time& time, + const std::string& message) { + if (level < min_log_level_) + return; + + const char* level_name = "UNKNOWN"; + switch (level) { + case kSevereLogLevel: + level_name = "SEVERE"; + break; + case kWarningLogLevel: + level_name = "WARNING"; + break; + case kInfoLogLevel: + level_name = "INFO"; + break; + case kFineLogLevel: + level_name = "FINE"; + break; + case kFinerLogLevel: + level_name = "FINER"; + break; + default: + break; + } + base::TimeDelta delta(time - base::Time::UnixEpoch()); + std::string time_utc = base::Int64ToString(delta.InMilliseconds()); + std::string entry = base::StringPrintf( + "[%s][%s]:", time_utc.c_str(), level_name); -void InitWebDriverLogging(int min_log_level) { + int pad_length = 26 - entry.length(); + if (pad_length < 1) + pad_length = 1; + std::string padding(pad_length, ' '); + entry += base::StringPrintf( + "%s%s\n", padding.c_str(), message.c_str()); + + base::AutoLock auto_lock(lock_); +#if defined(OS_WIN) + SetFilePointer(file_.get(), 0, 0, SEEK_END); + DWORD num_written; + WriteFile(file_.get(), + static_cast<const void*>(entry.c_str()), + static_cast<DWORD>(entry.length()), + &num_written, + NULL); +#else + fprintf(file_.get(), "%s", entry.c_str()); + fflush(file_.get()); +#endif +} + +bool FileLog::GetLogContents(std::string* contents) { + return file_util::ReadFileToString(path_, contents); +} + +void FileLog::set_min_log_level(LogLevel level) { + min_log_level_ = level; +} + +InMemoryLog::InMemoryLog() { } + +InMemoryLog::~InMemoryLog() { } + +void InMemoryLog::Log(LogLevel level, const base::Time& time, + const std::string& message) { + // base's JSONWriter doesn't obey the spec, and writes + // doubles without a fraction with a '.0' postfix. base's Value class + // only includes doubles or int types. Instead of returning the timestamp + // in unix epoch time, we return it based on the start of chromedriver so + // a 32-bit int doesn't overflow. + // TODO(kkania): Add int64_t to base Values or fix the JSONWriter/Reader and + // use unix epoch time. + base::TimeDelta delta(time - base::Time::FromDoubleT(start_time)); + DictionaryValue* entry = new DictionaryValue(); + entry->SetInteger("level", level); + entry->SetInteger("timestamp", delta.InMilliseconds()); + entry->SetString("message", message); + base::AutoLock auto_lock(entries_lock_); + entries_list_.Append(entry); +} + +const ListValue* InMemoryLog::entries_list() const { + return &entries_list_; +} + +Logger::Logger() : min_log_level_(kAllLogLevel) { } + +Logger::Logger(LogLevel level) : min_log_level_(level) { } + +Logger::~Logger() { } + +void Logger::Log(LogLevel level, const std::string& message) const { + if (level < min_log_level_) + return; + + base::Time time = base::Time::Now(); + for (size_t i = 0; i < handlers_.size(); ++i) { + handlers_[i]->Log(level, time, message); + } +} + +void Logger::AddHandler(LogHandler* log_handler) { + handlers_.push_back(log_handler); +} + +void Logger::set_min_log_level(LogLevel level) { + min_log_level_ = level; +} + +void InitWebDriverLogging(LogLevel min_log_level) { + start_time = base::Time::Now().ToDoubleT(); + // Turn off base/logging. bool success = InitLogging( - kLogPath, - logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG, - logging::LOCK_LOG_FILE, + NULL, + logging::LOG_NONE, + logging::DONT_LOCK_LOG_FILE, logging::DELETE_OLD_LOG_FILE, logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); if (!success) { @@ -31,11 +201,9 @@ void InitWebDriverLogging(int min_log_level) { false, // enable_thread_id true, // enable_timestamp false); // enable_tickcount - logging::SetMinLogLevel(min_log_level); -} -bool GetLogContents(std::string* log_contents) { - return file_util::ReadFileToString(FilePath(kLogPath), log_contents); + // Init global file log. + FileLog::InitGlobalLog(min_log_level); } } // namespace webdriver diff --git a/chrome/test/webdriver/webdriver_logging.h b/chrome/test/webdriver/webdriver_logging.h index 2b8ab5e..e10ec1e 100644 --- a/chrome/test/webdriver/webdriver_logging.h +++ b/chrome/test/webdriver/webdriver_logging.h @@ -6,16 +6,129 @@ #define CHROME_TEST_WEBDRIVER_WEBDRIVER_LOGGING_H_ #include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "base/values.h" + +namespace base { +class Time; +} namespace webdriver { -// Initializes logging for WebDriver. All logging below the given level -// will be discarded. -void InitWebDriverLogging(int min_log_level); +// WebDriver logging levels. +enum LogLevel { + kSevereLogLevel = 1000, + kWarningLogLevel = 900, + kInfoLogLevel = 800, + kFineLogLevel = 500, + kFinerLogLevel = 400, + kAllLogLevel = -1000 +}; + +// Represents a type/source of a WebDriver log. +class LogType { + public: + enum Type { + kInvalid = -1, + kDriver, + kNum // must be correct + }; + + static bool FromString(const std::string& name, LogType* log_type); + + LogType(); + explicit LogType(Type type); + ~LogType(); + + std::string ToString() const; + Type type() const; + + private: + Type type_; +}; + +class LogHandler { + public: + LogHandler(); + virtual ~LogHandler(); + + virtual void Log(LogLevel level, const base::Time& time, + const std::string& message) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(LogHandler); +}; + +class FileLog : public LogHandler { + public: + static void InitGlobalLog(LogLevel level); + static FileLog* Get(); + + FileLog(const FilePath& path, LogLevel level); + virtual ~FileLog(); + + virtual void Log(LogLevel level, const base::Time& time, + const std::string& message) OVERRIDE; + + bool GetLogContents(std::string* contents); -// Retrieves the current contents of the WebDriver log file. -// Returns true on success. -bool GetLogContents(std::string* log_contents); + void set_min_log_level(LogLevel level); + + private: + static FileLog* singleton_; + + FilePath path_; + file_util::ScopedFILE file_; + base::Lock lock_; + + LogLevel min_log_level_; + + DISALLOW_COPY_AND_ASSIGN(FileLog); +}; + +class InMemoryLog : public LogHandler { + public: + InMemoryLog(); + virtual ~InMemoryLog(); + + virtual void Log(LogLevel level, const base::Time& time, + const std::string& message) OVERRIDE; + + const base::ListValue* entries_list() const; + + private: + base::ListValue entries_list_; + base::Lock entries_lock_; + + DISALLOW_COPY_AND_ASSIGN(InMemoryLog); +}; + +// Forwards logging messages to added logs. +class Logger { + public: + Logger(); + explicit Logger(LogLevel level); + ~Logger(); + + void Log(LogLevel level, const std::string& message) const; + void AddHandler(LogHandler* log_handler); + + void set_min_log_level(LogLevel level); + + private: + std::vector<LogHandler*> handlers_; + LogLevel min_log_level_; +}; + +// Initializes logging for WebDriver. All logging below the given level +// will be discarded in the global file log. +void InitWebDriverLogging(LogLevel min_log_level); } // namespace webdriver diff --git a/chrome/test/webdriver/webdriver_server.cc b/chrome/test/webdriver/webdriver_server.cc index 22e6cf2..b5c692d 100644 --- a/chrome/test/webdriver/webdriver_server.cc +++ b/chrome/test/webdriver/webdriver_server.cc @@ -16,7 +16,6 @@ #include "base/command_line.h" #include "base/file_path.h" #include "base/file_util.h" -#include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/string_number_conversions.h" #include "base/string_split.h" @@ -24,6 +23,7 @@ #include "base/synchronization/waitable_event.h" #include "base/test/test_timeouts.h" #include "base/threading/platform_thread.h" +#include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" @@ -37,6 +37,7 @@ #include "chrome/test/webdriver/commands/execute_async_script_command.h" #include "chrome/test/webdriver/commands/execute_command.h" #include "chrome/test/webdriver/commands/find_element_commands.h" +#include "chrome/test/webdriver/commands/log_command.h" #include "chrome/test/webdriver/commands/navigate_commands.h" #include "chrome/test/webdriver/commands/mouse_commands.h" #include "chrome/test/webdriver/commands/screenshot_command.h" @@ -64,6 +65,8 @@ namespace webdriver { +namespace { + void InitCallbacks(Dispatcher* dispatcher, base::WaitableEvent* shutdown_event, bool forbid_other_requests) { @@ -134,6 +137,7 @@ void InitCallbacks(Dispatcher* dispatcher, dispatcher->Add<SetAsyncScriptTimeoutCommand>( "/session/*/timeouts/async_script"); dispatcher->Add<ImplicitWaitCommand>( "/session/*/timeouts/implicit_wait"); + dispatcher->Add<LogCommand>( "/session/*/log"); // Cookie functions. dispatcher->Add<CookieCommand>( "/session/*/cookie"); @@ -155,17 +159,13 @@ void InitCallbacks(Dispatcher* dispatcher, dispatcher->ForbidAllOtherRequests(); } -} // namespace webdriver - -namespace { - void* ProcessHttpRequest(mg_event event_raised, struct mg_connection* connection, const struct mg_request_info* request_info) { bool handler_result_code = false; if (event_raised == MG_NEW_REQUEST) { handler_result_code = - reinterpret_cast<webdriver::Dispatcher*>(request_info->user_data)-> + reinterpret_cast<Dispatcher*>(request_info->user_data)-> ProcessHttpRequest(connection, request_info); } @@ -191,13 +191,9 @@ void MakeMongooseOptions(const std::string& port, } // namespace -// Sets up and runs the Mongoose HTTP server for the JSON over HTTP -// protcol of webdriver. The spec is located at: -// http://code.google.com/p/selenium/wiki/JsonWireProtocol. -int main(int argc, char *argv[]) { +int RunChromeDriver() { base::AtExitManager exit; base::WaitableEvent shutdown_event(false, false); - CommandLine::Init(argc, argv); CommandLine* cmd_line = CommandLine::ForCurrentProcess(); #if defined(OS_POSIX) @@ -210,11 +206,15 @@ int main(int argc, char *argv[]) { chrome::RegisterPathProvider(); TestTimeouts::Initialize(); + InitWebDriverLogging(kAllLogLevel); + FileLog::Get()->Log(kInfoLogLevel, + base::Time::Now(), + std::string("ChromeDriver ") + chrome::kChromeVersion); + // Parse command line flags. std::string port = "9515"; std::string root; std::string url_base; - bool verbose = false; int http_threads = 4; bool enable_keep_alive = true; if (cmd_line->HasSwitch("port")) @@ -226,9 +226,6 @@ int main(int argc, char *argv[]) { root = cmd_line->GetSwitchValueASCII("root"); if (cmd_line->HasSwitch("url-base")) url_base = cmd_line->GetSwitchValueASCII("url-base"); - // Whether or not to do verbose logging. - if (cmd_line->HasSwitch("verbose")) - verbose = true; if (cmd_line->HasSwitch("http-threads")) { if (!base::StringToInt(cmd_line->GetSwitchValueASCII("http-threads"), &http_threads)) { @@ -239,15 +236,12 @@ int main(int argc, char *argv[]) { if (cmd_line->HasSwitch("disable-keep-alive")) enable_keep_alive = false; - webdriver::InitWebDriverLogging( - verbose ? logging::LOG_INFO : logging::LOG_WARNING); - - webdriver::SessionManager* manager = webdriver::SessionManager::GetInstance(); + SessionManager* manager = SessionManager::GetInstance(); manager->set_port(port); manager->set_url_base(url_base); - webdriver::Dispatcher dispatcher(url_base); - webdriver::InitCallbacks(&dispatcher, &shutdown_event, root.empty()); + Dispatcher dispatcher(url_base); + InitCallbacks(&dispatcher, &shutdown_event, root.empty()); std::vector<std::string> args; MakeMongooseOptions(port, root, http_threads, enable_keep_alive, &args); @@ -284,3 +278,10 @@ int main(int argc, char *argv[]) { return (EXIT_SUCCESS); } + +} // namespace webdriver + +int main(int argc, char *argv[]) { + CommandLine::Init(argc, argv); + webdriver::RunChromeDriver(); +} diff --git a/chrome/test/webdriver/webdriver_session.cc b/chrome/test/webdriver/webdriver_session.cc index fd02cf7..92c3a08 100644 --- a/chrome/test/webdriver/webdriver_session.cc +++ b/chrome/test/webdriver/webdriver_session.cc @@ -14,7 +14,6 @@ #include "base/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" -#include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop_proxy.h" #include "base/process.h" @@ -35,8 +34,10 @@ #include "chrome/common/chrome_switches.h" #include "chrome/test/automation/automation_json_requests.h" #include "chrome/test/automation/value_conversion_util.h" +#include "chrome/test/webdriver/webdriver_capabilities_parser.h" #include "chrome/test/webdriver/webdriver_error.h" #include "chrome/test/webdriver/webdriver_key_converter.h" +#include "chrome/test/webdriver/webdriver_logging.h" #include "chrome/test/webdriver/webdriver_session_manager.h" #include "chrome/test/webdriver/webdriver_util.h" #include "third_party/webdriver/atoms.h" @@ -50,43 +51,58 @@ FrameId::FrameId(const WebViewId& view_id, const FramePath& frame_path) frame_path(frame_path) { } -Session::Options::Options() - : use_native_events(false), - load_async(false), - no_website_testing_defaults(false) { -} - -Session::Options::~Options() { -} - -Session::Session(const Options& options) - : id_(GenerateRandomID()), +Session::Session() + : session_log_(new InMemoryLog()), + logger_(kAllLogLevel), + id_(GenerateRandomID()), current_target_(FrameId(WebViewId(), FramePath())), thread_(id_.c_str()), async_script_timeout_(0), implicit_wait_(0), - has_alert_prompt_text_(false), - options_(options) { + has_alert_prompt_text_(false) { SessionManager::GetInstance()->Add(this); + logger_.AddHandler(session_log_.get()); + if (FileLog::Get()) + logger_.AddHandler(FileLog::Get()); } Session::~Session() { SessionManager::GetInstance()->Remove(id_); } -Error* Session::Init(const Automation::BrowserOptions& options) { +Error* Session::Init(const DictionaryValue* capabilities_dict) { if (!thread_.Start()) { delete this; return new Error(kUnknownError, "Cannot start session thread"); } + ScopedTempDir temp_dir; + if (!temp_dir.CreateUniqueTempDir()) { + delete this; + return new Error( + kUnknownError, "Unable to create temp directory for unpacking"); + } + logger_.Log(kFineLogLevel, + "Initializing session with capabilities " + + JsonStringifyForDisplay(capabilities_dict)); + CapabilitiesParser parser( + capabilities_dict, temp_dir.path(), logger_, &capabilities_); + Error* error = parser.Parse(); + if (error) { + delete this; + return error; + } + logger_.set_min_log_level(capabilities_.log_levels[LogType::kDriver]); - Error* error = NULL; + Automation::BrowserOptions browser_options; + browser_options.command = capabilities_.command; + browser_options.channel_id = capabilities_.channel; + browser_options.detach_process = capabilities_.detach; + browser_options.user_data_dir = capabilities_.profile; RunSessionTask(base::Bind( &Session::InitOnSessionThread, base::Unretained(this), - options, + browser_options, &error)); - if (!error) error = PostBrowserStartInit(); @@ -114,10 +130,8 @@ Error* Session::BeforeExecuteCommand() { Error* Session::AfterExecuteCommand() { Error* error = NULL; - if (!options_.load_async) { - LOG(INFO) << "Waiting for the page to stop loading"; + if (!capabilities_.load_async) { error = WaitForAllViewsToStopLoading(); - LOG(INFO) << "Done waiting for the page to stop loading"; } return error; } @@ -291,7 +305,7 @@ Error* Session::NavigateToURL(const std::string& url) { "The current target does not support navigation"); } Error* error = NULL; - if (options_.load_async) { + if (capabilities_.load_async) { RunSessionTask(base::Bind( &Automation::NavigateToURLAsync, base::Unretained(automation_.get()), @@ -1020,11 +1034,14 @@ Error* Session::GetAttribute(const ElementId& element, Error* Session::WaitForAllViewsToStopLoading() { if (!automation_.get()) return NULL; + + logger_.Log(kFinerLogLevel, "Waiting for all views to stop loading..."); Error* error = NULL; RunSessionTask(base::Bind( &Automation::WaitForAllViewsToStopLoading, base::Unretained(automation_.get()), &error)); + logger_.Log(kFinerLogLevel, "Done waiting for all views to stop loading"); return error; } @@ -1137,6 +1154,10 @@ Error* Session::SetPreference( return error; } +base::ListValue* Session::GetLog() const { + return session_log_->entries_list()->DeepCopy(); +} + const std::string& Session::id() const { return id_; } @@ -1165,8 +1186,12 @@ const Point& Session::get_mouse_position() const { return mouse_position_; } -const Session::Options& Session::options() const { - return options_; +const Logger& Session::logger() const { + return logger_; +} + +const Capabilities& Session::capabilities() const { + return capabilities_; } void Session::RunSessionTask(Task* task) { @@ -1204,7 +1229,7 @@ void Session::RunClosureOnSessionThread(const base::Closure& task, void Session::InitOnSessionThread(const Automation::BrowserOptions& options, Error** error) { - automation_.reset(new Automation()); + automation_.reset(new Automation(logger_)); automation_->Init(options, error); if (*error) return; @@ -1279,12 +1304,12 @@ Error* Session::ExecuteScriptAndParseValue(const FrameId& frame_id, void Session::SendKeysOnSessionThread(const string16& keys, Error** error) { std::vector<WebKeyEvent> key_events; std::string error_msg; - if (!ConvertKeysToWebKeyEvents(keys, &key_events, &error_msg)) { + if (!ConvertKeysToWebKeyEvents(keys, logger_, &key_events, &error_msg)) { *error = new Error(kUnknownError, error_msg); return; } for (size_t i = 0; i < key_events.size(); ++i) { - if (options_.use_native_events) { + if (capabilities_.native_events) { // The automation provider will generate up/down events for us, we // only need to call it once as compared to the WebKeyEvent method. // Hence we filter events by their types, keeping only rawkeydown. @@ -1511,7 +1536,7 @@ Error* Session::VerifyElementIsClickable( return new Error(kUnknownError, message); } if (message.length()) { - LOG(WARNING) << message; + logger_.Log(kWarningLogLevel, message); } return NULL; } @@ -1588,14 +1613,14 @@ Error* Session::GetAppCacheStatus(int* status) { Error* Session::PostBrowserStartInit() { Error* error = NULL; - if (!options_.no_website_testing_defaults) + if (!capabilities_.no_website_testing_defaults) error = InitForWebsiteTesting(); if (error) return error; // Install extensions. - for (size_t i = 0; i < options_.extensions.size(); ++i) { - error = InstallExtensionDeprecated(options_.extensions[i]); + for (size_t i = 0; i < capabilities_.extensions.size(); ++i) { + error = InstallExtensionDeprecated(capabilities_.extensions[i]); if (error) return error; } diff --git a/chrome/test/webdriver/webdriver_session.h b/chrome/test/webdriver/webdriver_session.h index d2ea02f..834d2f4 100644 --- a/chrome/test/webdriver/webdriver_session.h +++ b/chrome/test/webdriver/webdriver_session.h @@ -19,7 +19,9 @@ #include "chrome/test/webdriver/frame_path.h" #include "chrome/test/webdriver/webdriver_automation.h" #include "chrome/test/webdriver/webdriver_basic_types.h" +#include "chrome/test/webdriver/webdriver_capabilities_parser.h" #include "chrome/test/webdriver/webdriver_element_id.h" +#include "chrome/test/webdriver/webdriver_logging.h" class FilePath; @@ -51,43 +53,15 @@ struct FrameId { // A session manages its own lifetime. class Session { public: - struct Options { - Options(); - ~Options(); - - // True if the session should simulate OS-level input. Currently only - // applies to keyboard input. - bool use_native_events; - - // True if the session should not wait for page loads and navigate - // asynchronously. - bool load_async; - - // By default, ChromeDriver configures Chrome in such a way as convenient - // for website testing. E.g., it configures Chrome so that sites are allowed - // to use the geolocation API without requesting the user's consent. - // If this is set to true, ChromeDriver will not modify Chrome's default - // behavior. - bool no_website_testing_defaults; - - // A list of extensions to install on startup. - std::vector<FilePath> extensions; - }; - // Adds this |Session| to the |SessionManager|. The session manages its own - // lifetime. Do not call delete. - explicit Session(const Options& options); + // lifetime. Call |Terminate|, not delete, if you need to quit. + Session(); // Removes this |Session| from the |SessionManager|. ~Session(); - // Starts the session thread and a new browser, using the exe found at - // |browser_exe| and duplicating the provided |user_data_dir|. - // If |browser_exe| is empty, it will search in all the default locations. - // It |user_data_dir| is empty, it will use a temporary dir. - // Returns true on success. On failure, the session will delete - // itself and return an error code. - Error* Init(const Automation::BrowserOptions& options); + // Initializes the session with the given capabilities. + Error* Init(const base::DictionaryValue* capabilities_dict); // Should be called before executing a command. Error* BeforeExecuteCommand(); @@ -347,6 +321,10 @@ class Session { bool is_user_pref, base::Value* value); + // Returns a copy of the current log entries. Caller is responsible for + // returned value. + base::ListValue* GetLog() const; + const std::string& id() const; const FrameId& current_target() const; @@ -359,7 +337,9 @@ class Session { const Point& get_mouse_position() const; - const Options& options() const; + const Logger& logger() const; + + const Capabilities& capabilities() const; // Gets the browser connection state. Error* GetBrowserConnectionState(bool* online); @@ -418,6 +398,9 @@ class Session { Error* PostBrowserStartInit(); Error* InitForWebsiteTesting(); + scoped_ptr<InMemoryLog> session_log_; + Logger logger_; + const std::string id_; FrameId current_target_; @@ -446,7 +429,7 @@ class Session { std::string alert_prompt_text_; bool has_alert_prompt_text_; - Options options_; + Capabilities capabilities_; DISALLOW_COPY_AND_ASSIGN(Session); }; |