diff options
author | srawlins@google.com <srawlins@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-21 20:04:49 +0000 |
---|---|---|
committer | srawlins@google.com <srawlins@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-21 20:04:49 +0000 |
commit | d682978b9c077121cb8f9d7c65604ea9199438e2 (patch) | |
tree | 90bc4375cdff462291674de6d8881a62929032b0 | |
parent | 612f95c234dc405a9194b42b74f870f75a89d99b (diff) | |
download | chromium_src-d682978b9c077121cb8f9d7c65604ea9199438e2.zip chromium_src-d682978b9c077121cb8f9d7c65604ea9199438e2.tar.gz chromium_src-d682978b9c077121cb8f9d7c65604ea9199438e2.tar.bz2 |
[Chromedriver] Add Device Metrics override support to ChromeDriver via Capabilities
BUG=chromedriver:399
All of the code reviews for this patch were written against https://codereview.chromium.org/251933005/ . I used the wrong email address to create said patch, so I have created this one with the correct email address.
Review URL: https://codereview.chromium.org/288193004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271947 0039d316-1c4b-4281-b951-d872f2087c98
30 files changed, 762 insertions, 18 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 94ea1e0..6dcc50b 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -402,6 +402,8 @@ '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/user_data_dir.h', '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/embedded_automation_extension.cc', '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/embedded_automation_extension.h', + '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/mobile_device_list.cc', + '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/mobile_device_list.h', 'test/chromedriver/chrome/adb.h', 'test/chromedriver/chrome/adb_impl.cc', 'test/chromedriver/chrome/adb_impl.h', @@ -425,6 +427,8 @@ 'test/chromedriver/chrome/debugger_tracker.h', 'test/chromedriver/chrome/device_manager.cc', 'test/chromedriver/chrome/device_manager.h', + 'test/chromedriver/chrome/device_metrics.cc', + 'test/chromedriver/chrome/device_metrics.h', 'test/chromedriver/chrome/devtools_client.h', 'test/chromedriver/chrome/devtools_client_impl.cc', 'test/chromedriver/chrome/devtools_client_impl.h', @@ -445,6 +449,10 @@ 'test/chromedriver/chrome/javascript_dialog_manager.h', 'test/chromedriver/chrome/log.h', 'test/chromedriver/chrome/log.cc', + 'test/chromedriver/chrome/mobile_device.cc', + 'test/chromedriver/chrome/mobile_device.h', + 'test/chromedriver/chrome/mobile_emulation_override_manager.cc', + 'test/chromedriver/chrome/mobile_emulation_override_manager.h', 'test/chromedriver/chrome/navigation_tracker.cc', 'test/chromedriver/chrome/navigation_tracker.h', 'test/chromedriver/chrome/performance_logger.h', @@ -550,6 +558,25 @@ ], 'message': 'Generating sources for embedding automation extension', }, + { + 'action_name': 'embed_mobile_devices_in_cpp', + 'inputs': [ + 'test/chromedriver/cpp_source.py', + 'test/chromedriver/embed_mobile_devices_in_cpp.py', + '../third_party/WebKit/Source/devtools/front_end/elements/OverridesView.js', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/mobile_device_list.cc', + '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome/mobile_device_list.h', + ], + 'action': [ 'python', + 'test/chromedriver/embed_mobile_devices_in_cpp.py', + '--directory', + '<(SHARED_INTERMEDIATE_DIR)/chrome/test/chromedriver/chrome', + '../third_party/WebKit/Source/devtools/front_end/elements/OverridesView.js', + ], + 'message': 'Generating sources for embedding mobile devices in chromedriver', + }, ], # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. 'msvs_disabled_warnings': [ 4267, ], @@ -699,6 +726,7 @@ 'test/chromedriver/chrome/geolocation_override_manager_unittest.cc', 'test/chromedriver/chrome/heap_snapshot_taker_unittest.cc', 'test/chromedriver/chrome/javascript_dialog_manager_unittest.cc', + 'test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc', 'test/chromedriver/chrome/navigation_tracker_unittest.cc', 'test/chromedriver/chrome/performance_logger_unittest.cc', 'test/chromedriver/chrome/status_unittest.cc', diff --git a/chrome/test/chromedriver/capabilities.cc b/chrome/test/chromedriver/capabilities.cc index 518af52..b6fb0a5 100644 --- a/chrome/test/chromedriver/capabilities.cc +++ b/chrome/test/chromedriver/capabilities.cc @@ -17,6 +17,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" +#include "chrome/test/chromedriver/chrome/mobile_device.h" #include "chrome/test/chromedriver/chrome/status.h" #include "chrome/test/chromedriver/logging.h" #include "net/base/net_util.h" @@ -84,6 +85,70 @@ Status ParseLogPath(const base::Value& option, Capabilities* capabilities) { return Status(kOk); } +Status ParseDeviceName(std::string device_name, Capabilities* capabilities) { + scoped_ptr<MobileDevice> device; + Status status = FindMobileDevice(device_name, &device); + + if (status.IsError()) { + return Status(kUnknownError, + "'" + device_name + "' must be a valid device", + status); + } + + capabilities->device_metrics.reset(device->device_metrics.release()); + capabilities->switches.SetSwitch("user-agent", device->user_agent); + + return Status(kOk); +} + +Status ParseMobileEmulation(const base::Value& option, + Capabilities* capabilities) { + const base::DictionaryValue* mobile_emulation; + if (!option.GetAsDictionary(&mobile_emulation)) + return Status(kUnknownError, "'mobileEmulation' must be a dictionary"); + + if (mobile_emulation->HasKey("deviceName")) { + // Cannot use any other options with deviceName. + if (mobile_emulation->size() > 1) + return Status(kUnknownError, "'deviceName' must be used alone"); + + std::string device_name; + if (!mobile_emulation->GetString("deviceName", &device_name)) + return Status(kUnknownError, "'deviceName' must be a string"); + + return ParseDeviceName(device_name, capabilities); + } + + if (mobile_emulation->HasKey("deviceMetrics")) { + const base::DictionaryValue* metrics; + if (!mobile_emulation->GetDictionary("deviceMetrics", &metrics)) + return Status(kUnknownError, "'deviceMetrics' must be a dictionary"); + + int width; + int height; + double device_scale_factor; + if (!metrics->GetInteger("width", &width) || + !metrics->GetInteger("height", &height) || + !metrics->GetDouble("pixelRatio", &device_scale_factor)) + return Status(kUnknownError, "invalid 'deviceMetrics'"); + + DeviceMetrics* device_metrics = + new DeviceMetrics(width, height, device_scale_factor); + capabilities->device_metrics = + scoped_ptr<DeviceMetrics>(device_metrics); + } + + if (mobile_emulation->HasKey("userAgent")) { + std::string user_agent; + if (!mobile_emulation->GetString("userAgent", &user_agent)) + return Status(kUnknownError, "'userAgent' must be a string"); + + capabilities->switches.SetSwitch("user-agent", user_agent); + } + + return Status(kOk); +} + Status ParseSwitches(const base::Value& option, Capabilities* capabilities) { const base::ListValue* switches_list = NULL; @@ -272,6 +337,7 @@ Status ParseChromeOptions( parser_map["args"] = base::Bind(&ParseSwitches); parser_map["binary"] = base::Bind(&ParseFilePath, &capabilities->binary); parser_map["detach"] = base::Bind(&ParseBoolean, &capabilities->detach); + parser_map["mobileEmulation"] = base::Bind(&ParseMobileEmulation); parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches); parser_map["extensions"] = base::Bind(&ParseExtensions); parser_map["forceDevToolsScreenshot"] = base::Bind( diff --git a/chrome/test/chromedriver/capabilities.h b/chrome/test/chromedriver/capabilities.h index 998c56a..c21c045 100644 --- a/chrome/test/chromedriver/capabilities.h +++ b/chrome/test/chromedriver/capabilities.h @@ -14,6 +14,7 @@ #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" +#include "chrome/test/chromedriver/chrome/device_metrics.h" #include "chrome/test/chromedriver/chrome/log.h" #include "chrome/test/chromedriver/net/net_util.h" @@ -91,6 +92,9 @@ struct Capabilities { // ChromeDriver dies. bool detach; + // Device metrics for use in Device Emulation. + scoped_ptr<DeviceMetrics> device_metrics; + // Set of switches which should be removed from default list when launching // Chrome. std::set<std::string> exclude_switches; diff --git a/chrome/test/chromedriver/capabilities_unittest.cc b/chrome/test/chromedriver/capabilities_unittest.cc index f4b313e..ea1e87c 100644 --- a/chrome/test/chromedriver/capabilities_unittest.cc +++ b/chrome/test/chromedriver/capabilities_unittest.cc @@ -361,3 +361,98 @@ TEST(ParseCapabilities, UseExistingBrowser) { ASSERT_EQ("abc", capabilities.debugger_address.host()); ASSERT_EQ(123, capabilities.debugger_address.port()); } + +TEST(ParseCapabilities, MobileEmulationUserAgent) { + Capabilities capabilities; + base::DictionaryValue mobile_emulation; + mobile_emulation.SetString("userAgent", "Agent Smith"); + base::DictionaryValue caps; + caps.Set("chromeOptions.mobileEmulation", mobile_emulation.DeepCopy()); + Status status = capabilities.Parse(caps); + ASSERT_TRUE(status.IsOk()); + + ASSERT_EQ(1u, capabilities.switches.GetSize()); + ASSERT_TRUE(capabilities.switches.HasSwitch("user-agent")); + ASSERT_EQ("Agent Smith", capabilities.switches.GetSwitchValue("user-agent")); +} + +TEST(ParseCapabilities, MobileEmulationDeviceMetrics) { + Capabilities capabilities; + base::DictionaryValue mobile_emulation; + mobile_emulation.SetInteger("deviceMetrics.width", 360); + mobile_emulation.SetInteger("deviceMetrics.height", 640); + mobile_emulation.SetDouble("deviceMetrics.pixelRatio", 3.0); + base::DictionaryValue caps; + caps.Set("chromeOptions.mobileEmulation", mobile_emulation.DeepCopy()); + Status status = capabilities.Parse(caps); + ASSERT_TRUE(status.IsOk()); + + ASSERT_EQ(360, capabilities.device_metrics->width); + ASSERT_EQ(640, capabilities.device_metrics->height); + ASSERT_EQ(3.0, capabilities.device_metrics->device_scale_factor); +} + +TEST(ParseCapabilities, MobileEmulationDeviceName) { + Capabilities capabilities; + base::DictionaryValue mobile_emulation; + mobile_emulation.SetString("deviceName", "Google Nexus 5"); + base::DictionaryValue caps; + caps.Set("chromeOptions.mobileEmulation", mobile_emulation.DeepCopy()); + Status status = capabilities.Parse(caps); + ASSERT_TRUE(status.IsOk()); + + ASSERT_EQ(1u, capabilities.switches.GetSize()); + ASSERT_TRUE(capabilities.switches.HasSwitch("user-agent")); + ASSERT_EQ( + "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) " + "AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 " + "Mobile Safari/535.19", + capabilities.switches.GetSwitchValue("user-agent")); + + ASSERT_EQ(360, capabilities.device_metrics->width); + ASSERT_EQ(640, capabilities.device_metrics->height); + ASSERT_EQ(3.0, capabilities.device_metrics->device_scale_factor); +} + +TEST(ParseCapabilities, MobileEmulationNotDict) { + Capabilities capabilities; + base::DictionaryValue caps; + caps.SetString("chromeOptions.mobileEmulation", "Google Nexus 5"); + Status status = capabilities.Parse(caps); + ASSERT_FALSE(status.IsOk()); +} + +TEST(ParseCapabilities, MobileEmulationDeviceMetricsNotDict) { + Capabilities capabilities; + base::DictionaryValue mobile_emulation; + mobile_emulation.SetInteger("deviceMetrics", 360); + base::DictionaryValue caps; + caps.Set("chromeOptions.mobileEmulation", mobile_emulation.DeepCopy()); + Status status = capabilities.Parse(caps); + ASSERT_FALSE(status.IsOk()); +} + +TEST(ParseCapabilities, MobileEmulationDeviceMetricsNotNumbers) { + Capabilities capabilities; + base::DictionaryValue mobile_emulation; + mobile_emulation.SetString("deviceMetrics.width", "360"); + mobile_emulation.SetString("deviceMetrics.height", "640"); + mobile_emulation.SetString("deviceMetrics.pixelRatio", "3.0"); + base::DictionaryValue caps; + caps.Set("chromeOptions.mobileEmulation", mobile_emulation.DeepCopy()); + Status status = capabilities.Parse(caps); + ASSERT_FALSE(status.IsOk()); +} + +TEST(ParseCapabilities, MobileEmulationBadDict) { + Capabilities capabilities; + base::DictionaryValue mobile_emulation; + mobile_emulation.SetString("deviceName", "Google Nexus 5"); + mobile_emulation.SetInteger("deviceMetrics.width", 360); + mobile_emulation.SetInteger("deviceMetrics.height", 640); + mobile_emulation.SetDouble("deviceMetrics.pixelRatio", 3.0); + base::DictionaryValue caps; + caps.Set("chromeOptions.mobileEmulation", mobile_emulation.DeepCopy()); + Status status = capabilities.Parse(caps); + ASSERT_FALSE(status.IsOk()); +} diff --git a/chrome/test/chromedriver/chrome/chrome.h b/chrome/test/chromedriver/chrome/chrome.h index 56885bc..277c58d 100644 --- a/chrome/test/chromedriver/chrome/chrome.h +++ b/chrome/test/chromedriver/chrome/chrome.h @@ -40,6 +40,9 @@ class Chrome { // Get the operation system where Chrome is running. virtual std::string GetOperatingSystemName() = 0; + // Return whether the mobileEmulation capability has been enabled. + virtual bool IsMobileEmulationEnabled() const = 0; + // Quits Chrome. virtual Status Quit() = 0; }; diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc index 8cdd130..d737249 100644 --- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc +++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc @@ -117,7 +117,8 @@ Status ChromeDesktopImpl::WaitForPageToLoad(const std::string& url, scoped_ptr<WebView> web_view_tmp( new WebViewImpl(id, devtools_http_client_->browser_info(), - devtools_http_client_->CreateClient(id))); + devtools_http_client_->CreateClient(id), + devtools_http_client_->device_metrics())); Status status = web_view_tmp->ConnectIfNecessary(); if (status.IsError()) return status; @@ -155,6 +156,10 @@ std::string ChromeDesktopImpl::GetOperatingSystemName() { return base::SysInfo::OperatingSystemName(); } +bool ChromeDesktopImpl::IsMobileEmulationEnabled() const { + return devtools_http_client_->device_metrics() != NULL; +} + Status ChromeDesktopImpl::QuitImpl() { if (!KillProcess(process_)) return Status(kUnknownError, "cannot kill Chrome"); diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h index c8e86a7..d448419 100644 --- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h +++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h @@ -49,6 +49,7 @@ class ChromeDesktopImpl : public ChromeImpl { virtual std::string GetOperatingSystemName() OVERRIDE; // Overridden from ChromeImpl: + virtual bool IsMobileEmulationEnabled() const OVERRIDE; virtual Status QuitImpl() OVERRIDE; const base::CommandLine& command() const; diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc index f0b87d6..7f796d2 100644 --- a/chrome/test/chromedriver/chrome/chrome_impl.cc +++ b/chrome/test/chromedriver/chrome/chrome_impl.cc @@ -83,7 +83,10 @@ Status ChromeImpl::GetWebViewIds(std::list<std::string>* web_view_ids) { // OnConnected will fire when DevToolsClient connects later. } web_views_.push_back(make_linked_ptr(new WebViewImpl( - view.id, devtools_http_client_->browser_info(), client.Pass()))); + view.id, + devtools_http_client_->browser_info(), + client.Pass(), + devtools_http_client_->device_metrics()))); } } @@ -125,6 +128,10 @@ Status ChromeImpl::ActivateWebView(const std::string& id) { return devtools_http_client_->ActivateWebView(id); } +bool ChromeImpl::IsMobileEmulationEnabled() const { + return false; +} + Status ChromeImpl::Quit() { Status status = QuitImpl(); if (status.IsOk()) diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h index 01ad596..d852351 100644 --- a/chrome/test/chromedriver/chrome/chrome_impl.h +++ b/chrome/test/chromedriver/chrome/chrome_impl.h @@ -37,6 +37,7 @@ class ChromeImpl : public Chrome { WebView** web_view) OVERRIDE; virtual Status CloseWebView(const std::string& id) OVERRIDE; virtual Status ActivateWebView(const std::string& id) OVERRIDE; + virtual bool IsMobileEmulationEnabled() const OVERRIDE; virtual Status Quit() OVERRIDE; protected: diff --git a/chrome/test/chromedriver/chrome/device_metrics.cc b/chrome/test/chromedriver/chrome/device_metrics.cc new file mode 100644 index 0000000..059e55d --- /dev/null +++ b/chrome/test/chromedriver/chrome/device_metrics.cc @@ -0,0 +1,16 @@ +// Copyright 2014 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/chrome/device_metrics.h" + +DeviceMetrics::DeviceMetrics(int width, int height, double device_scale_factor) + : width(width), + height(height), + device_scale_factor(device_scale_factor), + emulate_viewport(false), + fit_window(true), + text_autosizing(true), + font_scale_factor(1) {} + +DeviceMetrics::~DeviceMetrics() {} diff --git a/chrome/test/chromedriver/chrome/device_metrics.h b/chrome/test/chromedriver/chrome/device_metrics.h new file mode 100644 index 0000000..d40159b --- /dev/null +++ b/chrome/test/chromedriver/chrome/device_metrics.h @@ -0,0 +1,21 @@ +// Copyright 2014 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_CHROME_DEVICE_METRICS_H_ +#define CHROME_TEST_CHROMEDRIVER_CHROME_DEVICE_METRICS_H_ + +struct DeviceMetrics { + DeviceMetrics(int width, int height, double device_scale_factor); + ~DeviceMetrics(); + + int width; + int height; + double device_scale_factor; + bool emulate_viewport; + bool fit_window; + bool text_autosizing; + double font_scale_factor; +}; + +#endif // CHROME_TEST_CHROMEDRIVER_CHROME_DEVICE_METRICS_H_ diff --git a/chrome/test/chromedriver/chrome/devtools_http_client.cc b/chrome/test/chromedriver/chrome/devtools_http_client.cc index 2d380a8..99ff945 100644 --- a/chrome/test/chromedriver/chrome/devtools_http_client.cc +++ b/chrome/test/chromedriver/chrome/devtools_http_client.cc @@ -13,6 +13,7 @@ #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/values.h" +#include "chrome/test/chromedriver/chrome/device_metrics.h" #include "chrome/test/chromedriver/chrome/devtools_client_impl.h" #include "chrome/test/chromedriver/chrome/log.h" #include "chrome/test/chromedriver/chrome/status.h" @@ -67,12 +68,14 @@ const WebViewInfo* WebViewsInfo::GetForId(const std::string& id) const { DevToolsHttpClient::DevToolsHttpClient( const NetAddress& address, scoped_refptr<URLRequestContextGetter> context_getter, - const SyncWebSocketFactory& socket_factory) + const SyncWebSocketFactory& socket_factory, + scoped_ptr<DeviceMetrics> device_metrics) : context_getter_(context_getter), socket_factory_(socket_factory), server_url_("http://" + address.ToString()), web_socket_url_prefix_(base::StringPrintf( - "ws://%s/devtools/page/", address.ToString().c_str())) {} + "ws://%s/devtools/page/", address.ToString().c_str())), + device_metrics_(device_metrics.Pass()) {} DevToolsHttpClient::~DevToolsHttpClient() {} @@ -196,6 +199,10 @@ const BrowserInfo* DevToolsHttpClient::browser_info() { return &browser_info_; } +const DeviceMetrics* DevToolsHttpClient::device_metrics() { + return device_metrics_.get(); +} + Status DevToolsHttpClient::GetVersion(std::string* browser_version, std::string* blink_version) { std::string data; @@ -248,7 +255,7 @@ Status DevToolsHttpClient::CloseFrontends(const std::string& for_client_id) { *it, base::Bind(&FakeCloseFrontends))); scoped_ptr<WebViewImpl> web_view( - new WebViewImpl(*it, &browser_info_, client.Pass())); + new WebViewImpl(*it, &browser_info_, client.Pass(), NULL)); status = web_view->ConnectIfNecessary(); // Ignore disconnected error, because the debugger might have closed when diff --git a/chrome/test/chromedriver/chrome/devtools_http_client.h b/chrome/test/chromedriver/chrome/devtools_http_client.h index 693f0c0..1e1c5eb 100644 --- a/chrome/test/chromedriver/chrome/devtools_http_client.h +++ b/chrome/test/chromedriver/chrome/devtools_http_client.h @@ -17,6 +17,7 @@ namespace base { class TimeDelta; } +struct DeviceMetrics; class DevToolsClient; class NetAddress; class Status; @@ -64,7 +65,8 @@ class DevToolsHttpClient { DevToolsHttpClient( const NetAddress& address, scoped_refptr<URLRequestContextGetter> context_getter, - const SyncWebSocketFactory& socket_factory); + const SyncWebSocketFactory& socket_factory, + scoped_ptr<DeviceMetrics> device_metrics); ~DevToolsHttpClient(); Status Init(const base::TimeDelta& timeout); @@ -78,6 +80,7 @@ class DevToolsHttpClient { Status ActivateWebView(const std::string& id); const BrowserInfo* browser_info(); + const DeviceMetrics* device_metrics(); private: Status GetVersion(std::string* browser_version, std::string* blink_version); @@ -91,6 +94,7 @@ class DevToolsHttpClient { std::string server_url_; std::string web_socket_url_prefix_; BrowserInfo browser_info_; + scoped_ptr<DeviceMetrics> device_metrics_; DISALLOW_COPY_AND_ASSIGN(DevToolsHttpClient); }; diff --git a/chrome/test/chromedriver/chrome/mobile_device.cc b/chrome/test/chromedriver/chrome/mobile_device.cc new file mode 100644 index 0000000..fe6b4d1 --- /dev/null +++ b/chrome/test/chromedriver/chrome/mobile_device.cc @@ -0,0 +1,87 @@ +// Copyright 2014 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 "base/json/json_reader.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/values.h" +#include "chrome/test/chromedriver/chrome/mobile_device.h" +#include "chrome/test/chromedriver/chrome/mobile_device_list.h" +#include "chrome/test/chromedriver/chrome/status.h" + +MobileDevice::MobileDevice() {} +MobileDevice::~MobileDevice() {} + +Status FindMobileDevice(std::string device_name, + scoped_ptr<MobileDevice>* mobile_device) { + base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS); + scoped_ptr<base::Value> devices_value; + devices_value.reset(json_reader.ReadToValue(kMobileDevices)); + if (!devices_value.get()) + return Status(kUnknownError, + "could not parse mobile device list because " + + json_reader.GetErrorMessage()); + + base::ListValue* mobile_devices; + if (!devices_value->GetAsList(&mobile_devices)) + return Status(kUnknownError, "malformed device metrics list"); + + for (base::ListValue::iterator it = mobile_devices->begin(); + it != mobile_devices->end(); + ++it) { + base::ListValue* device = NULL; + if (!(*it)->GetAsList(&device)) { + return Status(kUnknownError, + "malformed device in list: should be an array"); + } + + if (device != NULL) { + std::string name; + if (!device->GetString(0, &name)) { + return Status(kUnknownError, + "malformed device name: should be a string"); + } + if (name != device_name) + continue; + + scoped_ptr<MobileDevice> tmp_mobile_device(new MobileDevice()); + std::string device_metrics_string; + if (!device->GetString(1, &tmp_mobile_device->user_agent)) { + return Status(kUnknownError, + "malformed device user agent: should be a string"); + } + if (!device->GetString(2, &device_metrics_string)) { + return Status(kUnknownError, + "malformed device metrics: should be a string"); + } + std::vector<std::string> metrics_vector; + base::SplitString(device_metrics_string, 'x', &metrics_vector); + if (metrics_vector.size() < 3) + return Status(kUnknownError, "malformed device metrics string"); + + int width = 0; + int height = 0; + double device_scale_factor = 0.0; + if (!base::StringToInt(metrics_vector[0], &width)) { + return Status(kUnknownError, + "malformed device width: should be an integer"); + } + if (!base::StringToInt(metrics_vector[1], &height)) { + return Status(kUnknownError, + "malformed device height: should be an integer"); + } + if (!base::StringToDouble(metrics_vector[2], &device_scale_factor)) { + return Status(kUnknownError, + "malformed device scale factor: should be a double"); + } + tmp_mobile_device->device_metrics.reset( + new DeviceMetrics(width, height, device_scale_factor)); + + *mobile_device = tmp_mobile_device.Pass(); + return Status(kOk); + } + } + + return Status(kUnknownError, "must be a valid device"); +} diff --git a/chrome/test/chromedriver/chrome/mobile_device.h b/chrome/test/chromedriver/chrome/mobile_device.h new file mode 100644 index 0000000..fbb91eb --- /dev/null +++ b/chrome/test/chromedriver/chrome/mobile_device.h @@ -0,0 +1,24 @@ +// Copyright 2014 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_CHROME_MOBILE_DEVICE_H_ +#define CHROME_TEST_CHROMEDRIVER_CHROME_MOBILE_DEVICE_H_ + +#include <string> +#include "base/memory/scoped_ptr.h" +#include "chrome/test/chromedriver/chrome/device_metrics.h" + +class Status; + +struct MobileDevice { + MobileDevice(); + ~MobileDevice(); + scoped_ptr<DeviceMetrics> device_metrics; + std::string user_agent; +}; + +Status FindMobileDevice(std::string device_name, + scoped_ptr<MobileDevice>* mobile_device); + +#endif // CHROME_TEST_CHROMEDRIVER_CHROME_MOBILE_DEVICE_H_ diff --git a/chrome/test/chromedriver/chrome/mobile_emulation_override_manager.cc b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager.cc new file mode 100644 index 0000000..4a69c66 --- /dev/null +++ b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager.cc @@ -0,0 +1,55 @@ +// Copyright 2014 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 "base/values.h" +#include "chrome/test/chromedriver/chrome/device_metrics.h" +#include "chrome/test/chromedriver/chrome/devtools_client.h" +#include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h" +#include "chrome/test/chromedriver/chrome/status.h" + +MobileEmulationOverrideManager::MobileEmulationOverrideManager( + DevToolsClient* client, + const DeviceMetrics* device_metrics) + : client_(client), overridden_device_metrics_(device_metrics) { + if (overridden_device_metrics_) + client_->AddListener(this); +} + +MobileEmulationOverrideManager::~MobileEmulationOverrideManager() { +} + +Status MobileEmulationOverrideManager::OnConnected(DevToolsClient* client) { + return ApplyOverrideIfNeeded(); +} + +Status MobileEmulationOverrideManager::OnEvent( + DevToolsClient* client, + const std::string& method, + const base::DictionaryValue& params) { + if (method == "Page.frameNavigated") { + const base::Value* unused_value; + if (!params.Get("frame.parentId", &unused_value)) + return ApplyOverrideIfNeeded(); + } + return Status(kOk); +} + +Status MobileEmulationOverrideManager::ApplyOverrideIfNeeded() { + if (overridden_device_metrics_ == NULL) + return Status(kOk); + + base::DictionaryValue params; + params.SetInteger("width", overridden_device_metrics_->width); + params.SetInteger("height", overridden_device_metrics_->height); + params.SetDouble("deviceScaleFactor", + overridden_device_metrics_->device_scale_factor); + params.SetBoolean("emulateViewport", + overridden_device_metrics_->emulate_viewport); + params.SetBoolean("fitWindow", overridden_device_metrics_->fit_window); + params.SetBoolean("textAutosizing", + overridden_device_metrics_->text_autosizing); + params.SetDouble("fontScaleFactor", + overridden_device_metrics_->font_scale_factor); + return client_->SendCommand("Page.setDeviceMetricsOverride", params); +} diff --git a/chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h new file mode 100644 index 0000000..4c08f44 --- /dev/null +++ b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h @@ -0,0 +1,46 @@ +// Copyright 2014 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_CHROME_MOBILE_EMULATION_OVERRIDE_MANAGER_H_ +#define CHROME_TEST_CHROMEDRIVER_CHROME_MOBILE_EMULATION_OVERRIDE_MANAGER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/test/chromedriver/chrome/devtools_event_listener.h" + +namespace base { +class DictionaryValue; +} + +class DevToolsClient; +struct DeviceMetrics; +class Status; + +// Overrides the device metrics, if requested, for the duration of the +// given |DevToolsClient|'s lifetime. +class MobileEmulationOverrideManager : public DevToolsEventListener { + public: + MobileEmulationOverrideManager(DevToolsClient* client, + const DeviceMetrics* device_metrics); + virtual ~MobileEmulationOverrideManager(); + + // Overridden from DevToolsEventListener: + virtual Status OnConnected(DevToolsClient* client) OVERRIDE; + virtual Status OnEvent(DevToolsClient* client, + const std::string& method, + const base::DictionaryValue& params) OVERRIDE; + + private: + Status ApplyOverrideIfNeeded(); + + DevToolsClient* client_; + const DeviceMetrics* overridden_device_metrics_; + + DISALLOW_COPY_AND_ASSIGN(MobileEmulationOverrideManager); +}; + +#endif // CHROME_TEST_CHROMEDRIVER_CHROME_MOBILE_EMULATION_OVERRIDE_MANAGER_H_ diff --git a/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc new file mode 100644 index 0000000..a318783 --- /dev/null +++ b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc @@ -0,0 +1,117 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/values.h" +#include "chrome/test/chromedriver/chrome/device_metrics.h" +#include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h" +#include "chrome/test/chromedriver/chrome/status.h" +#include "chrome/test/chromedriver/chrome/stub_devtools_client.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +struct Command { + Command() {} + Command(const std::string& method, const base::DictionaryValue& params) + : method(method) { + this->params.MergeDictionary(¶ms); + } + Command(const Command& command) { + *this = command; + } + Command& operator=(const Command& command) { + method = command.method; + params.Clear(); + params.MergeDictionary(&command.params); + return *this; + } + ~Command() {} + + std::string method; + base::DictionaryValue params; +}; + +class RecorderDevToolsClient : public StubDevToolsClient { + public: + RecorderDevToolsClient() {} + virtual ~RecorderDevToolsClient() {} + + // Overridden from StubDevToolsClient: + virtual Status SendCommandAndGetResult( + const std::string& method, + const base::DictionaryValue& params, + scoped_ptr<base::DictionaryValue>* result) OVERRIDE { + commands_.push_back(Command(method, params)); + return Status(kOk); + } + + std::vector<Command> commands_; +}; + +void AssertDeviceMetricsCommand(const Command& command, + const DeviceMetrics& device_metrics) { + ASSERT_EQ("Page.setDeviceMetricsOverride", command.method); + int width, height; + double device_scale_factor, font_scale_factor; + bool emulate_viewport, fit_window, text_autosizing; + ASSERT_TRUE(command.params.GetInteger("width", &width)); + ASSERT_TRUE(command.params.GetInteger("height", &height)); + ASSERT_TRUE(command.params.GetDouble("deviceScaleFactor", + &device_scale_factor)); + ASSERT_TRUE(command.params.GetBoolean("emulateViewport", &emulate_viewport)); + ASSERT_TRUE(command.params.GetBoolean("fitWindow", &fit_window)); + ASSERT_TRUE(command.params.GetBoolean("textAutosizing", &text_autosizing)); + ASSERT_TRUE(command.params.GetDouble("fontScaleFactor", &font_scale_factor)); + ASSERT_EQ(device_metrics.width, width); + ASSERT_EQ(device_metrics.height, height); + ASSERT_EQ(device_metrics.device_scale_factor, device_scale_factor); + ASSERT_EQ(device_metrics.emulate_viewport, emulate_viewport); + ASSERT_EQ(device_metrics.fit_window, fit_window); + ASSERT_EQ(device_metrics.text_autosizing, text_autosizing); + ASSERT_EQ(device_metrics.font_scale_factor, font_scale_factor); +} + +} // namespace + +TEST(MobileEmulationOverrideManager, SendsCommandOnConnect) { + RecorderDevToolsClient client; + DeviceMetrics device_metrics(1, 2, 3.0); + MobileEmulationOverrideManager manager(&client, &device_metrics); + ASSERT_EQ(0u, client.commands_.size()); + ASSERT_EQ(kOk, manager.OnConnected(&client).code()); + + ASSERT_EQ(1u, client.commands_.size()); + ASSERT_EQ(kOk, manager.OnConnected(&client).code()); + ASSERT_EQ(2u, client.commands_.size()); + ASSERT_NO_FATAL_FAILURE( + AssertDeviceMetricsCommand(client.commands_[1], device_metrics)); +} + +TEST(MobileEmulationOverrideManager, SendsCommandOnNavigation) { + RecorderDevToolsClient client; + DeviceMetrics device_metrics(1, 2, 3.0); + MobileEmulationOverrideManager manager(&client, &device_metrics); + base::DictionaryValue main_frame_params; + ASSERT_EQ(kOk, + manager.OnEvent(&client, "Page.frameNavigated", main_frame_params) + .code()); + ASSERT_EQ(1u, client.commands_.size()); + ASSERT_EQ(kOk, + manager.OnEvent(&client, "Page.frameNavigated", main_frame_params) + .code()); + ASSERT_EQ(2u, client.commands_.size()); + ASSERT_NO_FATAL_FAILURE( + AssertDeviceMetricsCommand(client.commands_[1], device_metrics)); + + base::DictionaryValue sub_frame_params; + sub_frame_params.SetString("frame.parentId", "id"); + ASSERT_EQ( + kOk, + manager.OnEvent(&client, "Page.frameNavigated", sub_frame_params).code()); + ASSERT_EQ(2u, client.commands_.size()); +} diff --git a/chrome/test/chromedriver/chrome/stub_chrome.cc b/chrome/test/chromedriver/chrome/stub_chrome.cc index 5e1cbe1..394a183 100644 --- a/chrome/test/chromedriver/chrome/stub_chrome.cc +++ b/chrome/test/chromedriver/chrome/stub_chrome.cc @@ -43,6 +43,10 @@ std::string StubChrome::GetOperatingSystemName() { return std::string(); } +bool StubChrome::IsMobileEmulationEnabled() const { + return false; +} + Status StubChrome::Quit() { return Status(kOk); } diff --git a/chrome/test/chromedriver/chrome/stub_chrome.h b/chrome/test/chromedriver/chrome/stub_chrome.h index 1e07aa0..ba09328 100644 --- a/chrome/test/chromedriver/chrome/stub_chrome.h +++ b/chrome/test/chromedriver/chrome/stub_chrome.h @@ -30,6 +30,7 @@ class StubChrome : public Chrome { virtual Status CloseWebView(const std::string& id) OVERRIDE; virtual Status ActivateWebView(const std::string& id) OVERRIDE; virtual std::string GetOperatingSystemName() OVERRIDE; + virtual bool IsMobileEmulationEnabled() const OVERRIDE; virtual Status Quit() OVERRIDE; private: diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc index 4403e89..f49485d 100644 --- a/chrome/test/chromedriver/chrome/web_view_impl.cc +++ b/chrome/test/chromedriver/chrome/web_view_impl.cc @@ -21,6 +21,7 @@ #include "chrome/test/chromedriver/chrome/heap_snapshot_taker.h" #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h" #include "chrome/test/chromedriver/chrome/js.h" +#include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h" #include "chrome/test/chromedriver/chrome/navigation_tracker.h" #include "chrome/test/chromedriver/chrome/status.h" #include "chrome/test/chromedriver/chrome/ui_events.h" @@ -114,13 +115,16 @@ const char* GetAsString(KeyEventType type) { WebViewImpl::WebViewImpl(const std::string& id, const BrowserInfo* browser_info, - scoped_ptr<DevToolsClient> client) + scoped_ptr<DevToolsClient> client, + const DeviceMetrics* device_metrics) : id_(id), browser_info_(browser_info), dom_tracker_(new DomTracker(client.get())), frame_tracker_(new FrameTracker(client.get())), navigation_tracker_(new NavigationTracker(client.get(), browser_info)), dialog_manager_(new JavaScriptDialogManager(client.get())), + mobile_emulation_override_manager_( + new MobileEmulationOverrideManager(client.get(), device_metrics)), geolocation_override_manager_( new GeolocationOverrideManager(client.get())), heap_snapshot_taker_(new HeapSnapshotTaker(client.get())), diff --git a/chrome/test/chromedriver/chrome/web_view_impl.h b/chrome/test/chromedriver/chrome/web_view_impl.h index c1424c8..06718b7 100644 --- a/chrome/test/chromedriver/chrome/web_view_impl.h +++ b/chrome/test/chromedriver/chrome/web_view_impl.h @@ -21,10 +21,12 @@ class Value; struct BrowserInfo; class DebuggerTracker; +struct DeviceMetrics; class DevToolsClient; class DomTracker; class FrameTracker; class GeolocationOverrideManager; +class MobileEmulationOverrideManager; class HeapSnapshotTaker; struct KeyEvent; struct MouseEvent; @@ -36,6 +38,10 @@ class WebViewImpl : public WebView { WebViewImpl(const std::string& id, const BrowserInfo* browser_info, scoped_ptr<DevToolsClient> client); + WebViewImpl(const std::string& id, + const BrowserInfo* browser_info, + scoped_ptr<DevToolsClient> client, + const DeviceMetrics* device_metrics); virtual ~WebViewImpl(); // Overridden from WebView: @@ -111,6 +117,7 @@ class WebViewImpl : public WebView { scoped_ptr<FrameTracker> frame_tracker_; scoped_ptr<NavigationTracker> navigation_tracker_; scoped_ptr<JavaScriptDialogManager> dialog_manager_; + scoped_ptr<MobileEmulationOverrideManager> mobile_emulation_override_manager_; scoped_ptr<GeolocationOverrideManager> geolocation_override_manager_; scoped_ptr<HeapSnapshotTaker> heap_snapshot_taker_; scoped_ptr<DebuggerTracker> debugger_; diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc index 24037f5..49d5f05 100644 --- a/chrome/test/chromedriver/chrome_launcher.cc +++ b/chrome/test/chromedriver/chrome_launcher.cc @@ -157,9 +157,14 @@ Status WaitForDevToolsAndCheckVersion( const NetAddress& address, URLRequestContextGetter* context_getter, const SyncWebSocketFactory& socket_factory, + const Capabilities* capabilities, scoped_ptr<DevToolsHttpClient>* user_client) { + scoped_ptr<DeviceMetrics> device_metrics; + if (capabilities && capabilities->device_metrics) + device_metrics.reset(new DeviceMetrics(*capabilities->device_metrics)); + scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient( - address, context_getter, socket_factory)); + address, context_getter, socket_factory, device_metrics.Pass())); base::TimeTicks deadline = base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60); Status status = client->Init(deadline - base::TimeTicks::Now()); @@ -194,7 +199,7 @@ Status LaunchExistingChromeSession( scoped_ptr<DevToolsHttpClient> devtools_client; status = WaitForDevToolsAndCheckVersion( capabilities.debugger_address, context_getter, socket_factory, - &devtools_client); + NULL, &devtools_client); if (status.IsError()) { return Status(kUnknownError, "cannot connect to chrome at " + capabilities.debugger_address.ToString(), @@ -278,7 +283,8 @@ Status LaunchDesktopChrome( scoped_ptr<DevToolsHttpClient> devtools_client; status = WaitForDevToolsAndCheckVersion( - NetAddress(port), context_getter, socket_factory, &devtools_client); + NetAddress(port), context_getter, socket_factory, &capabilities, + &devtools_client); if (status.IsError()) { int exit_code; @@ -378,6 +384,7 @@ Status LaunchAndroidChrome( status = WaitForDevToolsAndCheckVersion(NetAddress(port), context_getter, socket_factory, + &capabilities, &devtools_client); if (status.IsError()) { device->TearDown(); diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py index 946f85c..063de99 100644 --- a/chrome/test/chromedriver/client/chromedriver.py +++ b/chrome/test/chromedriver/client/chromedriver.py @@ -65,7 +65,7 @@ class ChromeDriver(object): android_use_running_app=None, chrome_switches=None, chrome_extensions=None, chrome_log_path=None, debugger_address=None, browser_log_level=None, - experimental_options=None): + mobile_emulation=None, experimental_options=None): self._executor = command_executor.CommandExecutor(server_url) options = {} @@ -89,6 +89,10 @@ class ChromeDriver(object): assert type(chrome_switches) is list options['args'] = chrome_switches + if mobile_emulation: + assert type(mobile_emulation) is dict + options['mobileEmulation'] = mobile_emulation + if chrome_extensions: assert type(chrome_extensions) is list options['extensions'] = chrome_extensions diff --git a/chrome/test/chromedriver/client/webelement.py b/chrome/test/chromedriver/client/webelement.py index b3e6a64..5d7dda6 100644 --- a/chrome/test/chromedriver/client/webelement.py +++ b/chrome/test/chromedriver/client/webelement.py @@ -25,6 +25,9 @@ class WebElement(object): return self._Execute( Command.FIND_CHILD_ELEMENTS, {'using': strategy, 'value': target}) + def GetText(self): + return self._Execute(Command.GET_ELEMENT_TEXT) + def HoverOver(self): self._Execute(Command.HOVER_OVER_ELEMENT) diff --git a/chrome/test/chromedriver/embed_mobile_devices_in_cpp.py b/chrome/test/chromedriver/embed_mobile_devices_in_cpp.py new file mode 100755 index 0000000..766edce --- /dev/null +++ b/chrome/test/chromedriver/embed_mobile_devices_in_cpp.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# Copyright 2014 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. + +"""Embeds standalone JavaScript snippets in C++ code. + +The script requires the OverridesView file from WebKit that lists the known +mobile devices to be passed in as the only argument. The list of known devices +will be written to a C-style string to be parsed with JSONReader. +""" + +import optparse +import os +import sys + +import cpp_source + + +def main(): + parser = optparse.OptionParser() + parser.add_option( + '', '--directory', type='string', default='.', + help='Path to directory where the cc/h files should be created') + options, args = parser.parse_args() + + devices = '[' + file_name = args[0] + inside_list = False + with open(file_name, 'r') as f: + for line in f: + if not inside_list: + if 'DeviceTab._phones = [' in line or 'DeviceTab._tablets = [' in line: + inside_list = True + else: + if line.strip() == '];': + inside_list = False + continue + devices += line.strip() + + devices += ']' + cpp_source.WriteSource('mobile_device_list', + 'chrome/test/chromedriver/chrome', + options.directory, {'kMobileDevices': devices}) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chrome/test/chromedriver/session.h b/chrome/test/chromedriver/session.h index 38c59e2..fe837fb 100644 --- a/chrome/test/chromedriver/session.h +++ b/chrome/test/chromedriver/session.h @@ -14,6 +14,7 @@ #include "base/memory/scoped_vector.h" #include "base/time/time.h" #include "chrome/test/chromedriver/basic_types.h" +#include "chrome/test/chromedriver/chrome/device_metrics.h" #include "chrome/test/chromedriver/chrome/geoposition.h" namespace base { @@ -69,6 +70,7 @@ struct Session { base::TimeDelta script_timeout; scoped_ptr<std::string> prompt_text; scoped_ptr<Geoposition> overridden_geoposition; + scoped_ptr<DeviceMetrics> overridden_device_metrics; // Logs that populate from DevTools events. ScopedVector<WebDriverLog> devtools_logs; scoped_ptr<WebDriverLog> driver_log; diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc index b0ad052..864ffd7 100644 --- a/chrome/test/chromedriver/session_commands.cc +++ b/chrome/test/chromedriver/session_commands.cc @@ -81,6 +81,8 @@ scoped_ptr<base::DictionaryValue> CreateCapabilities(Chrome* chrome) { caps->SetBoolean("handlesAlerts", true); caps->SetBoolean("databaseEnabled", false); caps->SetBoolean("locationContextEnabled", true); + caps->SetBoolean("mobileEmulationEnabled", + chrome->IsMobileEmulationEnabled()); caps->SetBoolean("applicationCacheEnabled", false); caps->SetBoolean("browserConnectionEnabled", false); caps->SetBoolean("cssSelectorsEnabled", true); @@ -99,7 +101,6 @@ scoped_ptr<base::DictionaryValue> CreateCapabilities(Chrome* chrome) { return caps.Pass(); } - Status InitSessionHelper( const InitSessionParams& bound_params, Session* session, diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py index bce431b..1a83bb6 100755 --- a/chrome/test/chromedriver/test/run_py_tests.py +++ b/chrome/test/chromedriver/test/run_py_tests.py @@ -100,6 +100,7 @@ _ANDROID_NEGATIVE_FILTER['chrome'] = ( # Android doesn't support switches and extensions. 'ChromeSwitchesCapabilityTest.*', 'ChromeExtensionsCapabilityTest.*', + 'MobileEmulationCapabilityTest.*', # https://crbug.com/274650 'ChromeDriverTest.testCloseWindow', # https://code.google.com/p/chromedriver/issues/detail?id=270 @@ -708,6 +709,9 @@ class ChromeDriverTest(ChromeDriverBaseTest): def testDoesntHangOnDebugger(self): self._driver.ExecuteScript('debugger;') + def testMobileEmulationDisabledByDefault(self): + self.assertFalse(self._driver.capabilities['mobileEmulationEnabled']) + class ChromeDriverAndroidTest(ChromeDriverBaseTest): """End to end tests for Android-specific tests.""" @@ -814,6 +818,56 @@ class ChromeLogPathCapabilityTest(ChromeDriverBaseTest): self.assertTrue(self.LOG_MESSAGE in open(tmp_log_path.name).read()) +class MobileEmulationCapabilityTest(ChromeDriverBaseTest): + """Tests that ChromeDriver processes chromeOptions.mobileEmulation. + + Makes sure the device metrics are overridden in DevTools and user agent is + overridden in Chrome. + """ + + @staticmethod + def GlobalSetUp(): + def respondWithUserAgentString(request): + return request.GetHeader('User-Agent') + + MobileEmulationCapabilityTest._http_server = webserver.WebServer( + chrome_paths.GetTestData()) + MobileEmulationCapabilityTest._http_server.SetCallbackForPath( + '/userAgent', respondWithUserAgentString) + + @staticmethod + def GlobalTearDown(): + MobileEmulationCapabilityTest._http_server.Shutdown() + + def testDeviceMetrics(self): + driver = self.CreateDriver( + mobile_emulation = { + 'deviceMetrics': {'width': 360, 'height': 640, 'pixelRatio': 3}}) + self.assertTrue(driver.capabilities['mobileEmulationEnabled']) + self.assertEqual(360, driver.ExecuteScript('return window.innerWidth')) + self.assertEqual(640, driver.ExecuteScript('return window.innerHeight')) + + def testUserAgent(self): + driver = self.CreateDriver( + mobile_emulation = {'userAgent': 'Agent Smith'}) + driver.Load(self._http_server.GetUrl() + '/userAgent') + body_tag = driver.FindElement('tag name', 'body') + self.assertEqual("Agent Smith", body_tag.GetText()) + + def testDeviceName(self): + driver = self.CreateDriver( + mobile_emulation = {'deviceName': 'Google Nexus 5'}) + driver.Load(self._http_server.GetUrl() + '/userAgent') + self.assertEqual(360, driver.ExecuteScript('return window.innerWidth')) + self.assertEqual(640, driver.ExecuteScript('return window.innerHeight')) + body_tag = driver.FindElement('tag name', 'body') + self.assertEqual( + 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleW' + 'ebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/53' + '5.19', + body_tag.GetText()) + + class ChromeDriverLogTest(unittest.TestCase): """Tests that chromedriver produces the expected log file.""" @@ -1007,6 +1061,8 @@ if __name__ == '__main__': sys.modules[__name__]) tests = unittest_util.FilterTestSuite(all_tests_suite, options.filter) ChromeDriverTest.GlobalSetUp() + MobileEmulationCapabilityTest.GlobalSetUp() result = unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(tests) ChromeDriverTest.GlobalTearDown() + MobileEmulationCapabilityTest.GlobalTearDown() sys.exit(len(result.failures) + len(result.errors)) diff --git a/chrome/test/chromedriver/test/webserver.py b/chrome/test/chromedriver/test/webserver.py index 716e0f4..9de0339 100644 --- a/chrome/test/chromedriver/test/webserver.py +++ b/chrome/test/chromedriver/test/webserver.py @@ -48,6 +48,9 @@ class Request(object): def GetPath(self): return self._handler.path + def GetHeader(self, name): + return self._handler.headers.getheader(name) + class _BaseServer(BaseHTTPServer.HTTPServer): """Internal server that throws if timed out waiting for a request.""" @@ -120,19 +123,28 @@ class WebServer(object): self._thread.daemon = True self._thread.start() self._path_data_map = {} - self._path_data_lock = threading.Lock() + self._path_callback_map = {} + self._path_maps_lock = threading.Lock() def _OnRequest(self, request, responder): path = request.GetPath().split('?')[0] - # Serve from path -> data map. - self._path_data_lock.acquire() + # Serve from path -> callback and data maps. + self._path_maps_lock.acquire() try: + if path in self._path_callback_map: + body = self._path_callback_map[path](request) + if body: + responder.SendResponse(body) + else: + responder.SendError(503) + return + if path in self._path_data_map: responder.SendResponse(self._path_data_map[path]) return finally: - self._path_data_lock.release() + self._path_maps_lock.release() # Serve from file. path = os.path.normpath( @@ -146,11 +158,19 @@ class WebServer(object): responder.SendResponseFromFile(path) def SetDataForPath(self, path, data): - self._path_data_lock.acquire() + self._path_maps_lock.acquire() try: self._path_data_map[path] = data finally: - self._path_data_lock.release() + self._path_maps_lock.release() + + def SetCallbackForPath(self, path, func): + self._path_maps_lock.acquire() + try: + self._path_callback_map[path] = func + finally: + self._path_maps_lock.release() + def GetUrl(self): """Returns the base URL of the server.""" |