Commit 711de0ef authored by ernesto.mudu's avatar ernesto.mudu Committed by Commit bot

Add webdriver endpoint to send custom debugger commands

Since its introduction in Canary I became a big fan of the CSS rule usage
tracker and I'm looking forward to its introduction in Chrome stable.

I also thought it would be a great idea to run it periodically as part of the
e2e tests at work to collect the (un)used CSS rules instead of during manual
testing.

I then decided to implement a POC [1] to run the CSS tracker with Protractor.
This of course required some changes in Chromedriver, Protractor and WebDriver
JS Extender.
In particular, I had to change the Chromedriver so that I could send the
commands to start and stop the CSS rule usage tracking
(CSS.startRuleUsageTracking/CSS.stopRuleUsageTracking) to the remote debugger.

I thought of 3 possible implementations:
1) as part of the performance logging [2];
2) as a new endpoint of the Chromedriver to start/stop the CSS rule usage
tracking;
3) as a new endpoint of the Chromedriver to send any command directly to the
debugger, hence opening up the road to a whole series of tools able to send
commands directly to the debugger through the Chromedriver.

Of these 3 options, I implemented option 2 and 3 in this change.

If this change is acceptable, I'll proceed with the tests and fixing the linter issues.

Thanks,

EM.

[1] https://github.com/ventuno/css-usage-recorder/tree/ftr-css-recording
[2]
https://sites.google.com/a/chromium.org/chromedriver/logging/performance-log

BUG=

