Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 109 additions & 1 deletion src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde_json::Value as Json;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use webdriver::command::WebDriverCommand;
use webdriver::common::FrameId;
use webdriver::common::{FrameId, SHADOW_KEY};

/// Web element reference.
///
Expand Down Expand Up @@ -87,6 +87,114 @@ impl Element {
pub fn element_id(&self) -> ElementRef {
ElementRef(self.element.0.clone())
}

/// Get the shadow root of the element.
///
/// Returns a [`ShadowRoot`] that can be used to find elements within the shadow DOM.
///
/// See [12.6 Get Element Shadow Root](https://www.w3.org/TR/webdriver2/#get-element-shadow-root)
/// of the WebDriver standard.
#[cfg_attr(docsrs, doc(alias = "Get Element Shadow Root"))]
pub async fn shadow_root(&self) -> Result<ShadowRoot, error::CmdError> {
let res = self
.client
.issue(WebDriverCommand::GetShadowRoot(self.element.clone()))
.await?;
let shadow = parse_shadow_root(res)?;
Ok(ShadowRoot {
client: self.client.clone(),
shadow_root: shadow,
})
}
}

/// A shadow root of an element on the current page.
///
/// A shadow root encapsulates a shadow DOM tree, which is a separate DOM subtree
/// attached to an element. This struct provides methods to find elements within
/// the shadow DOM.
///
/// See [Shadow Root](https://www.w3.org/TR/webdriver2/#dfn-shadow-roots) of the WebDriver standard.
#[derive(Clone, Debug)]
pub struct ShadowRoot {
/// The high-level WebDriver client, for sending commands.
pub(crate) client: Client,
/// The encapsulated ShadowRoot struct.
pub(crate) shadow_root: webdriver::common::ShadowRoot,
}

/// Extract the `ShadowRoot` from a `GetShadowRoot` command response.
fn parse_shadow_root(res: Json) -> Result<webdriver::common::ShadowRoot, error::CmdError> {
let mut res = match res {
Json::Object(o) => o,
res => return Err(error::CmdError::NotW3C(res)),
};

if !res.contains_key(SHADOW_KEY) {
return Err(error::CmdError::NotW3C(Json::Object(res)));
}

match res.remove(SHADOW_KEY) {
Some(Json::String(shadow_id)) => Ok(webdriver::common::ShadowRoot(shadow_id)),
Some(v) => {
res.insert(SHADOW_KEY.to_string(), v);
Err(error::CmdError::NotW3C(Json::Object(res)))
}
None => Err(error::CmdError::NotW3C(Json::Object(res))),
}
}

impl ShadowRoot {
/// Get back the [`Client`] hosting this `ShadowRoot`.
pub fn client(self) -> Client {
self.client
}
}

/// [Element Retrieval](https://www.w3.org/TR/webdriver2/#element-retrieval) for Shadow Root
impl ShadowRoot {
/// Find the first element within the shadow root that matches the given [`Locator`].
///
/// See [12.4.1 Find Element From Shadow Root](https://www.w3.org/TR/webdriver2/#find-element-from-shadow-root)
/// of the WebDriver standard.
#[cfg_attr(docsrs, doc(alias = "Find Element From Shadow Root"))]
pub async fn find(&self, search: Locator<'_>) -> Result<Element, error::CmdError> {
let res = self
.client
.issue(WebDriverCommand::FindShadowRootElement(
self.shadow_root.clone(),
search.into_parameters(),
))
.await?;
let e = self.client.parse_lookup(res)?;
Ok(Element {
client: self.client.clone(),
element: e,
})
}

/// Find all elements within the shadow root that match the given [`Locator`].
///
/// See [12.4.2 Find Elements From Shadow Root](https://www.w3.org/TR/webdriver2/#find-elements-from-shadow-root)
/// of the WebDriver standard.
#[cfg_attr(docsrs, doc(alias = "Find Elements From Shadow Root"))]
pub async fn find_all(&self, search: Locator<'_>) -> Result<Vec<Element>, error::CmdError> {
let res = self
.client
.issue(WebDriverCommand::FindShadowRootElements(
self.shadow_root.clone(),
search.into_parameters(),
))
.await?;
let array = self.client.parse_lookup_all(res)?;
Ok(array
.into_iter()
.map(move |e| Element {
client: self.client.clone(),
element: e,
})
.collect())
}
}

/// An HTML form on the current page.
Expand Down
15 changes: 13 additions & 2 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ impl WebDriverCompatibleCommand for Wcmd {
WebDriverCommand::FindElementElements(ref p, _) => {
base.join(&format!("element/{}/elements", p.0))
}
WebDriverCommand::FindShadowRootElement(ref shadow, _) => {
base.join(&format!("shadow/{}/element", shadow.0))
}
WebDriverCommand::FindShadowRootElements(ref shadow, _) => {
base.join(&format!("shadow/{}/elements", shadow.0))
}
WebDriverCommand::GetActiveElement => base.join("element/active"),
WebDriverCommand::IsDisplayed(ref we) => {
base.join(&format!("element/{}/displayed", we.0))
Expand Down Expand Up @@ -142,9 +148,12 @@ impl WebDriverCompatibleCommand for Wcmd {
WebDriverCommand::TakeElementScreenshot(ref we) => {
base.join(&format!("element/{}/screenshot", we.0))
}
WebDriverCommand::GetShadowRoot(ref we) => {
base.join(&format!("element/{}/shadow", we.0))
}
WebDriverCommand::Print(..) => base.join("print"),
WebDriverCommand::Status => unreachable!(),
_ => unimplemented!(),
_ => unimplemented!("{:?}", self),
}
}

Expand Down Expand Up @@ -194,7 +203,9 @@ impl WebDriverCompatibleCommand for Wcmd {
WebDriverCommand::FindElement(ref loc)
| WebDriverCommand::FindElements(ref loc)
| WebDriverCommand::FindElementElement(_, ref loc)
| WebDriverCommand::FindElementElements(_, ref loc) => {
| WebDriverCommand::FindElementElements(_, ref loc)
| WebDriverCommand::FindShadowRootElement(_, ref loc)
| WebDriverCommand::FindShadowRootElements(_, ref loc) => {
body = Some(serde_json::to_string(loc).unwrap());
method = Method::POST;
}
Expand Down
Loading