Commit f8e1db1c authored by Rohan Pavone's avatar Rohan Pavone Committed by Commit Bot

[ChromeDriver] Adds ChromeDriver end of NewWindow API.

Adds ChromeDriver components necessary to leverage DevTools
Target.createTarget changes, allowing for new window or tab to be
be created based on user preference in both headless and regular Chrome.
This completes the implementation of the NewWindow command from the
W3C-spec (https://w3c.github.io/webdriver/#new-window).

Design Doc: http://bit.ly/chromedriver-new-window

focus/blur test to confirm that focussing steps are not run during
creation.

Tested: WPTs webdriver/tests/new_window, all run_py_tests, and added
Bug: chromedriver:2690, b/127692156
Change-Id: I638167a47aa759ce511aae734c615cc6cf76df59
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1636464
Commit-Queue: Rohan Pavone <rohpavone@chromium.org>
Reviewed-by: default avatarJohn Chen <johnchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665612}
parent 6586e8f0
...@@ -16,6 +16,11 @@ class WebView; ...@@ -16,6 +16,11 @@ class WebView;
class Chrome { class Chrome {
public: public:
enum class WindowType {
kWindow,
kTab,
};
virtual ~Chrome() {} virtual ~Chrome() {}
virtual Status GetAsDesktop(ChromeDesktopImpl** desktop) = 0; virtual Status GetAsDesktop(ChromeDesktopImpl** desktop) = 0;
...@@ -37,6 +42,11 @@ class Chrome { ...@@ -37,6 +42,11 @@ class Chrome {
// Return the WebView for the given id. // Return the WebView for the given id.
virtual Status GetWebViewById(const std::string& id, WebView** web_view) = 0; virtual Status GetWebViewById(const std::string& id, WebView** web_view) = 0;
// Makes new window or tab.
virtual Status NewWindow(const std::string& target_id,
WindowType type,
std::string* window_handle) = 0;
// Gets the size of the specified WebView. // Gets the size of the specified WebView.
virtual Status GetWindowSize(const std::string& id, virtual Status GetWindowSize(const std::string& id,
int* width, int* width,
......
...@@ -117,6 +117,34 @@ Status ChromeImpl::GetWebViewById(const std::string& id, WebView** web_view) { ...@@ -117,6 +117,34 @@ Status ChromeImpl::GetWebViewById(const std::string& id, WebView** web_view) {
return Status(kUnknownError, "web view not found"); return Status(kUnknownError, "web view not found");
} }
Status ChromeImpl::NewWindow(const std::string& target_id,
WindowType type,
std::string* window_handle) {
Status status = devtools_websocket_client_->ConnectIfNecessary();
if (status.IsError())
return status;
Window window;
status = GetWindow(target_id, &window);
if (status.IsError())
return Status(kNoSuchWindow);
base::DictionaryValue params;
params.SetString("url", "about:blank");
params.SetBoolean("newWindow", type == WindowType::kWindow);
params.SetBoolean("background", true);
std::unique_ptr<base::DictionaryValue> result;
status = devtools_websocket_client_->SendCommandAndGetResult(
"Target.createTarget", params, &result);
if (status.IsError())
return status;
if (!result->GetString("targetId", window_handle))
return Status(kUnknownError, "no targetId from createTarget");
return Status(kOk);
}
Status ChromeImpl::GetWindow(const std::string& target_id, Window* window) { Status ChromeImpl::GetWindow(const std::string& target_id, Window* window) {
Status status = devtools_websocket_client_->ConnectIfNecessary(); Status status = devtools_websocket_client_->ConnectIfNecessary();
if (status.IsError()) if (status.IsError())
......
...@@ -35,6 +35,9 @@ class ChromeImpl : public Chrome { ...@@ -35,6 +35,9 @@ class ChromeImpl : public Chrome {
Status GetWebViewIds(std::list<std::string>* web_view_ids, Status GetWebViewIds(std::list<std::string>* web_view_ids,
bool w3c_compliant) override; bool w3c_compliant) override;
Status GetWebViewById(const std::string& id, WebView** web_view) override; Status GetWebViewById(const std::string& id, WebView** web_view) override;
Status NewWindow(const std::string& target_id,
WindowType type,
std::string* window_handle) override;
Status GetWindowSize(const std::string& id, int* width, int* height) override; Status GetWindowSize(const std::string& id, int* width, int* height) override;
Status SetWindowSize(const std::string& target_id, Status SetWindowSize(const std::string& target_id,
int width, int height) override; int width, int height) override;
......
...@@ -36,6 +36,12 @@ Status StubChrome::GetWebViewById(const std::string& id, WebView** web_view) { ...@@ -36,6 +36,12 @@ Status StubChrome::GetWebViewById(const std::string& id, WebView** web_view) {
return Status(kOk); return Status(kOk);
} }
Status StubChrome::NewWindow(const std::string& target_id,
WindowType type,
std::string* window_handle) {
return Status(kOk);
}
Status StubChrome::GetWindowSize(const std::string& id, Status StubChrome::GetWindowSize(const std::string& id,
int* width, int* width,
int* height) { int* height) {
......
...@@ -29,6 +29,9 @@ class StubChrome : public Chrome { ...@@ -29,6 +29,9 @@ class StubChrome : public Chrome {
Status GetWebViewIds(std::list<std::string>* web_view_ids, Status GetWebViewIds(std::list<std::string>* web_view_ids,
bool w3c_compliant) override; bool w3c_compliant) override;
Status GetWebViewById(const std::string& id, WebView** web_view) override; Status GetWebViewById(const std::string& id, WebView** web_view) override;
Status NewWindow(const std::string& target_id,
WindowType type,
std::string* window_handle) override;
Status GetWindowSize(const std::string& id, int* width, int* height) override; Status GetWindowSize(const std::string& id, int* width, int* height) override;
Status SetWindowSize(const std::string& id, int width, int height) override; Status SetWindowSize(const std::string& id, int width, int height) override;
Status SetWindowRect(const std::string& target_id, Status SetWindowRect(const std::string& target_id,
......
...@@ -520,6 +520,10 @@ class ChromeDriver(object): ...@@ -520,6 +520,10 @@ class ChromeDriver(object):
{'windowHandle': 'current'}) {'windowHandle': 'current'})
return [size['width'], size['height']] return [size['width'], size['height']]
def NewWindow(self, window_type="window"):
return self.ExecuteCommand(Command.NEW_WINDOW,
{'type': window_type})
def GetWindowRect(self): def GetWindowRect(self):
rect = self.ExecuteCommand(Command.GET_WINDOW_RECT) rect = self.ExecuteCommand(Command.GET_WINDOW_RECT)
return [rect['width'], rect['height'], rect['x'], rect['y']] return [rect['width'], rect['height'], rect['x'], rect['y']]
......
...@@ -83,6 +83,8 @@ class Command(object): ...@@ -83,6 +83,8 @@ class Command(object):
GET_WINDOW_RECT = (_Method.GET, '/session/:sessionId/window/rect') GET_WINDOW_RECT = (_Method.GET, '/session/:sessionId/window/rect')
GET_WINDOW_SIZE = ( GET_WINDOW_SIZE = (
_Method.GET, '/session/:sessionId/window/:windowHandle/size') _Method.GET, '/session/:sessionId/window/:windowHandle/size')
NEW_WINDOW = (
_Method.POST, '/session/:sessionId/window/new')
GET_WINDOW_POSITION = ( GET_WINDOW_POSITION = (
_Method.GET, '/session/:sessionId/window/:windowHandle/position') _Method.GET, '/session/:sessionId/window/:windowHandle/position')
SET_WINDOW_SIZE = ( SET_WINDOW_SIZE = (
......
...@@ -184,6 +184,9 @@ HttpHandler::HttpHandler( ...@@ -184,6 +184,9 @@ HttpHandler::HttpHandler(
CommandMapping( CommandMapping(
kDelete, "session/:sessionId/window", kDelete, "session/:sessionId/window",
WrapToCommand("CloseWindow", base::BindRepeating(&ExecuteClose))), WrapToCommand("CloseWindow", base::BindRepeating(&ExecuteClose))),
CommandMapping(
kPost, "session/:sessionId/window/new",
WrapToCommand("NewWindow", base::BindRepeating(&ExecuteNewWindow))),
CommandMapping( CommandMapping(
kPost, "session/:sessionId/window", kPost, "session/:sessionId/window",
WrapToCommand("SwitchToWindow", WrapToCommand("SwitchToWindow",
......
...@@ -57,22 +57,6 @@ const int k3GThroughput = 750 * 1024; ...@@ -57,22 +57,6 @@ const int k3GThroughput = 750 * 1024;
const int k2GLatency = 300; const int k2GLatency = 300;
const int k2GThroughput = 250 * 1024; const int k2GThroughput = 250 * 1024;
const char kWindowHandlePrefix[] = "CDwindow-";
std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
return kWindowHandlePrefix + web_view_id;
}
bool WindowHandleToWebViewId(const std::string& window_handle,
std::string* web_view_id) {
if (!base::StartsWith(window_handle, kWindowHandlePrefix,
base::CompareCase::SENSITIVE)) {
return false;
}
*web_view_id = window_handle.substr(sizeof(kWindowHandlePrefix) - 1);
return true;
}
Status EvaluateScriptAndIgnoreResult(Session* session, std::string expression) { Status EvaluateScriptAndIgnoreResult(Session* session, std::string expression) {
WebView* web_view = nullptr; WebView* web_view = nullptr;
Status status = session->GetTargetWindow(&web_view); Status status = session->GetTargetWindow(&web_view);
......
...@@ -159,6 +159,8 @@ _INTEGRATION_NEGATIVE_FILTER = [ ...@@ -159,6 +159,8 @@ _INTEGRATION_NEGATIVE_FILTER = [
'PerfTest.*', 'PerfTest.*',
# HeadlessInvalidCertificateTest is sometimes flaky. # HeadlessInvalidCertificateTest is sometimes flaky.
'HeadlessInvalidCertificateTest.*', 'HeadlessInvalidCertificateTest.*',
# Similar issues with HeadlessChromeDriverTest.
'HeadlessChromeDriverTest.*',
# https://bugs.chromium.org/p/chromedriver/issues/detail?id=2277 # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2277
# RemoteBrowserTest requires extra setup. TODO(johnchen@chromium.org): # RemoteBrowserTest requires extra setup. TODO(johnchen@chromium.org):
# Modify the test so it runs correctly as isolated test. # Modify the test so it runs correctly as isolated test.
...@@ -213,6 +215,7 @@ _ANDROID_NEGATIVE_FILTER['chrome'] = ( ...@@ -213,6 +215,7 @@ _ANDROID_NEGATIVE_FILTER['chrome'] = (
'ChromeDriverTest.testCloseWindowUsingJavascript', 'ChromeDriverTest.testCloseWindowUsingJavascript',
# Android doesn't support headless mode # Android doesn't support headless mode
'HeadlessInvalidCertificateTest.*', 'HeadlessInvalidCertificateTest.*',
'HeadlessChromeDriverTest.*',
# Tests of the desktop Chrome launch process. # Tests of the desktop Chrome launch process.
'LaunchDesktopTest.*', 'LaunchDesktopTest.*',
# https://bugs.chromium.org/p/chromedriver/issues/detail?id=2737 # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2737
...@@ -413,6 +416,23 @@ class ChromeDriverTest(ChromeDriverBaseTestWithWebServer): ...@@ -413,6 +416,23 @@ class ChromeDriverTest(ChromeDriverBaseTestWithWebServer):
def testGetCurrentWindowHandle(self): def testGetCurrentWindowHandle(self):
self._driver.GetCurrentWindowHandle() self._driver.GetCurrentWindowHandle()
def _newWindowDoesNotFocus(self, window_type='window'):
current_handles = self._driver.GetWindowHandles()
self._driver.Load(self.GetHttpUrlForFile(
'/chromedriver/focus_blur_test.html'))
new_window = self._driver.NewWindow(window_type=window_type)
text = self._driver.FindElement('css selector', '#result').GetText()
self.assertTrue(new_window['handle'] not in current_handles)
self.assertTrue(new_window['handle'] in self._driver.GetWindowHandles())
self.assertEquals(text, 'PASS')
def testNewWindowDoesNotFocus(self):
self._newWindowDoesNotFocus(window_type='window')
def testNewTabDoesNotFocus(self):
self._newWindowDoesNotFocus(window_type='tab')
def testCloseWindow(self): def testCloseWindow(self):
self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html')) self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html'))
old_handles = self._driver.GetWindowHandles() old_handles = self._driver.GetWindowHandles()
...@@ -3405,6 +3425,29 @@ class HeadlessInvalidCertificateTest(ChromeDriverBaseTest): ...@@ -3405,6 +3425,29 @@ class HeadlessInvalidCertificateTest(ChromeDriverBaseTest):
self._driver.FindElement('css selector', '#link') self._driver.FindElement('css selector', '#link')
class HeadlessChromeDriverTest(ChromeDriverBaseTestWithWebServer):
"""End to end tests for ChromeDriver."""
def setUp(self):
self._driver = self.CreateDriver(chrome_switches=['--headless'])
def _newWindowDoesNotFocus(self, window_type='window'):
current_handles = self._driver.GetWindowHandles()
self._driver.Load(self.GetHttpUrlForFile(
'/chromedriver/focus_blur_test.html'))
new_window = self._driver.NewWindow(window_type=window_type)
text = self._driver.FindElement('css selector', '#result').GetText()
self.assertTrue(new_window['handle'] not in current_handles)
self.assertTrue(new_window['handle'] in self._driver.GetWindowHandles())
self.assertEquals(text, 'PASS')
def testNewWindowDoesNotFocus(self):
self._newWindowDoesNotFocus(window_type='window')
def testNewTabDoesNotFocus(self):
self._newWindowDoesNotFocus(window_type='tab')
class SupportIPv4AndIPv6(ChromeDriverBaseTest): class SupportIPv4AndIPv6(ChromeDriverBaseTest):
def testSupportIPv4AndIPv6(self): def testSupportIPv4AndIPv6(self):
has_ipv4 = False has_ipv4 = False
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
#include "chrome/test/chromedriver/session.h" #include "chrome/test/chromedriver/session.h"
#include "third_party/zlib/google/zip.h" #include "third_party/zlib/google/zip.h"
const char kWindowHandlePrefix[] = "CDwindow-";
std::string GenerateId() { std::string GenerateId() {
uint64_t msb = base::RandUint64(); uint64_t msb = base::RandUint64();
uint64_t lsb = base::RandUint64(); uint64_t lsb = base::RandUint64();
...@@ -547,3 +549,17 @@ bool SetSafeInt(base::DictionaryValue* dict, ...@@ -547,3 +549,17 @@ bool SetSafeInt(base::DictionaryValue* dict,
else else
return dict->SetDouble(path, in_value_64); return dict->SetDouble(path, in_value_64);
} }
std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
return kWindowHandlePrefix + web_view_id;
}
bool WindowHandleToWebViewId(const std::string& window_handle,
std::string* web_view_id) {
if (!base::StartsWith(window_handle, kWindowHandlePrefix,
base::CompareCase::SENSITIVE)) {
return false;
}
*web_view_id = window_handle.substr(sizeof(kWindowHandlePrefix) - 1);
return true;
}
...@@ -92,4 +92,11 @@ bool SetSafeInt(base::DictionaryValue* dict, ...@@ -92,4 +92,11 @@ bool SetSafeInt(base::DictionaryValue* dict,
const base::StringPiece path, const base::StringPiece path,
int64_t in_value_64); int64_t in_value_64);
// Provides WindowHandle to WebView method to maintain consistency across
// ChromeDriver.
std::string WebViewIdToWindowHandle(const std::string& web_view_id);
bool WindowHandleToWebViewId(const std::string& window_handle,
std::string* web_view_id);
#endif // CHROME_TEST_CHROMEDRIVER_UTIL_H_ #endif // CHROME_TEST_CHROMEDRIVER_UTIL_H_
...@@ -608,6 +608,38 @@ Status ExecuteExecuteAsyncScript(Session* session, ...@@ -608,6 +608,38 @@ Status ExecuteExecuteAsyncScript(Session* session,
return status; return status;
} }
Status ExecuteNewWindow(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string type = "";
// "type" can either be None or a string.
auto* type_param = params.FindKey("type");
if (!(!type_param || type_param->is_none() ||
params.GetString("type", &type)))
return Status(kInvalidArgument, "missing or invalid 'type'");
// By default, creates new tab.
Chrome::WindowType window_type = (type == "window")
? Chrome::WindowType::kWindow
: Chrome::WindowType::kTab;
std::string handle = "";
Status status =
session->chrome->NewWindow(session->window, window_type, &handle);
if (status.IsError())
return status;
auto results = std::make_unique<base::DictionaryValue>();
results->SetString("handle", WebViewIdToWindowHandle(handle));
results->SetString(
"type", (window_type == Chrome::WindowType::kWindow) ? "window" : "tab");
*value = std::move(results);
return Status(kOk);
}
Status ExecuteSwitchToFrame(Session* session, Status ExecuteSwitchToFrame(Session* session,
WebView* web_view, WebView* web_view,
const base::DictionaryValue& params, const base::DictionaryValue& params,
......
...@@ -56,6 +56,13 @@ Status ExecuteExecuteAsyncScript(Session* session, ...@@ -56,6 +56,13 @@ Status ExecuteExecuteAsyncScript(Session* session,
std::unique_ptr<base::Value>* value, std::unique_ptr<base::Value>* value,
Timeout* timeout); Timeout* timeout);
// Creates a new window/tab.
Status ExecuteNewWindow(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout);
// Changes the targeted frame for the given session. // Changes the targeted frame for the given session.
Status ExecuteSwitchToFrame(Session* session, Status ExecuteSwitchToFrame(Session* session,
WebView* web_view, WebView* web_view,
......
<html>
<head>
<title>No blur test</title>
<!-- This simple page indicates whether or not this page ever loses focus. -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="result">PASS</p>
</body>
<script>
window.onblur = function() {
document.getElementById("result").innerHTML = "FAIL";
};
</script>
</html>
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