Skip to content

Commit

Permalink
Reland "Add support of session.status PureBiDi command"
Browse files Browse the repository at this point in the history
This is a reland of commit 9906eb9

Original change's description:
> Add support of session.status PureBiDi command
>
> With this commit user is able to establish a WebSocket connection not
> bound to any session. ChromeDriver is able to handle session.status
> static BiDi command.
>
> Bug: chromedriver:4496
> Change-Id: Ib70062244f4f2ad8eb18c3d3faca18d8e0ea3341
> Validate-Test-Flakiness: skip
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4905411
> Reviewed-by: Maksim Sadym <sadym@chromium.org>
> Commit-Queue: Vladimir Nechaev <nechaev@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1207004}

Bug: chromedriver:4496
Change-Id: I4ee19cde4cc9675f9eca4646d4e584aef03def7f
Validate-Test-Flakiness: skip
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4938596
Reviewed-by: Thiago Perrotta <tperrotta@chromium.org>
Commit-Queue: Vladimir Nechaev <nechaev@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1210074}
  • Loading branch information
nechaev-chromium authored and Chromium LUCI CQ committed Oct 16, 2023
1 parent 5c3692b commit 71017ed
Show file tree
Hide file tree
Showing 11 changed files with 1,307 additions and 217 deletions.
12 changes: 10 additions & 2 deletions chrome/test/chromedriver/client/chromedriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import psutil
import sys
import time
import urllib.parse
import util

import psutil

import command_executor
from command_executor import Command
from webelement import WebElement
Expand Down Expand Up @@ -359,6 +360,13 @@ def ExecuteCommand(self, command, params={}):
def CreateWebSocketConnection(self):
return WebSocketConnection(self._server_url, self._session_id)

def CreateWebSocketConnectionIPv6(self):
url_components = urllib.parse.urlparse(self._server_url)
new_url = urllib.parse.urlunparse(
url_components._replace(
netloc=('%s:%d' % ('[::1]', url_components.port))))
return WebSocketConnection(new_url, self._session_id)

def GetWindowHandles(self):
return self.ExecuteCommand(Command.GET_WINDOW_HANDLES)

Expand Down
22 changes: 14 additions & 8 deletions chrome/test/chromedriver/client/websocket_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,25 @@
from exceptions import WebSocketTimeoutException

class WebSocketCommands:
CREATE_WEBSOCKET = \
ATTACH_WEBSOCKET_TO_SESSION = \
'/session/:sessionId'
CREATE_UNBOUND_WEBSOCKET = \
'/session'
SEND_OVER_WEBSOCKET = \
'/session/:sessionId/chromium/send_command_from_websocket'

class WebSocketConnection(object):
def __init__(self, server_url, session_id):
def __init__(self, server_url, session_id = None):
self._server_url = server_url.replace('http', 'ws')
self._session_id = session_id
self._command_id = 0
cmd_params = {'sessionId': session_id}
path = CommandExecutor.CreatePath(
WebSocketCommands.CREATE_WEBSOCKET, cmd_params)
if session_id is None:
path = CommandExecutor.CreatePath(
WebSocketCommands.CREATE_UNBOUND_WEBSOCKET, {})
else:
path = CommandExecutor.CreatePath(
WebSocketCommands.ATTACH_WEBSOCKET_TO_SESSION,
{'sessionId': session_id})
self._websocket = websocket.create_connection(self._server_url + path)
self._responses = {}
self._events = []
Expand All @@ -52,9 +58,9 @@ def SendCommand(self, cmd_params):
return cmd_params['id']

def TakeEvents(self):
result = self._events;
result = self._events
self._events = []
return result;
return result

def TryGetResponse(self, command_id, channel = None):
if channel not in self._responses:
Expand Down Expand Up @@ -96,7 +102,7 @@ def WaitForResponse(self, command_id, channel = None):
raise WebSocketTimeoutException()

def Close(self):
self._websocket.close();
self._websocket.close()

def __enter__(self):
return self
Expand Down
57 changes: 48 additions & 9 deletions chrome/test/chromedriver/commands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@
#include "chrome/test/chromedriver/session_thread_map.h"
#include "chrome/test/chromedriver/util.h"

namespace {
void WriteChromeDriverExtendedStatus(base::Value::Dict& info) {
base::Value::Dict build;
build.Set("version", kChromeDriverVersion);
info.Set("build", std::move(build));

base::Value::Dict os;
os.Set("name", base::SysInfo::OperatingSystemName());
os.Set("version", base::SysInfo::OperatingSystemVersion());
os.Set("arch", base::SysInfo::OperatingSystemArchitecture());
info.Set("os", std::move(os));
}
} // namespace

void ExecuteGetStatus(const base::Value::Dict& params,
const std::string& session_id,
const CommandCallback& callback) {
Expand All @@ -46,20 +60,35 @@ void ExecuteGetStatus(const base::Value::Dict& params,
kChromeDriverProductShortName));

// ChromeDriver specific data:
base::Value::Dict build;
build.Set("version", kChromeDriverVersion);
info.Set("build", std::move(build));

base::Value::Dict os;
os.Set("name", base::SysInfo::OperatingSystemName());
os.Set("version", base::SysInfo::OperatingSystemVersion());
os.Set("arch", base::SysInfo::OperatingSystemArchitecture());
info.Set("os", std::move(os));
WriteChromeDriverExtendedStatus(info);