Review-Url: https://codereview.chromium.org/2743013002
Cr-Commit-Position: refs/heads/master@{#469275}
parent c2fe0046
......@@ -836,6 +836,7 @@ Debug Wang <debugwang@tencent.com>
Zeqin Chen <talonchen@tencent.com>
Yong Wang <ccyongwang@tencent.com>
Charles Vaughn <cvaughn@gmail.com>
Ernesto Mudu <ernesto.mudu@gmail.com>
ACCESS CO., LTD. <*@access-company.com>
BlackBerry Limited <*@blackberry.com>
......
......@@ -234,6 +234,8 @@ source_set("lib") {
"command_listener_proxy.h",
"commands.cc",
"commands.h",
"devtools_events_logger.cc",
"devtools_events_logger.h",
"element_commands.cc",
"element_commands.h",
"element_util.cc",
......
......@@ -400,6 +400,18 @@ Status ParsePerfLoggingPrefs(const base::Value& option,
return Status(kOk);
}
Status ParseDevToolsEventsLoggingPrefs(const base::Value& option,
Capabilities* capabilities) {
const base::ListValue* devtools_events_logging_prefs = nullptr;
if (!option.GetAsList(&devtools_events_logging_prefs))
return Status(kUnknownError, "must be a list");
if (devtools_events_logging_prefs->empty())
return Status(kUnknownError, "list must contain values");
capabilities->devtools_events_logging_prefs.reset(
devtools_events_logging_prefs->DeepCopy());
return Status(kOk);
}
Status ParseWindowTypes(const base::Value& option, Capabilities* capabilities) {
const base::ListValue* window_types = NULL;
if (!option.GetAsList(&window_types))
......@@ -438,6 +450,8 @@ Status ParseChromeOptions(
parser_map["extensions"] = base::Bind(&IgnoreCapability);
parser_map["perfLoggingPrefs"] = base::Bind(&ParsePerfLoggingPrefs);
parser_map["devToolsEventsToLog"] = base::Bind(
&ParseDevToolsEventsLoggingPrefs);
parser_map["windowTypes"] = base::Bind(&ParseWindowTypes);
// Compliance is read when session is initialized and correct response is
// sent if not parsed correctly.
......@@ -666,5 +680,16 @@ Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
"but performance logging was not enabled");
}
}
LoggingPrefs::const_iterator dt_events_logging_iter = logging_prefs.find(
WebDriverLog::kDevToolsType);
if (dt_events_logging_iter == logging_prefs.end()
|| dt_events_logging_iter->second == Log::kOff) {
const base::DictionaryValue* chrome_options = NULL;
if (desired_caps.GetDictionary("chromeOptions", &chrome_options) &&
chrome_options->HasKey("devToolsEventsToLog")) {
return Status(kUnknownError, "devToolsEventsToLog specified, "
"but devtools events logging was not enabled");
}
}
return Status(kOk);
}
......@@ -24,6 +24,7 @@
namespace base {
class CommandLine;
class DictionaryValue;
class ListValue;
}
class Status;
......@@ -150,6 +151,8 @@ struct Capabilities {
PerfLoggingPrefs perf_logging_prefs;
std::unique_ptr<base::ListValue> devtools_events_logging_prefs;
std::unique_ptr<base::DictionaryValue> prefs;
Switches switches;
......
......@@ -40,6 +40,18 @@ Status StubWebView::Reload(const Timeout* timeout) {
return Status(kOk);
}
Status StubWebView::SendCommand(const std::string& cmd,
const base::DictionaryValue& params) {
return Status(kOk);
}
Status StubWebView::SendCommandAndGetResult(
const std::string& cmd,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
return Status(kOk);
}
Status StubWebView::TraverseHistory(int delta, const Timeout* timeout) {
return Status(kOk);
}
......
......@@ -25,6 +25,11 @@ class StubWebView : public WebView {
Status GetUrl(std::string* url) override;
Status Load(const std::string& url, const Timeout* timeout) override;
Status Reload(const Timeout* timeout) override;
Status SendCommand(const std::string& cmd,
const base::DictionaryValue& params) override;
Status SendCommandAndGetResult(const std::string& cmd,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) override;
Status TraverseHistory(int delta, const Timeout* timeout) override;
Status EvaluateScript(const std::string& frame,
const std::string& function,
......
......@@ -53,6 +53,16 @@ class WebView {
// Reload the current page.
virtual Status Reload(const Timeout* timeout) = 0;
// Send a command to the DevTools debugger
virtual Status SendCommand(const std::string& cmd,
const base::DictionaryValue& params) = 0;
// Send a command to the DevTools debugger and wait for the result
virtual Status SendCommandAndGetResult(
const std::string& cmd,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) = 0;
// Navigate |delta| steps forward in the browser history. A negative value
// will navigate back in the history. If the delta exceeds the number of items
// in the browser history, stay on the current page.
......
......@@ -206,6 +206,23 @@ Status WebViewImpl::Reload(const Timeout* timeout) {
return client_->SendCommandWithTimeout("Page.reload", params, timeout);
}
Status WebViewImpl::SendCommand(const std::string& cmd,
const base::DictionaryValue& params) {
return client_->SendCommand(cmd, params);
}
Status WebViewImpl::SendCommandAndGetResult(
const std::string& cmd,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::unique_ptr<base::DictionaryValue> result;
Status status = client_->SendCommandAndGetResult(cmd, params, &result);
if (status.IsError())
return status;
*value = std::move(result);
return Status(kOk);
}
Status WebViewImpl::TraverseHistory(int delta, const Timeout* timeout) {
base::DictionaryValue params;
std::unique_ptr<base::DictionaryValue> result;
......
......@@ -52,6 +52,11 @@ class WebViewImpl : public WebView {
Status GetUrl(std::string* url) override;
Status Load(const std::string& url, const Timeout* timeout) override;
Status Reload(const Timeout* timeout) override;
Status SendCommand(const std::string& cmd,
const base::DictionaryValue& params) override;
Status SendCommandAndGetResult(const std::string& cmd,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) override;
Status TraverseHistory(int delta, const Timeout* timeout) override;
Status EvaluateScript(const std::string& frame,
const std::string& expression,
......
......@@ -114,7 +114,8 @@ class ChromeDriver(object):
mobile_emulation=None, experimental_options=None,
download_dir=None, network_connection=None,
send_w3c_capability=None, send_w3c_request=None,
page_load_strategy=None, unexpected_alert_behaviour=None):
page_load_strategy=None, unexpected_alert_behaviour=None,
devtools_events_to_log=None):
self._executor = command_executor.CommandExecutor(server_url)
options = {}
......@@ -164,7 +165,8 @@ class ChromeDriver(object):
if logging_prefs:
assert type(logging_prefs) is dict
log_types = ['client', 'driver', 'browser', 'server', 'performance']
log_types = ['client', 'driver', 'browser', 'server', 'performance',
'devtools']
log_levels = ['ALL', 'DEBUG', 'INFO', 'WARNING', 'SEVERE', 'OFF']
for log_type, log_level in logging_prefs.iteritems():
assert log_type in log_types
......@@ -172,6 +174,10 @@ class ChromeDriver(object):
else:
logging_prefs = {}
if devtools_events_to_log:
assert type(devtools_events_to_log) is list
options['devToolsEventsToLog'] = devtools_events_to_log
download_prefs = {}
if download_dir:
if 'prefs' not in options:
......@@ -495,6 +501,14 @@ class ChromeDriver(object):
params = {'parameters': {'type': connection_type}}
return self.ExecuteCommand(Command.SET_NETWORK_CONNECTION, params)
def SendCommand(self, cmd, cmd_params):
params = {'parameters': {'cmd': cmd, 'params': cmd_params}};
return self.ExecuteCommand(Command.SEND_COMMAND, params)
def SendCommandAndGetResult(self, cmd, cmd_params):
params = {'cmd': cmd, 'params': cmd_params};
return self.ExecuteCommand(Command.SEND_COMMAND_AND_GET_RESULT, params)
def GetScreenOrientation(self):
screen_orientation = self.ExecuteCommand(Command.GET_SCREEN_ORIENTATION)
return {
......
......@@ -152,6 +152,10 @@ class Command(object):
STATUS = (_Method.GET, '/status')
SET_NETWORK_CONNECTION = (
_Method.POST, '/session/:sessionId/network_connection')
SEND_COMMAND = (
_Method.POST, '/session/:sessionId/chromium/send_command')
SEND_COMMAND_AND_GET_RESULT = (
_Method.POST, '/session/:sessionId/chromium/send_command_and_get_result')
# Custom Chrome commands.
IS_LOADING = (_Method.GET, '/session/:sessionId/is_loading')
......
// Copyright 2017 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/chromedriver/devtools_events_logger.h"
#include "base/json/json_writer.h"
#include "chrome/test/chromedriver/chrome/devtools_client.h"
#include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
DevToolsEventsLogger::DevToolsEventsLogger(Log* log,
const base::ListValue* prefs)
: log_(log),
prefs_(prefs) {}
inline DevToolsEventsLogger::~DevToolsEventsLogger() {}
Status DevToolsEventsLogger::OnConnected(DevToolsClient* client) {
for (base::ListValue::const_iterator it = prefs_->begin();
it != prefs_->end();
++it) {
std::string event;
it->GetAsString(&event);
events_.insert(event);
}
return Status(kOk);
}
Status DevToolsEventsLogger::OnEvent(DevToolsClient* client,
const std::string& method,
const base::DictionaryValue& params) {
std::unordered_set<std::string>::iterator it = events_.find(method);
if (it != events_.end()) {
base::DictionaryValue log_message_dict;
log_message_dict.SetString("method", method);
log_message_dict.Set("params", params.DeepCopy());
std::string log_message_json;
base::JSONWriter::Write(log_message_dict, &log_message_json);
log_->AddEntry(Log::kInfo, log_message_json);
}
return Status(kOk);
}
// Copyright 2017 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_CHROMEDRIVER_DEVTOOLS_EVENTS_LOGGER_H_
#define CHROME_TEST_CHROMEDRIVER_DEVTOOLS_EVENTS_LOGGER_H_
#include <string>
#include <unordered_set>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
#include "chrome/test/chromedriver/chrome/log.h"
#include "chrome/test/chromedriver/chrome/status.h"
// Collects DevTools events into Log messages with info level.
//
// The message is a JSON string of the following structure:
// {
// "message": { "method": "...", "params": { ... }} // DevTools message.
// }
class DevToolsEventsLogger : public DevToolsEventListener {
public:
// Creates a |DevToolsEventsLogger| with specific preferences.
DevToolsEventsLogger(Log* log, const base::ListValue* prefs);
~DevToolsEventsLogger() override;
Status OnConnected(DevToolsClient* client) override;
Status OnEvent(DevToolsClient* client,
const std::string& method,
const base::DictionaryValue& params) override;
private:
Log* log_; // The log where to create entries.
const base::ListValue* prefs_;
std::unordered_set<std::string> events_;
DISALLOW_COPY_AND_ASSIGN(DevToolsEventsLogger);
};
#endif // CHROME_TEST_CHROMEDRIVER_DEVTOOLS_EVENTS_LOGGER_H_
......@@ -23,6 +23,7 @@
#include "chrome/test/chromedriver/chrome/console_logger.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/command_listener_proxy.h"
#include "chrome/test/chromedriver/devtools_events_logger.h"
#include "chrome/test/chromedriver/performance_logger.h"
#include "chrome/test/chromedriver/session.h"
......@@ -131,6 +132,7 @@ bool HandleLogMessage(int severity,
const char WebDriverLog::kBrowserType[] = "browser";
const char WebDriverLog::kDriverType[] = "driver";
const char WebDriverLog::kPerformanceType[] = "performance";
const char WebDriverLog::kDevToolsType[] = "devtools";
bool WebDriverLog::NameToLevel(const std::string& name, Log::Level* out_level) {
for (size_t i = 0; i < arraysize(kNameToLevel); ++i) {
......@@ -304,6 +306,12 @@ Status CreateLogs(
command_listeners.push_back(
base::MakeUnique<CommandListenerProxy>(perf_log));
}
} else if (type == WebDriverLog::kDevToolsType) {
logs.push_back(base::MakeUnique<WebDriverLog>(type, Log::kAll));
devtools_listeners.push_back(
base::MakeUnique<DevToolsEventsLogger>(
logs.back().get(),
capabilities.devtools_events_logging_prefs.get()));
} else if (type == WebDriverLog::kBrowserType) {
browser_log_level = level;
} else if (type != WebDriverLog::kDriverType) {
......
......@@ -32,6 +32,7 @@ class WebDriverLog : public Log {
static const char kBrowserType[];
static const char kDriverType[];
static const char kPerformanceType[];
static const char kDevToolsType[];
// Converts WD wire protocol level name -> Level, false on bad name.
static bool NameToLevel(const std::string& name, Level* out_level);
......
......@@ -612,6 +612,15 @@ HttpHandler::HttpHandler(
"session/:sessionId/touch/pinch",
WrapToCommand("TouchPinch",
base::Bind(&ExecuteTouchPinch))),
CommandMapping(kPost,
"session/:sessionId/chromium/send_command",
WrapToCommand("SendCommand",
base::Bind(&ExecuteSendCommand))),
CommandMapping(
kPost,
"session/:sessionId/chromium/send_command_and_get_result",
WrapToCommand("SendCommandAndGetResult",
base::Bind(&ExecuteSendCommandAndGetResult))),
};
command_map_.reset(
new CommandMap(commands, commands + arraysize(commands)));
......
......@@ -175,7 +175,7 @@ _ANDROID_NEGATIVE_FILTER['chromium'] = (
)
_ANDROID_NEGATIVE_FILTER['chromedriver_webview_shell'] = (
_ANDROID_NEGATIVE_FILTER['chrome'] + [
'PerformanceLoggerTest.testPerformanceLogger',
'ChromeLoggingCapabilityTest.testPerformanceLogger',
'ChromeDriverTest.testShadowDom*',
# WebView doesn't support emulating network conditions.
'ChromeDriverTest.testEmulateNetworkConditions',
......@@ -1062,6 +1062,25 @@ class ChromeDriverTest(ChromeDriverBaseTestWithWebServer):
# navigation tracker to block the call to Load() above.
self.WaitForCondition(lambda: 'is not available' in self._driver.GetTitle())
def testSendCommand(self):
"""Sends a custom command to the DevTools debugger"""
params = {}
res = self._driver.SendCommandAndGetResult('CSS.enable', params)
self.assertEqual({}, res)
def testSendCommandNoParams(self):
"""Sends a custom command to the DevTools debugger without params"""
self.assertRaisesRegexp(
chromedriver.UnknownError, "params not passed",
self._driver.SendCommandAndGetResult, 'CSS.enable', None)
def testSendCommandAndGetResult(self):
"""Sends a custom command to the DevTools debugger and gets the result"""
self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html'))
params = {}
document = self._driver.SendCommandAndGetResult('DOM.getDocument', params)
self.assertTrue('root' in document)
def testShadowDomFindElementWithSlashDeep(self):
"""Checks that chromedriver can find elements in a shadow DOM using /deep/
css selectors."""
......@@ -2217,7 +2236,7 @@ class ChromeDriverLogTest(ChromeDriverBaseTest):
self.assertNotIn('bosco', f.read())
class PerformanceLoggerTest(ChromeDriverBaseTest):
class ChromeLoggingCapabilityTest(ChromeDriverBaseTest):
"""Tests chromedriver tracing support and Inspector event collection."""
def testPerformanceLogger(self):
......@@ -2252,6 +2271,18 @@ class PerformanceLoggerTest(ChromeDriverBaseTest):
self.assertEquals({'Network', 'Page', 'Tracing'},
set(seen_log_domains.keys()))
def testDevToolsEventsLogger(self):
"""Tests that the correct event type (and no other) is logged"""
event = 'Page.loadEventFired'
driver = self.CreateDriver(
devtools_events_to_log=[event], logging_prefs={'devtools':'ALL'})
driver.Load('about:blank')
logs = driver.GetLog('devtools')
for entry in logs:
devtools_message = json.loads(entry['message'])
method = devtools_message['method']
self.assertTrue('params' in devtools_message)
self.assertEquals(event, method)
class SessionHandlingTest(ChromeDriverBaseTest):
"""Tests for session operations."""
......
......@@ -715,6 +715,38 @@ Status ExecuteTouchPinch(Session* session,
return web_view->SynthesizePinchGesture(location.x, location.y, scale_factor);
}
Status ExecuteSendCommand(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string cmd;
if (!params.GetString("cmd", &cmd)) {
return Status(kUnknownError, "command not passed");
}
const base::DictionaryValue* cmdParams;
if (!params.GetDictionary("params", &cmdParams)) {
return Status(kUnknownError, "params not passed");
}
return web_view->SendCommand(cmd, *cmdParams);
}
Status ExecuteSendCommandAndGetResult(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string cmd;
if (!params.GetString("cmd", &cmd)) {
return Status(kUnknownError, "command not passed");
}
const base::DictionaryValue* cmdParams;
if (!params.GetDictionary("params", &cmdParams)) {
return Status(kUnknownError, "params not passed");
}
return web_view->SendCommandAndGetResult(cmd, *cmdParams, value);
}
Status ExecuteGetActiveElement(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
......
......@@ -194,6 +194,18 @@ Status ExecuteTouchPinch(Session* session,
std::unique_ptr<base::Value>* value,
Timeout* timeout);
Status ExecuteSendCommand(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout);
Status ExecuteSendCommandAndGetResult(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout);
Status ExecuteGetActiveElement(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment