Bug 1700093 - [geckodriver] Add support for "Get Element Shadow Root" r=webdriver-reviewers,whimboo,jgraham

This adds the support for "Get Element Shadow Root" from the WebDriver specification:
https://w3c.github.io/webdriver/#get-element-shadow-root

Differential Revision: https://phabricator.services.mozilla.com/D110938
This commit is contained in:
David Burns 2021-12-03 14:02:21 +00:00
Родитель 37608acf34
Коммит 4ef99ace6a
8 изменённых файлов: 125 добавлений и 80 удалений

Просмотреть файл

@ -231,6 +231,8 @@ pub enum Command {
GetElementText(LegacyWebElement),
#[serde(rename = "WebDriver:GetPageSource")]
GetPageSource,
#[serde(rename = "WebDriver:GetShadowRoot")]
GetShadowRoot { id: String },
#[serde(rename = "WebDriver:GetTimeouts")]
GetTimeouts,
#[serde(rename = "WebDriver:GetTitle")]

Просмотреть файл

@ -43,11 +43,11 @@ use webdriver::command::WebDriverCommand::{
FindElement, FindElementElement, FindElementElements, FindElements, FullscreenWindow, Get,
GetActiveElement, GetAlertText, GetCSSValue, GetCookies, GetCurrentUrl, GetElementAttribute,
GetElementProperty, GetElementRect, GetElementTagName, GetElementText, GetNamedCookie,
GetPageSource, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles, GetWindowRect, GoBack,
GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow, MinimizeWindow, NewSession,
NewWindow, PerformActions, Print, Refresh, ReleaseActions, SendAlertText, SetTimeouts,
SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame, SwitchToWindow,
TakeElementScreenshot, TakeScreenshot,
GetPageSource, GetShadowRoot, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles,
GetWindowRect, GoBack, GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow,
MinimizeWindow, NewSession, NewWindow, PerformActions, Print, Refresh, ReleaseActions,
SendAlertText, SetTimeouts, SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame,
SwitchToWindow, TakeElementScreenshot, TakeScreenshot,
};
use webdriver::command::{
ActionsParameters, AddCookieParameters, GetNamedCookieParameters, GetParameters,
@ -57,7 +57,8 @@ use webdriver::command::{
};
use webdriver::command::{WebDriverCommand, WebDriverMessage};
use webdriver::common::{
Cookie, Date, FrameId, LocatorStrategy, WebElement, ELEMENT_KEY, FRAME_KEY, WINDOW_KEY,
Cookie, Date, FrameId, LocatorStrategy, ShadowRoot, WebElement, ELEMENT_KEY, FRAME_KEY,
SHADOW_KEY, WINDOW_KEY,
};
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use webdriver::response::{
@ -322,6 +323,30 @@ impl MarionetteSession {
Ok(WebElement(id))
}
/// Converts a Marionette JSON response into a `ShadowRoot`.
fn to_shadow_root(&self, json_data: &Value) -> WebDriverResult<ShadowRoot> {
let data = try_opt!(
json_data.as_object(),
ErrorStatus::UnknownError,
"Failed to convert data to an object"
);
let shadow_root = data.get(SHADOW_KEY);
let value = try_opt!(
shadow_root,
ErrorStatus::UnknownError,
"Failed to extract shadow root from Marionette response"
);
let id = try_opt!(
value.as_str(),
ErrorStatus::UnknownError,
"Failed to convert shadow root reference value to string"
)
.to_string();
Ok(ShadowRoot(id))
}
fn next_command_id(&mut self) -> MessageId {
self.command_id += 1;
self.command_id
@ -631,6 +656,14 @@ impl MarionetteSession {
.collect(),
)))
}
GetShadowRoot(_) => {
let shadow_root = self.to_shadow_root(try_opt!(
resp.result.get("value"),
ErrorStatus::UnknownError,
"Failed to find value field"
))?;
WebDriverResponse::Generic(ValueResponse(serde_json::to_value(shadow_root)?))
}
GetActiveElement => {
let element = self.to_web_element(try_opt!(
resp.result.get("value"),
@ -806,6 +839,11 @@ fn try_convert_to_marionette_message(
GetPageSource => Some(Command::WebDriver(
MarionetteWebDriverCommand::GetPageSource,
)),
GetShadowRoot(ref e) => Some(Command::WebDriver(
MarionetteWebDriverCommand::GetShadowRoot {
id: e.clone().to_string(),
},
)),
GetTitle => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTitle)),
GetWindowHandle => Some(Command::WebDriver(
MarionetteWebDriverCommand::GetWindowHandle,

Просмотреть файл

@ -1,19 +0,0 @@
[get.py]
[test_element_not_found]
expected: FAIL
[test_no_browsing_context]
expected: FAIL
[test_no_top_browsing_context]
expected: FAIL
[test_get_shadow_root]
expected: FAIL
[test_element_stale]
expected: FAIL
[test_no_shadow_root]
expected: FAIL

Просмотреть файл

@ -1,55 +0,0 @@
[user_prompts.py]
[test_ignore[capabilities0-alert\]]
expected: FAIL
[test_accept[capabilities0-prompt-\]]
expected: FAIL
[test_accept_and_notify[capabilities0-alert-None\]]
expected: FAIL
[test_default[confirm-False\]]
expected: FAIL
[test_dismiss[capabilities0-prompt-None\]]
expected: FAIL
[test_accept[capabilities0-confirm-True\]]
expected: FAIL
[test_dismiss_and_notify[capabilities0-alert-None\]]
expected: FAIL
[test_accept_and_notify[capabilities0-prompt-\]]
expected: FAIL
[test_dismiss_and_notify[capabilities0-confirm-False\]]
expected: FAIL
[test_dismiss_and_notify[capabilities0-prompt-None\]]
expected: FAIL
[test_default[prompt-None\]]
expected: FAIL
[test_default[alert-None\]]
expected: FAIL
[test_ignore[capabilities0-prompt\]]
expected: FAIL
[test_dismiss[capabilities0-alert-None\]]
expected: FAIL
[test_accept[capabilities0-alert-None\]]
expected: FAIL
[test_dismiss[capabilities0-confirm-False\]]
expected: FAIL
[test_accept_and_notify[capabilities0-confirm-True\]]
expected: FAIL
[test_ignore[capabilities0-confirm\]]
expected: FAIL

Просмотреть файл

@ -42,6 +42,7 @@ pub enum WebDriverCommand<T: WebDriverExtensionCommand> {
FindElementElement(WebElement, LocatorParameters),
FindElementElements(WebElement, LocatorParameters),
GetActiveElement,
GetShadowRoot(WebElement),
IsDisplayed(WebElement),
IsSelected(WebElement),
GetElementAttribute(WebElement, String),
@ -167,6 +168,15 @@ impl<U: WebDriverExtensionRoute> WebDriverMessage<U> {
WebDriverCommand::FindElementElements(element, serde_json::from_str(raw_body)?)
}
Route::GetActiveElement => WebDriverCommand::GetActiveElement,
Route::GetShadowRoot => {
let element_id = try_opt!(
params.get("elementId"),
ErrorStatus::InvalidArgument,
"Missing elementId parameter"
);
let element = WebElement(element_id.as_str().into());
WebDriverCommand::GetShadowRoot(element)
}
Route::IsDisplayed => {
let element_id = try_opt!(
params.get("elementId"),

Просмотреть файл

@ -9,6 +9,7 @@ use std::fmt::{self, Display, Formatter};
pub static ELEMENT_KEY: &str = "element-6066-11e4-a52e-4f735466cecf";
pub static FRAME_KEY: &str = "frame-075b-4da1-b6ba-e579c2d3230a";
pub static SHADOW_KEY: &str = "shadow-6066-11e4-a52e-4f735466cecf";
pub static WINDOW_KEY: &str = "window-fcc6-11e5-b4f8-330a88ab9d7f";
pub static MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
@ -67,6 +68,40 @@ pub enum LocatorStrategy {
XPath,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ShadowRoot(pub String);
// private
#[derive(Serialize, Deserialize)]
struct ShadowRootObject {
#[serde(rename = "shadow-6066-11e4-a52e-4f735466cecf")]
id: String,
}
impl Serialize for ShadowRoot {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ShadowRootObject { id: self.0.clone() }.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ShadowRoot {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).map(|ShadowRootObject { id }| ShadowRoot(id))
}
}
impl Display for ShadowRoot {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct WebElement(pub String);
@ -224,6 +259,16 @@ mod tests {
assert!(serde_json::from_value::<LocatorStrategy>(json!("foo")).is_err());
}
#[test]
fn test_json_shadowroot() {
assert_ser_de(&ShadowRoot("shadow".into()), json!({SHADOW_KEY: "shadow"}));
}
#[test]
fn test_json_shadowroot_invalid() {
assert!(serde_json::from_value::<ShadowRoot>(json!({"invalid":"shadow"})).is_err());
}
#[test]
fn test_json_webelement() {
assert_ser_de(&WebElement("elem".into()), json!({ELEMENT_KEY: "elem"}));

Просмотреть файл

@ -14,6 +14,12 @@ use std::io;
#[derive(Debug, PartialEq)]
pub enum ErrorStatus {
/// The [element]'s [ShadowRoot] is not attached to the active document,
/// or the reference is stale
/// [element]: ../common/struct.WebElement.html
/// [ShadowRoot]: ../common/struct.ShadowRoot.html
DetachedShadowRoot,
/// The [`ElementClick`] command could not be completed because the
/// [element] receiving the events is obscuring the element that was
/// requested clicked.
@ -91,6 +97,12 @@ pub enum ErrorStatus {
/// [command]: ../command/index.html
NoSuchFrame,
/// An [element]'s [ShadowRoot] was not found attached to the element.
///
/// [element]: ../common/struct.WebElement.html
/// [ShadowRoot]: ../common/struct.ShadowRoot.html
NoSuchShadowRoot,
/// A [command] to switch to a window could not be satisfied because the
/// window could not be found.
///
@ -168,6 +180,7 @@ impl ErrorStatus {
pub fn error_code(&self) -> &'static str {
use self::ErrorStatus::*;
match *self {
DetachedShadowRoot => "detached shadow root",
ElementClickIntercepted => "element click intercepted",
ElementNotInteractable => "element not interactable",
ElementNotSelectable => "element not selectable",
@ -184,6 +197,7 @@ impl ErrorStatus {
NoSuchCookie => "no such cookie",
NoSuchElement => "no such element",
NoSuchFrame => "no such frame",
NoSuchShadowRoot => "no such shadow root",
NoSuchWindow => "no such window",
ScriptTimeout => "script timeout",
SessionNotCreated => "session not created",
@ -203,6 +217,7 @@ impl ErrorStatus {
pub fn http_status(&self) -> StatusCode {
use self::ErrorStatus::*;
match *self {
DetachedShadowRoot => StatusCode::NOT_FOUND,
ElementClickIntercepted => StatusCode::BAD_REQUEST,
ElementNotInteractable => StatusCode::BAD_REQUEST,
ElementNotSelectable => StatusCode::BAD_REQUEST,
@ -219,6 +234,7 @@ impl ErrorStatus {
NoSuchCookie => StatusCode::NOT_FOUND,
NoSuchElement => StatusCode::NOT_FOUND,
NoSuchFrame => StatusCode::NOT_FOUND,
NoSuchShadowRoot => StatusCode::NOT_FOUND,
NoSuchWindow => StatusCode::NOT_FOUND,
ScriptTimeout => StatusCode::INTERNAL_SERVER_ERROR,
SessionNotCreated => StatusCode::INTERNAL_SERVER_ERROR,
@ -241,6 +257,7 @@ impl From<String> for ErrorStatus {
fn from(s: String) -> ErrorStatus {
use self::ErrorStatus::*;
match &*s {
"detached shadow root" => DetachedShadowRoot,
"element click intercepted" => ElementClickIntercepted,
"element not interactable" | "element not visible" => ElementNotInteractable,
"element not selectable" => ElementNotSelectable,
@ -256,6 +273,7 @@ impl From<String> for ErrorStatus {
"no such alert" => NoSuchAlert,
"no such element" => NoSuchElement,
"no such frame" => NoSuchFrame,
"no such shadow root" => NoSuchShadowRoot,
"no such window" => NoSuchWindow,
"script timeout" => ScriptTimeout,
"session not created" => SessionNotCreated,

Просмотреть файл

@ -137,6 +137,11 @@ pub fn standard_routes<U: WebDriverExtensionRoute>() -> Vec<(Method, &'static st
"/session/{sessionId}/element/active",
Route::GetActiveElement,
),
(
Method::GET,
"/session/{sessionId}/element/{elementId}/shadow",
Route::GetShadowRoot,
),
(
Method::GET,
"/session/{sessionId}/element/{elementId}/displayed",
@ -319,6 +324,7 @@ pub enum Route<U: WebDriverExtensionRoute> {
FindElementElement,
FindElementElements,
GetActiveElement,
GetShadowRoot,
IsDisplayed,
IsSelected,
GetElementAttribute,