callback.Run(Status(kOk), std::make_unique<base::Value>(std::move(info)),
std::string(), kW3CDefault);
}

void ExecuteBidiGetStatus(const base::Value::Dict& params,
const std::string& session_id,
const CommandCallback& callback) {
base::Value::Dict info;
info.Set("ready", false);
if (session_id.empty()) {
info.Set("message",
base::StringPrintf("%s does not yet support BiDi-only sessions.",
kChromeDriverProductShortName));
} else {
// The error message is borrowed from BiDiMapper code.
// See bidiMapper/domains/session/SessionProcessor.ts of chromium-bidi
// repository.
info.Set("message", "already connected");
}

// ChromeDriver specific data:
WriteChromeDriverExtendedStatus(info);

callback.Run(Status(kOk), std::make_unique<base::Value>(std::move(info)),
session_id, kW3CDefault);
}

void ExecuteCreateSession(SessionThreadMap* session_thread_map,
const Command& init_session_cmd,
const base::Value::Dict& params,
Expand All @@ -83,6 +112,16 @@ void ExecuteCreateSession(SessionThreadMap* session_thread_map,
init_session_cmd.Run(params, new_id, callback);
}

void ExecuteBidiCreateSession(const base::Value::Dict& params,
const std::string& session_id,
const CommandCallback& callback) {
Status new_session_not_supported = {
kSessionNotCreated,
base::StringPrintf("%s does not yet support BiDi-only sessions.",
kChromeDriverProductShortName)};
callback.Run(new_session_not_supported, nullptr, session_id, kW3CDefault);
}

namespace {

void OnGetSession(const base::WeakPtr<size_t>& session_remaining_count,
Expand Down
10 changes: 10 additions & 0 deletions chrome/test/chromedriver/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,23 @@ void ExecuteGetStatus(const base::Value::Dict& params,
const std::string& session_id,
const CommandCallback& callback);

// Gets session.status about ChromeDriver.
void ExecuteBidiGetStatus(const base::Value::Dict& params,
const std::string& session_id,
const CommandCallback& callback);

// Creates a new session.
void ExecuteCreateSession(SessionThreadMap* session_thread_map,
const Command& init_session_cmd,
const base::Value::Dict& params,
const std::string& host,
const CommandCallback& callback);

// Creates a new BiDi session.
void ExecuteBidiCreateSession(const base::Value::Dict& params,
const std::string& session_id,
const CommandCallback& callback);

// Gets all sessions
void ExecuteGetSessions(const Command& session_capabilities_command,
SessionThreadMap* session_thread_map,
Expand Down
65 changes: 61 additions & 4 deletions chrome/test/chromedriver/commands_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,25 @@
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/session_commands.h"
#include "chrome/test/chromedriver/window_commands.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/selenium-atoms/atoms.h"

using testing::ContainsRegex;
using testing::Eq;
using testing::HasSubstr;
using testing::Optional;
using testing::Pointee;

namespace {

void AssertGetStatusExtendedData(base::Value::Dict* dict) {
ASSERT_TRUE(dict->FindByDottedPath("os.name"));
ASSERT_TRUE(dict->FindByDottedPath("os.version"));
ASSERT_TRUE(dict->FindByDottedPath("os.arch"));
ASSERT_TRUE(dict->FindByDottedPath("build.version"));
}

void OnGetStatus(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
Expand All @@ -46,10 +60,7 @@ void OnGetStatus(const Status& status,
absl::optional<bool> ready = dict->FindBool("ready");
ASSERT_TRUE(ready.has_value() && ready.value());
ASSERT_TRUE(dict->Find("message"));
ASSERT_TRUE(dict->FindByDottedPath("os.name"));
ASSERT_TRUE(dict->FindByDottedPath("os.version"));
ASSERT_TRUE(dict->FindByDottedPath("os.arch"));
ASSERT_TRUE(dict->FindByDottedPath("build.version"));
AssertGetStatusExtendedData(dict);
}

} // namespace
Expand All @@ -61,6 +72,52 @@ TEST(CommandsTest, GetStatus) {

namespace {

void OnBidiGetStatusNoSession(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
base::Value::Dict* dict = value->GetIfDict();
ASSERT_TRUE(dict);
ASSERT_THAT(dict->FindBool("ready"), Optional(Eq(false)));
ASSERT_THAT(dict->FindString("message"),
Pointee(HasSubstr("does not yet support BiDi-only sessions")));
AssertGetStatusExtendedData(dict);
}

} // namespace

TEST(CommandsTest, BidiGetStatusNoSession) {
base::Value::Dict params;
ExecuteBidiGetStatus(params, std::string(),
base::BindRepeating(&OnBidiGetStatusNoSession));
}

namespace {

void OnBidiGetStatusWithSession(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
base::Value::Dict* dict = value->GetIfDict();
ASSERT_TRUE(dict);
ASSERT_THAT(dict->FindBool("ready"), Optional(Eq(false)));
ASSERT_THAT(dict->FindString("message"),
Pointee(HasSubstr("already connected")));
AssertGetStatusExtendedData(dict);
}

} // namespace

TEST(CommandsTest, BidiGetStatusWithSession) {
base::Value::Dict params;
ExecuteBidiGetStatus(params, "some_session",
base::BindRepeating(&OnBidiGetStatusWithSession));
}

namespace {

void ExecuteStubGetSession(int* count,
const base::Value::Dict& params,
const std::string& session_id,
Expand Down

0 comments on commit 71017ed

Please sign in to comment.