зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
37608acf34
Коммит
4ef99ace6a
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче