From 9e0e15862416722ed91ae3c9094adcc503285233 Mon Sep 17 00:00:00 2001 From: "kkania@chromium.org" Date: Tue, 27 Aug 2013 16:21:21 +0000 Subject: [chromedriver] Improve testing experience when using extensions. -Ensure we use the ID from the CRX, in case the user depends on the ID in some way -Wait for extension background pages to load BUG=none R=chrisgao@chromium.org Review URL: https://codereview.chromium.org/23054005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@219801 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/chrome_tests.gypi | 1 + chrome/test/chromedriver/alert_commands.cc | 4 +- .../chromedriver/chrome/chrome_desktop_impl.cc | 76 ++++---- .../test/chromedriver/chrome/chrome_desktop_impl.h | 12 ++ chrome/test/chromedriver/chrome/stub_web_view.cc | 3 +- chrome/test/chromedriver/chrome/stub_web_view.h | 3 +- chrome/test/chromedriver/chrome/web_view.h | 8 +- chrome/test/chromedriver/chrome/web_view_impl.cc | 10 +- chrome/test/chromedriver/chrome/web_view_impl.h | 3 +- chrome/test/chromedriver/chrome_launcher.cc | 205 ++++++++++++++++----- chrome/test/chromedriver/chrome_launcher.h | 3 +- .../test/chromedriver/chrome_launcher_unittest.cc | 71 ++++--- chrome/test/chromedriver/client/chromedriver.py | 10 +- chrome/test/chromedriver/commands_unittest.cc | 10 +- chrome/test/chromedriver/element_commands.cc | 3 +- chrome/test/chromedriver/element_util.cc | 3 +- chrome/test/chromedriver/session.cc | 14 +- chrome/test/chromedriver/session.h | 7 +- chrome/test/chromedriver/session_commands.cc | 26 ++- chrome/test/chromedriver/test/run_py_tests.py | 27 ++- chrome/test/chromedriver/test/webserver.py | 1 + chrome/test/chromedriver/window_commands.cc | 12 +- 22 files changed, 353 insertions(+), 159 deletions(-) diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 88abe82..2cc4cc1 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -950,6 +950,7 @@ 'chrome_devtools_lib', '../base/base.gyp:base', '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../crypto/crypto.gyp:crypto', '../net/net.gyp:net', '../ui/ui.gyp:ui', ], diff --git a/chrome/test/chromedriver/alert_commands.cc b/chrome/test/chromedriver/alert_commands.cc index 85836b9..de7118d 100644 --- a/chrome/test/chromedriver/alert_commands.cc +++ b/chrome/test/chromedriver/alert_commands.cc @@ -31,8 +31,8 @@ Status ExecuteAlertCommand( if (status.IsError()) return status; - status = web_view->WaitForPendingNavigations(session->GetCurrentFrameId(), - session->page_load_timeout); + status = web_view->WaitForPendingNavigations( + session->GetCurrentFrameId(), session->page_load_timeout, true); if (status.IsError() && status.code() != kUnexpectedAlertOpen) return status; diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc index c454917..799abbe 100644 --- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc +++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc @@ -87,42 +87,54 @@ ChromeDesktopImpl::~ChromeDesktopImpl() { base::CloseProcessHandle(process_); } -Status ChromeDesktopImpl::GetAutomationExtension( - AutomationExtension** extension) { - if (!automation_extension_) { - base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(10); - std::string id; - while (base::Time::Now() < deadline) { - WebViewsInfo views_info; - Status status = devtools_http_client_->GetWebViewsInfo(&views_info); - if (status.IsError()) - return status; - - for (size_t i = 0; i < views_info.GetSize(); ++i) { - if (views_info.Get(i).url.find( - "chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb") == 0) { - id = views_info.Get(i).id; - break; - } - } - if (!id.empty()) - break; - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); - } - if (id.empty()) - return Status(kUnknownError, "automation extension cannot be found"); - - scoped_ptr web_view(new WebViewImpl( - id, GetBuildNo(), devtools_http_client_->CreateClient(id), log_)); - Status status = web_view->ConnectIfNecessary(); +Status ChromeDesktopImpl::WaitForPageToLoad(const std::string& url, + const base::TimeDelta& timeout, + scoped_ptr* web_view) { + base::Time deadline = base::Time::Now() + timeout; + std::string id; + while (base::Time::Now() < deadline) { + WebViewsInfo views_info; + Status status = devtools_http_client_->GetWebViewsInfo(&views_info); if (status.IsError()) return status; - // Wait for the extension background page to load. - status = web_view->WaitForPendingNavigations( - std::string(), 5 * 60 * 1000); + for (size_t i = 0; i < views_info.GetSize(); ++i) { + if (views_info.Get(i).url.find(url) == 0) { + id = views_info.Get(i).id; + break; + } + } + if (!id.empty()) + break; + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } + if (id.empty()) + return Status(kUnknownError, "page could not be found: " + url); + + scoped_ptr web_view_tmp(new WebViewImpl( + id, GetBuildNo(), devtools_http_client_->CreateClient(id), log_)); + Status status = web_view_tmp->ConnectIfNecessary(); + if (status.IsError()) + return status; + + status = web_view_tmp->WaitForPendingNavigations( + std::string(), deadline - base::Time::Now(), false); + if (status.IsOk()) + *web_view = web_view_tmp.Pass(); + return status; +} + +Status ChromeDesktopImpl::GetAutomationExtension( + AutomationExtension** extension) { + if (!automation_extension_) { + scoped_ptr web_view; + Status status = WaitForPageToLoad( + "chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/" + "_generated_background_page.html", + base::TimeDelta::FromSeconds(10), + &web_view); if (status.IsError()) - return status; + return Status(kUnknownError, "cannot get automation extension", status); automation_extension_.reset(new AutomationExtension(web_view.Pass())); } diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h index bc17620..d033e23 100644 --- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h +++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h @@ -9,12 +9,18 @@ #include "base/compiler_specific.h" #include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" #include "base/process/process.h" #include "chrome/test/chromedriver/chrome/chrome_impl.h" +namespace base { +class TimeDelta; +} + class AutomationExtension; class DevToolsHttpClient; class Status; +class WebView; class ChromeDesktopImpl : public ChromeImpl { public: @@ -27,6 +33,12 @@ class ChromeDesktopImpl : public ChromeImpl { base::ScopedTempDir* extension_dir); virtual ~ChromeDesktopImpl(); + // Waits for a page with the given URL to appear and finish loading. + // Returns an error if the timeout is exceeded. + Status WaitForPageToLoad(const std::string& url, + const base::TimeDelta& timeout, + scoped_ptr* web_view); + // Overridden from Chrome: virtual Status GetAutomationExtension( AutomationExtension** extension) OVERRIDE; diff --git a/chrome/test/chromedriver/chrome/stub_web_view.cc b/chrome/test/chromedriver/chrome/stub_web_view.cc index a40fb60..9d90fd7 100644 --- a/chrome/test/chromedriver/chrome/stub_web_view.cc +++ b/chrome/test/chromedriver/chrome/stub_web_view.cc @@ -91,7 +91,8 @@ Status StubWebView::DeleteCookie(const std::string& name, } Status StubWebView::WaitForPendingNavigations(const std::string& frame_id, - int timeout) { + const base::TimeDelta& timeout, + bool stop_load_on_timeout) { return Status(kOk); } diff --git a/chrome/test/chromedriver/chrome/stub_web_view.h b/chrome/test/chromedriver/chrome/stub_web_view.h index 952b1b5..b19baf3 100644 --- a/chrome/test/chromedriver/chrome/stub_web_view.h +++ b/chrome/test/chromedriver/chrome/stub_web_view.h @@ -54,7 +54,8 @@ class StubWebView : public WebView { virtual Status DeleteCookie(const std::string& name, const std::string& url) OVERRIDE; virtual Status WaitForPendingNavigations(const std::string& frame_id, - int timeout) OVERRIDE; + const base::TimeDelta& timeout, + bool stop_load_on_timeout) OVERRIDE; virtual Status IsPendingNavigation( const std::string& frame_id, bool* is_pending) OVERRIDE; virtual JavaScriptDialogManager* GetJavaScriptDialogManager() OVERRIDE; diff --git a/chrome/test/chromedriver/chrome/web_view.h b/chrome/test/chromedriver/chrome/web_view.h index 5e11d59..e3cf35a 100644 --- a/chrome/test/chromedriver/chrome/web_view.h +++ b/chrome/test/chromedriver/chrome/web_view.h @@ -112,10 +112,12 @@ class WebView { // If |frame_id| is "", waits for navigations on the main frame. // If a modal dialog appears while waiting, kUnexpectedAlertOpen will be // returned. - // If there are still pending navigations after |timeout|ms, - // page load is stopped, and kTimeout status is returned. + // If timeout is exceeded, will return a timeout status. + // If |stop_load_on_timeout| is true, will attempt to stop the page load on + // timeout before returning the timeout status. virtual Status WaitForPendingNavigations(const std::string& frame_id, - int timeout) = 0; + const base::TimeDelta& timeout, + bool stop_load_on_timeout) = 0; // Returns whether the frame is pending navigation. virtual Status IsPendingNavigation( diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc index b463b0e..dd5d8ac 100644 --- a/chrome/test/chromedriver/chrome/web_view_impl.cc +++ b/chrome/test/chromedriver/chrome/web_view_impl.cc @@ -320,13 +320,15 @@ Status WebViewImpl::DeleteCookie(const std::string& name, } Status WebViewImpl::WaitForPendingNavigations(const std::string& frame_id, - int timeout) { + const base::TimeDelta& timeout, + bool stop_load_on_timeout) { log_->AddEntry(Log::kLog, "waiting for pending navigations..."); Status status = client_->HandleEventsUntil( - base::Bind(&WebViewImpl::IsNotPendingNavigation, base::Unretained(this), + base::Bind(&WebViewImpl::IsNotPendingNavigation, + base::Unretained(this), frame_id), - base::TimeDelta::FromMilliseconds(timeout)); - if (status.code() == kTimeout) { + timeout); + if (status.code() == kTimeout && stop_load_on_timeout) { log_->AddEntry(Log::kLog, "timed out. stopping navigations..."); scoped_ptr unused_value; EvaluateScript(std::string(), "window.stop();", &unused_value); diff --git a/chrome/test/chromedriver/chrome/web_view_impl.h b/chrome/test/chromedriver/chrome/web_view_impl.h index 239d7b4..1067ad0 100644 --- a/chrome/test/chromedriver/chrome/web_view_impl.h +++ b/chrome/test/chromedriver/chrome/web_view_impl.h @@ -74,7 +74,8 @@ class WebViewImpl : public WebView { virtual Status DeleteCookie(const std::string& name, const std::string& url) OVERRIDE; virtual Status WaitForPendingNavigations(const std::string& frame_id, - int timeout) OVERRIDE; + const base::TimeDelta& timeout, + bool stop_load_on_timeout) OVERRIDE; virtual Status IsPendingNavigation( const std::string& frame_id, bool* is_pending) OVERRIDE; virtual JavaScriptDialogManager* GetJavaScriptDialogManager() OVERRIDE; diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc index 369b24e..36e2a1c 100644 --- a/chrome/test/chromedriver/chrome_launcher.cc +++ b/chrome/test/chromedriver/chrome_launcher.cc @@ -36,8 +36,10 @@ #include "chrome/test/chromedriver/chrome/status.h" #include "chrome/test/chromedriver/chrome/user_data_dir.h" #include "chrome/test/chromedriver/chrome/version.h" +#include "chrome/test/chromedriver/chrome/web_view.h" #include "chrome/test/chromedriver/chrome/zip.h" #include "chrome/test/chromedriver/net/url_request_context_getter.h" +#include "crypto/sha2.h" namespace { @@ -79,7 +81,8 @@ Status PrepareCommandLine(int port, const Capabilities& capabilities, CommandLine* prepared_command, base::ScopedTempDir* user_data_dir, - base::ScopedTempDir* extension_dir) { + base::ScopedTempDir* extension_dir, + std::vector* extension_bg_pages) { CommandLine command = capabilities.command; base::FilePath program = command.GetProgram(); if (program.empty()) { @@ -135,8 +138,11 @@ Status PrepareCommandLine(int port, return Status(kUnknownError, "cannot create temp dir for unpacking extensions"); } - Status status = internal::ProcessExtensions( - capabilities.extensions, extension_dir->path(), true, &command); + Status status = internal::ProcessExtensions(capabilities.extensions, + extension_dir->path(), + true, + &command, + extension_bg_pages); if (status.IsError()) return status; @@ -205,8 +211,13 @@ Status LaunchDesktopChrome( CommandLine command(CommandLine::NO_PROGRAM); base::ScopedTempDir user_data_dir; base::ScopedTempDir extension_dir; - Status status = PrepareCommandLine(port, capabilities, - &command, &user_data_dir, &extension_dir); + std::vector extension_bg_pages; + Status status = PrepareCommandLine(port, + capabilities, + &command, + &user_data_dir, + &extension_dir, + &extension_bg_pages); if (status.IsError()) return status; @@ -272,12 +283,25 @@ Status LaunchDesktopChrome( } return status; } - chrome->reset(new ChromeDesktopImpl(devtools_client.Pass(), - devtools_event_listeners, - log, - process, - &user_data_dir, - &extension_dir)); + scoped_ptr chrome_desktop( + new ChromeDesktopImpl(devtools_client.Pass(), + devtools_event_listeners, + log, + process, + &user_data_dir, + &extension_dir)); + for (size_t i = 0; i < extension_bg_pages.size(); ++i) { + scoped_ptr web_view; + Status status = chrome_desktop->WaitForPageToLoad( + extension_bg_pages[i], base::TimeDelta::FromSeconds(10), &web_view); + if (status.IsError()) { + return Status(kUnknownError, + "failed to wait for extension background page to load: " + + extension_bg_pages[i], + status); + } + } + *chrome = chrome_desktop.Pass(); return Status(kOk); } @@ -358,43 +382,137 @@ Status LaunchChrome( namespace internal { +void ConvertHexadecimalToIDAlphabet(std::string* id) { + for (size_t i = 0; i < id->size(); ++i) { + int val; + if (base::HexStringToInt(base::StringPiece(id->begin() + i, + id->begin() + i + 1), + &val)) { + (*id)[i] = val + 'a'; + } else { + (*id)[i] = 'a'; + } + } +} + +std::string GenerateExtensionId(const std::string& input) { + uint8 hash[16]; + crypto::SHA256HashString(input, hash, sizeof(hash)); + std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash))); + ConvertHexadecimalToIDAlphabet(&output); + return output; +} + +Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest, + const std::string& id, + std::string* bg_page) { + std::string bg_page_name; + bool persistent = true; + manifest->GetBoolean("background.persistent", &persistent); + const base::Value* unused_value; + if (manifest->Get("background.scripts", &unused_value)) + bg_page_name = "_generated_background_page.html"; + manifest->GetString("background.page", &bg_page_name); + manifest->GetString("background_page", &bg_page_name); + if (bg_page_name.empty() || !persistent) + return Status(kOk); + *bg_page = "chrome-extension://" + id + "/" + bg_page_name; + return Status(kOk); +} + +Status ProcessExtension(const std::string& extension, + const base::FilePath& temp_dir, + base::FilePath* path, + std::string* bg_page) { + // Decodes extension string. + // Some WebDriver client base64 encoders follow RFC 1521, which require that + // 'encoded lines be no more than 76 characters long'. Just remove any + // newlines. + std::string extension_base64; + RemoveChars(extension, "\n", &extension_base64); + std::string decoded_extension; + if (!base::Base64Decode(extension_base64, &decoded_extension)) + return Status(kUnknownError, "cannot base64 decode"); + + // Get extension's ID from public key in crx file. + // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html. + std::string key_len_str = decoded_extension.substr(8, 4); + if (key_len_str.size() != 4) + return Status(kUnknownError, "cannot extract public key length"); + uint32 key_len = *reinterpret_cast(key_len_str.c_str()); + std::string public_key = decoded_extension.substr(16, key_len); + if (key_len != public_key.size()) + return Status(kUnknownError, "invalid public key length"); + std::string public_key_base64; + if (!base::Base64Encode(public_key, &public_key_base64)) + return Status(kUnknownError, "cannot base64 encode public key"); + std::string id = GenerateExtensionId(public_key); + + // Unzip the crx file. + base::ScopedTempDir temp_crx_dir; + if (!temp_crx_dir.CreateUniqueTempDir()) + return Status(kUnknownError, "cannot create temp dir"); + base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); + int size = static_cast(decoded_extension.length()); + if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) != + size) { + return Status(kUnknownError, "cannot write file"); + } + base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id); + if (!zip::Unzip(extension_crx, extension_dir)) + return Status(kUnknownError, "cannot unzip"); + + // Parse the manifest and set the 'key' if not already present. + base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json")); + std::string manifest_data; + if (!file_util::ReadFileToString(manifest_path, &manifest_data)) + return Status(kUnknownError, "cannot read manifest"); + scoped_ptr manifest_value(base::JSONReader::Read(manifest_data)); + base::DictionaryValue* manifest; + if (!manifest_value || !manifest_value->GetAsDictionary(&manifest)) + return Status(kUnknownError, "invalid manifest"); + if (!manifest->HasKey("key")) { + manifest->SetString("key", public_key_base64); + base::JSONWriter::Write(manifest, &manifest_data); + if (file_util::WriteFile( + manifest_path, manifest_data.c_str(), manifest_data.size()) != + static_cast(manifest_data.size())) { + return Status(kUnknownError, "cannot add 'key' to manifest"); + } + } + + // Get extension's background page URL, if there is one. + std::string bg_page_tmp; + Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp); + if (status.IsError()) + return status; + + *path = extension_dir; + if (bg_page_tmp.size()) + *bg_page = bg_page_tmp; + return Status(kOk); +} + Status ProcessExtensions(const std::vector& extensions, const base::FilePath& temp_dir, bool include_automation_extension, - CommandLine* command) { + CommandLine* command, + std::vector* bg_pages) { + std::vector bg_pages_tmp; std::vector extension_paths; - size_t count = 0; - for (std::vector::const_iterator it = extensions.begin(); - it != extensions.end(); ++it) { - std::string extension_base64; - // Decodes extension string. - // Some WebDriver client base64 encoders follow RFC 1521, which require that - // 'encoded lines be no more than 76 characters long'. Just remove any - // newlines. - RemoveChars(*it, "\n", &extension_base64); - std::string decoded_extension; - if (!base::Base64Decode(extension_base64, &decoded_extension)) - return Status(kUnknownError, "failed to base64 decode extension"); - - // Writes decoded extension into a temporary .crx file. - base::ScopedTempDir temp_crx_dir; - if (!temp_crx_dir.CreateUniqueTempDir()) - return Status(kUnknownError, - "cannot create temp dir for writing extension CRX file"); - base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); - int size = static_cast(decoded_extension.length()); - if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) - != size) { - return Status(kUnknownError, "failed to write extension file"); + for (size_t i = 0; i < extensions.size(); ++i) { + base::FilePath path; + std::string bg_page; + Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page); + if (status.IsError()) { + return Status( + kUnknownError, + base::StringPrintf("cannot process extension #%" PRIuS, i + 1), + status); } - - // Unzips the temporary .crx file. - count++; - base::FilePath extension_dir = temp_dir.AppendASCII( - base::StringPrintf("extension%" PRIuS, count)); - if (!zip::Unzip(extension_crx, extension_dir)) - return Status(kUnknownError, "failed to unzip the extension CRX file"); - extension_paths.push_back(extension_dir.value()); + extension_paths.push_back(path.value()); + if (bg_page.length()) + bg_pages_tmp.push_back(bg_page); } if (include_automation_extension) { @@ -415,6 +533,7 @@ Status ProcessExtensions(const std::vector& extensions, extension_paths, FILE_PATH_LITERAL(',')); command->AppendSwitchNative("load-extension", extension_paths_value); } + bg_pages->swap(bg_pages_tmp); return Status(kOk); } diff --git a/chrome/test/chromedriver/chrome_launcher.h b/chrome/test/chromedriver/chrome_launcher.h index 405c275..d671b56 100644 --- a/chrome/test/chromedriver/chrome_launcher.h +++ b/chrome/test/chromedriver/chrome_launcher.h @@ -42,7 +42,8 @@ namespace internal { Status ProcessExtensions(const std::vector& extensions, const base::FilePath& temp_dir, bool include_automation_extension, - CommandLine* command); + CommandLine* command, + std::vector* bg_pages); Status PrepareUserDataDir( const base::FilePath& user_data_dir, const base::DictionaryValue* custom_prefs, diff --git a/chrome/test/chromedriver/chrome_launcher_unittest.cc b/chrome/test/chromedriver/chrome_launcher_unittest.cc index 369fb9c..c613460 100644 --- a/chrome/test/chromedriver/chrome_launcher_unittest.cc +++ b/chrome/test/chromedriver/chrome_launcher_unittest.cc @@ -21,62 +21,80 @@ TEST(ProcessExtensions, NoExtension) { CommandLine command(CommandLine::NO_PROGRAM); std::vector extensions; base::FilePath extension_dir; + std::vector bg_pages; Status status = internal::ProcessExtensions(extensions, extension_dir, - false, &command); + false, &command, &bg_pages); ASSERT_TRUE(status.IsOk()); ASSERT_FALSE(command.HasSwitch("load-extension")); + ASSERT_EQ(0u, bg_pages.size()); } -TEST(ProcessExtensions, SingleExtension) { +bool AddExtensionForInstall(const std::string& relative_path, + std::vector* extensions) { base::FilePath source_root; PathService::Get(base::DIR_SOURCE_ROOT, &source_root); base::FilePath crx_file_path = source_root.AppendASCII( - "chrome/test/data/chromedriver/ext_test_1.crx"); + "chrome/test/data/chromedriver/" + relative_path); std::string crx_contents; - ASSERT_TRUE(file_util::ReadFileToString(crx_file_path, &crx_contents)); + if (!file_util::ReadFileToString(crx_file_path, &crx_contents)) + return false; - std::vector extensions; std::string crx_encoded; - ASSERT_TRUE(base::Base64Encode(crx_contents, &crx_encoded)); - extensions.push_back(crx_encoded); + if (!base::Base64Encode(crx_contents, &crx_encoded)) + return false; + extensions->push_back(crx_encoded); + return true; +} + +TEST(ProcessExtensions, SingleExtensionWithBgPage) { + std::vector extensions; + ASSERT_TRUE(AddExtensionForInstall("ext_slow_loader.crx", &extensions)); base::ScopedTempDir extension_dir; ASSERT_TRUE(extension_dir.CreateUniqueTempDir()); CommandLine command(CommandLine::NO_PROGRAM); + std::vector bg_pages; Status status = internal::ProcessExtensions(extensions, extension_dir.path(), - false, &command); + false, &command, &bg_pages); ASSERT_TRUE(status.IsOk()); ASSERT_TRUE(command.HasSwitch("load-extension")); base::FilePath temp_ext_path = command.GetSwitchValuePath("load-extension"); ASSERT_TRUE(base::PathExists(temp_ext_path)); + std::string manifest_txt; + ASSERT_TRUE(file_util::ReadFileToString( + temp_ext_path.AppendASCII("manifest.json"), &manifest_txt)); + scoped_ptr manifest(base::JSONReader::Read(manifest_txt)); + ASSERT_TRUE(manifest); + base::DictionaryValue* manifest_dict = NULL; + ASSERT_TRUE(manifest->GetAsDictionary(&manifest_dict)); + std::string key; + ASSERT_TRUE(manifest_dict->GetString("key", &key)); + ASSERT_EQ( + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8qhZthEHjTIA3IYMzi79s2KFepVziY0du" + "JzHcqRUB/YHSGseIUqcYXGazJhDz/" + "4FbRg8ef9fQazL1UbMMGBIf4za1kJ2os2MsRrNXzHslkbtcLVj2VfofhuHJmu+" + "CnKJ77UWamJiNAaQSiclu4duwnEWrkx+g/8ChQfhZzC4jvQIDAQAB", + key); + ASSERT_EQ(1u, bg_pages.size()); + ASSERT_EQ( + "chrome-extension://jijhlkpcmmeckhlgdipjhnchhoabdjae/" + "_generated_background_page.html", + bg_pages[0]); } -TEST(ProcessExtensions, MultipleExtensions) { - base::FilePath source_root; - PathService::Get(base::DIR_SOURCE_ROOT, &source_root); - base::FilePath test_ext_path = source_root.AppendASCII( - "chrome/test/data/chromedriver"); - base::FilePath test_crx_1 = test_ext_path.AppendASCII("ext_test_1.crx"); - base::FilePath test_crx_2 = test_ext_path.AppendASCII("ext_test_2.crx"); - - std::string crx_1_contents, crx_2_contents; - ASSERT_TRUE(file_util::ReadFileToString(test_crx_1, &crx_1_contents)); - ASSERT_TRUE(file_util::ReadFileToString(test_crx_2, &crx_2_contents)); - +TEST(ProcessExtensions, MultipleExtensionsNoBgPages) { std::vector extensions; - std::string crx_1_encoded, crx_2_encoded; - ASSERT_TRUE(base::Base64Encode(crx_1_contents, &crx_1_encoded)); - ASSERT_TRUE(base::Base64Encode(crx_2_contents, &crx_2_encoded)); - extensions.push_back(crx_1_encoded); - extensions.push_back(crx_2_encoded); + ASSERT_TRUE(AddExtensionForInstall("ext_test_1.crx", &extensions)); + ASSERT_TRUE(AddExtensionForInstall("ext_test_2.crx", &extensions)); base::ScopedTempDir extension_dir; ASSERT_TRUE(extension_dir.CreateUniqueTempDir()); CommandLine command(CommandLine::NO_PROGRAM); + std::vector bg_pages; Status status = internal::ProcessExtensions(extensions, extension_dir.path(), - false, &command); + false, &command, &bg_pages); ASSERT_TRUE(status.IsOk()); ASSERT_TRUE(command.HasSwitch("load-extension")); CommandLine::StringType ext_paths = command.GetSwitchValueNative( @@ -86,6 +104,7 @@ TEST(ProcessExtensions, MultipleExtensions) { ASSERT_EQ(2u, ext_path_list.size()); ASSERT_TRUE(base::PathExists(base::FilePath(ext_path_list[0]))); ASSERT_TRUE(base::PathExists(base::FilePath(ext_path_list[1]))); + ASSERT_EQ(0u, bg_pages.size()); } namespace { diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py index db71e2b..ae2ecd8 100644 --- a/chrome/test/chromedriver/client/chromedriver.py +++ b/chrome/test/chromedriver/client/chromedriver.py @@ -93,7 +93,7 @@ class ChromeDriver(object): } } - self._session_id = self._executor.Execute( + self._session_id = self._ExecuteCommand( Command.NEW_SESSION, params)['sessionId'] def _WrapValue(self, value): @@ -126,12 +126,16 @@ class ChromeDriver(object): else: return value - def ExecuteCommand(self, command, params={}): - params['sessionId'] = self._session_id + def _ExecuteCommand(self, command, params={}): params = self._WrapValue(params) response = self._executor.Execute(command, params) if response['status'] != 0: raise _ExceptionForResponse(response) + return response + + def ExecuteCommand(self, command, params={}): + params['sessionId'] = self._session_id + response = self._ExecuteCommand(command, params) return self._UnwrapValue(response['value']) def GetWindowHandles(self): diff --git a/chrome/test/chromedriver/commands_unittest.cc b/chrome/test/chromedriver/commands_unittest.cc index 064fb95..e377d29 100644 --- a/chrome/test/chromedriver/commands_unittest.cc +++ b/chrome/test/chromedriver/commands_unittest.cc @@ -338,7 +338,7 @@ class FindElementWebView : public StubWebView { TEST(CommandsTest, SuccessfulFindElement) { FindElementWebView web_view(true, kElementExistsQueryTwice); Session session("id"); - session.implicit_wait = 1000; + session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id1", std::string()); base::DictionaryValue params; params.SetString("using", "id"); @@ -367,7 +367,7 @@ TEST(CommandsTest, FailedFindElement) { TEST(CommandsTest, SuccessfulFindElements) { FindElementWebView web_view(false, kElementExistsQueryTwice); Session session("id"); - session.implicit_wait = 1000; + session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id2", std::string()); base::DictionaryValue params; params.SetString("using", "name"); @@ -401,7 +401,7 @@ TEST(CommandsTest, FailedFindElements) { TEST(CommandsTest, SuccessfulFindChildElement) { FindElementWebView web_view(true, kElementExistsQueryTwice); Session session("id"); - session.implicit_wait = 1000; + session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id3", std::string()); base::DictionaryValue params; params.SetString("using", "tag name"); @@ -439,7 +439,7 @@ TEST(CommandsTest, FailedFindChildElement) { TEST(CommandsTest, SuccessfulFindChildElements) { FindElementWebView web_view(false, kElementExistsQueryTwice); Session session("id"); - session.implicit_wait = 1000; + session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id4", std::string()); base::DictionaryValue params; params.SetString("using", "class name"); @@ -480,7 +480,7 @@ TEST(CommandsTest, FailedFindChildElements) { TEST(CommandsTest, TimeoutInFindElement) { Session session("id"); FindElementWebView web_view(true, kElementExistsTimeout); - session.implicit_wait = 2; + session.implicit_wait = base::TimeDelta::FromMilliseconds(2); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); diff --git a/chrome/test/chromedriver/element_commands.cc b/chrome/test/chromedriver/element_commands.cc index 81e83a3..989ed72 100644 --- a/chrome/test/chromedriver/element_commands.cc +++ b/chrome/test/chromedriver/element_commands.cc @@ -47,8 +47,7 @@ Status SendKeysToElement( return status; if (is_focused) break; - if ((base::Time::Now() - start_time).InMilliseconds() >= - session->implicit_wait) { + if (base::Time::Now() - start_time >= session->implicit_wait) { return Status(kElementNotVisible); } base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); diff --git a/chrome/test/chromedriver/element_util.cc b/chrome/test/chromedriver/element_util.cc index 6c4bca0..0ba4246 100644 --- a/chrome/test/chromedriver/element_util.cc +++ b/chrome/test/chromedriver/element_util.cc @@ -271,8 +271,7 @@ Status FindElement( } } - if ((base::Time::Now() - start_time).InMilliseconds() >= - session->implicit_wait) { + if (base::Time::Now() - start_time >= session->implicit_wait) { if (only_one) { return Status(kNoSuchElement); } else { diff --git a/chrome/test/chromedriver/session.cc b/chrome/test/chromedriver/session.cc index ea6da2c..5a18729 100644 --- a/chrome/test/chromedriver/session.cc +++ b/chrome/test/chromedriver/session.cc @@ -28,10 +28,8 @@ Session::Session(const std::string& id) detach(false), sticky_modifiers(0), mouse_position(0, 0), - implicit_wait(0), - page_load_timeout(kDefaultPageLoadTimeoutMs), - script_timeout(0) { -} + page_load_timeout( + base::TimeDelta::FromMilliseconds(kDefaultPageLoadTimeoutMs)) {} Session::Session(const std::string& id, scoped_ptr chrome) : id(id), @@ -40,11 +38,9 @@ Session::Session(const std::string& id, scoped_ptr chrome) chrome(chrome.Pass()), sticky_modifiers(0), mouse_position(0, 0), - implicit_wait(0), - page_load_timeout(kDefaultPageLoadTimeoutMs), - script_timeout(0), - capabilities(CreateCapabilities()) { -} + page_load_timeout( + base::TimeDelta::FromMilliseconds(kDefaultPageLoadTimeoutMs)), + capabilities(CreateCapabilities()) {} Session::~Session() {} diff --git a/chrome/test/chromedriver/session.h b/chrome/test/chromedriver/session.h index 40ec9e9..fef177a 100644 --- a/chrome/test/chromedriver/session.h +++ b/chrome/test/chromedriver/session.h @@ -12,6 +12,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "base/time/time.h" #include "chrome/test/chromedriver/basic_types.h" #include "chrome/test/chromedriver/chrome/geoposition.h" @@ -59,9 +60,9 @@ struct Session { // this list will be empty. std::list frames; WebPoint mouse_position; - int implicit_wait; - int page_load_timeout; - int script_timeout; + base::TimeDelta implicit_wait; + base::TimeDelta page_load_timeout; + base::TimeDelta script_timeout; scoped_ptr prompt_text; scoped_ptr overridden_geoposition; // Logs that populate from DevTools events. diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc index 23f362b..53b3933 100644 --- a/chrome/test/chromedriver/session_commands.cc +++ b/chrome/test/chromedriver/session_commands.cc @@ -212,18 +212,22 @@ Status ExecuteSetTimeout( if (!params.GetString("type", &type)) return Status(kUnknownError, "'type' must be a string"); - int ms = static_cast(ms_double); + base::TimeDelta timeout = + base::TimeDelta::FromMilliseconds(static_cast(ms_double)); // TODO(frankf): implicit and script timeout should be cleared // if negative timeout is specified. - if (type == "implicit") - session->implicit_wait = ms; - else if (type == "script") - session->script_timeout = ms; - else if (type == "page load") + if (type == "implicit") { + session->implicit_wait = timeout; + } else if (type == "script") { + session->script_timeout = timeout; + } else if (type == "page load") { session->page_load_timeout = - ((ms < 0) ? Session::kDefaultPageLoadTimeoutMs : ms); - else + ((timeout < base::TimeDelta()) ? base::TimeDelta::FromMilliseconds( + Session::kDefaultPageLoadTimeoutMs) + : timeout); + } else { return Status(kUnknownError, "unknown type of timeout:" + type); + } return Status(kOk); } @@ -234,7 +238,8 @@ Status ExecuteSetScriptTimeout( double ms; if (!params.GetDouble("ms", &ms) || ms < 0) return Status(kUnknownError, "'ms' must be a non-negative number"); - session->script_timeout = static_cast(ms); + session->script_timeout = + base::TimeDelta::FromMilliseconds(static_cast(ms)); return Status(kOk); } @@ -245,7 +250,8 @@ Status ExecuteImplicitlyWait( double ms; if (!params.GetDouble("ms", &ms) || ms < 0) return Status(kUnknownError, "'ms' must be a non-negative number"); - session->implicit_wait = static_cast(ms); + session->implicit_wait = + base::TimeDelta::FromMilliseconds(static_cast(ms)); return Status(kOk); } diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py index 0b9099d..2b01b5a 100755 --- a/chrome/test/chromedriver/test/run_py_tests.py +++ b/chrome/test/chromedriver/test/run_py_tests.py @@ -12,6 +12,7 @@ import os import sys import socket import tempfile +import threading import time import unittest @@ -638,14 +639,32 @@ class ChromeSwitchesCapabilityTest(ChromeDriverBaseTest): class ChromeExtensionsCapabilityTest(ChromeDriverBaseTest): """Tests that chromedriver properly processes chromeOptions.extensions.""" + def _PackExtension(self, ext_path): + return base64.b64encode(open(ext_path, 'rb').read()) + def testExtensionsInstall(self): """Checks that chromedriver can take the extensions.""" crx_1 = os.path.join(_TEST_DATA_DIR, 'ext_test_1.crx') crx_2 = os.path.join(_TEST_DATA_DIR, 'ext_test_2.crx') - crx_1_encoded = base64.b64encode(open(crx_1, 'rb').read()) - crx_2_encoded = base64.b64encode(open(crx_2, 'rb').read()) - extensions = [crx_1_encoded, crx_2_encoded] - self.CreateDriver(chrome_extensions=extensions) + self.CreateDriver(chrome_extensions=[self._PackExtension(crx_1), + self._PackExtension(crx_2)]) + + def testWaitsForExtensionToLoad(self): + did_load_event = threading.Event() + server = webserver.SyncWebServer() + def RunServer(): + time.sleep(5) + server.RespondWithContent('iframe') + did_load_event.set() + + thread = threading.Thread(target=RunServer) + thread.daemon = True + thread.start() + crx = os.path.join(_TEST_DATA_DIR, 'ext_slow_loader.crx') + driver = self.CreateDriver( + chrome_switches=['user-agent=' + server.GetUrl()], + chrome_extensions=[self._PackExtension(crx)]) + self.assertTrue(did_load_event.is_set()) class ChromeLogPathCapabilityTest(ChromeDriverBaseTest): diff --git a/chrome/test/chromedriver/test/webserver.py b/chrome/test/chromedriver/test/webserver.py index fb48cc5..716e0f4 100644 --- a/chrome/test/chromedriver/test/webserver.py +++ b/chrome/test/chromedriver/test/webserver.py @@ -117,6 +117,7 @@ class WebServer(object): self._root_dir = os.path.abspath(root_dir) self._server = _BaseServer(self._OnRequest, server_cert_and_key_path) self._thread = threading.Thread(target=self._server.serve_forever) + self._thread.daemon = True self._thread.start() self._path_data_map = {} self._path_data_lock = threading.Lock() diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc index ad62a9e..b3b9f8a 100644 --- a/chrome/test/chromedriver/window_commands.cc +++ b/chrome/test/chromedriver/window_commands.cc @@ -215,18 +215,16 @@ Status ExecuteWindowCommand( else break; } - nav_status = - web_view->WaitForPendingNavigations(session->GetCurrentFrameId(), - session->page_load_timeout); + nav_status = web_view->WaitForPendingNavigations( + session->GetCurrentFrameId(), session->page_load_timeout, true); if (nav_status.IsError()) return nav_status; status = command.Run(session, web_view, params, value); } - nav_status = - web_view->WaitForPendingNavigations(session->GetCurrentFrameId(), - session->page_load_timeout); + nav_status = web_view->WaitForPendingNavigations( + session->GetCurrentFrameId(), session->page_load_timeout, true); if (status.IsOk() && nav_status.IsError() && nav_status.code() != kDisconnected && @@ -278,7 +276,7 @@ Status ExecuteExecuteAsyncScript( return web_view->CallUserAsyncFunction( session->GetCurrentFrameId(), "function(){" + script + "}", *args, - base::TimeDelta::FromMilliseconds(session->script_timeout), value); + session->script_timeout, value); } Status ExecuteSwitchToFrame( -- cgit v1.1