Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensions feature #92

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
14 changes: 14 additions & 0 deletions examples/extensions/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "extensions"
version = "0.1.0"
authors = [""]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
fantoccini = {path="../../"}
futures = "0.3.0"
tokio = {version="0.2.21", features=["macros"]}
serde_json = "1.0.56"
serde = "1.0.114"
78 changes: 78 additions & 0 deletions examples/extensions/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/// This example is only work with geckodriver
extern crate fantoccini;
extern crate tokio;
extern crate serde;
extern crate serde_json;

use fantoccini::{Client, ExtensionCommand, Method, WebDriverExtensionCommand};
use serde::Serialize;
use serde_json::Value;
use std::io::Error;
use std::thread::sleep;
use std::time::Duration;

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct AddonInstallParameters {
pub path: String,
pub temporary: Option<bool>,
}

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct AddonUninstallParameters {
pub id: String,
}

#[derive(Clone, Debug, PartialEq)]
pub enum GeckoExtensionCommand {
InstallAddon(AddonInstallParameters),
UninstallAddon(AddonUninstallParameters)
}

impl ExtensionCommand for GeckoExtensionCommand {
fn method(&self) -> Method {
Method::POST
}

fn endpoint(&self) -> &str {
match self {
Self::InstallAddon(_)=>"/moz/addon/install",
Self::UninstallAddon(_)=>"/moz/addon/uninstall"
}
}
}

impl WebDriverExtensionCommand for GeckoExtensionCommand {
fn parameters_json(&self) -> Option<Value> {
Some(match self {
Self::InstallAddon(param)=>serde_json::to_value(param).unwrap(),
Self::UninstallAddon(param)=>serde_json::to_value(param).unwrap()
})
}
}

#[tokio::main]
async fn main()-> Result<(), Error> {
let mut client = Client::new("http://localhost:4444").await.unwrap();

let install_command = GeckoExtensionCommand::InstallAddon(AddonInstallParameters {
path: String::from("/path/to/addon.xpi"),
temporary: Some(true)
});
let ins_res = client.extension_command(install_command).await.expect("Can not install the addon");

println!("Install Response: {:#?}", ins_res);

let addon_id = ins_res.as_str().unwrap();

sleep(Duration::from_secs(5));

let uninstall_command = GeckoExtensionCommand::UninstallAddon(AddonUninstallParameters{
id: String::from(addon_id)
});

let uns_res = client.extension_command(uninstall_command).await.expect("Can not uninstall the addon");

println!("Uninstall Reponse: {:#?}", uns_res);

Ok(())
}
67 changes: 38 additions & 29 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,25 +193,26 @@ impl<'a> From<Locator<'a>> for webdriver::command::LocatorParameters {
}
}

pub use crate::session::Client;
pub use crate::session::{Client, ExtensionCommand, VoidExtensionCommand};
pub use webdriver::command::WebDriverExtensionCommand;

/// A single element on the current page.
#[derive(Clone, Debug, Serialize)]
pub struct Element {
pub struct Element<T: ExtensionCommand > {
#[serde(skip_serializing)]
client: Client,
client: Client<T>,
#[serde(flatten)]
element: webdriver::common::WebElement,
}

/// An HTML form on the current page.
#[derive(Clone, Debug)]
pub struct Form {
client: Client,
pub struct Form <T: ExtensionCommand + 'static> {
client: Client<T>,
form: webdriver::common::WebElement,
}

impl Client {
impl <T: ExtensionCommand +'static> Client <T> {
/// Create a new `Client` associated with a new WebDriver session on the server at the given
/// URL.
///
Expand Down Expand Up @@ -671,7 +672,7 @@ impl Client {
}

/// Switches to the frame specified at the index.
pub async fn enter_frame(mut self, index: Option<u16>) -> Result<Client, error::CmdError> {
pub async fn enter_frame(mut self, index: Option<u16>) -> Result<Client<T>, error::CmdError> {
let params = SwitchToFrameParameters {
id: index.map(FrameId::Short),
};
Expand All @@ -680,18 +681,18 @@ impl Client {
}

/// Switches to the parent of the frame the client is currently contained within.
pub async fn enter_parent_frame(mut self) -> Result<Client, error::CmdError> {
pub async fn enter_parent_frame(mut self) -> Result<Client<T>, error::CmdError> {
self.issue(WebDriverCommand::SwitchToParentFrame).await?;
Ok(self)
}

/// Find an element on the page.
pub async fn find(&mut self, search: Locator<'_>) -> Result<Element, error::CmdError> {
pub async fn find(&mut self, search: Locator<'_>) -> Result<Element<T>, error::CmdError> {
self.by(search.into()).await
}

/// Find elements on the page.
pub async fn find_all(&mut self, search: Locator<'_>) -> Result<Vec<Element>, error::CmdError> {
pub async fn find_all(&mut self, search: Locator<'_>) -> Result<Vec<Element<T>>, error::CmdError> {
let res = self
.issue(WebDriverCommand::FindElements(search.into()))
.await?;
Expand All @@ -713,7 +714,7 @@ impl Client {
/// the page.
pub async fn wait_for<F, FF>(&mut self, mut is_ready: F) -> Result<(), error::CmdError>
where
F: FnMut(&mut Client) -> FF,
F: FnMut(&mut Client<T>) -> FF,
FF: Future<Output = Result<bool, error::CmdError>>,
{
while !is_ready(self).await? {}
Expand All @@ -726,7 +727,7 @@ impl Client {
/// While this currently just spins and yields, it may be more efficient than this in the
/// future. In particular, in time, it may only run `is_ready` again when an event occurs on
/// the page.
pub async fn wait_for_find(&mut self, search: Locator<'_>) -> Result<Element, error::CmdError> {
pub async fn wait_for_find(&mut self, search: Locator<'_>) -> Result<Element<T>, error::CmdError> {
let s: webdriver::command::LocatorParameters = search.into();
loop {
match self
Expand Down Expand Up @@ -770,7 +771,7 @@ impl Client {
/// Locate a form on the page.
///
/// Through the returned `Form`, HTML forms can be filled out and submitted.
pub async fn form(&mut self, search: Locator<'_>) -> Result<Form, error::CmdError> {
pub async fn form(&mut self, search: Locator<'_>) -> Result<Form<T>, error::CmdError> {
let l = search.into();
let res = self.issue(WebDriverCommand::FindElement(l)).await?;
let f = self.parse_lookup(res)?;
Expand Down Expand Up @@ -864,12 +865,20 @@ impl Client {
}
}

/// Executes a browser specific extension command.
///
/// You can install or uninstall browser extensions and control other browser
/// specific features with this method.
pub async fn extension_command(&mut self, command: T)->Result<Json, error::CmdError> {
self.issue(WebDriverCommand::Extension(command)).await
}

// helpers

async fn by(
&mut self,
locator: webdriver::command::LocatorParameters,
) -> Result<Element, error::CmdError> {
) -> Result<Element<T>, error::CmdError> {
let res = self.issue(WebDriverCommand::FindElement(locator)).await?;
let e = self.parse_lookup(res)?;
Ok(Element {
Expand Down Expand Up @@ -943,7 +952,7 @@ impl Client {
}
}

impl Element {
impl <T: ExtensionCommand + 'static> Element <T> {
/// Look up an [attribute] value for this element by name.
///
/// `Ok(None)` is returned if the element does not have the given attribute.
Expand Down Expand Up @@ -999,7 +1008,7 @@ impl Element {
}

/// Find the first matching descendant element.
pub async fn find(&mut self, search: Locator<'_>) -> Result<Element, error::CmdError> {
pub async fn find(&mut self, search: Locator<'_>) -> Result<Element<T>, error::CmdError> {
let res = self
.client
.issue(WebDriverCommand::FindElementElement(
Expand All @@ -1014,7 +1023,7 @@ impl Element {
})
}
/// Find all matching descendant elements.
pub async fn find_all(&mut self, search: Locator<'_>) -> Result<Vec<Element>, error::CmdError> {
pub async fn find_all(&mut self, search: Locator<'_>) -> Result<Vec<Element<T>>, error::CmdError> {
let res = self
.client
.issue(WebDriverCommand::FindElementElements(
Expand All @@ -1035,7 +1044,7 @@ impl Element {
/// Simulate the user clicking on this element.
///
/// Note that since this *may* result in navigation, we give up the handle to the element.
pub async fn click(mut self) -> Result<Client, error::CmdError> {
pub async fn click(mut self) -> Result<Client<T>, error::CmdError> {
let cmd = WebDriverCommand::ElementClick(self.element);
let r = self.client.issue(cmd).await?;
if r.is_null() || r.as_object().map(|o| o.is_empty()).unwrap_or(false) {
Expand Down Expand Up @@ -1074,15 +1083,15 @@ impl Element {
}

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

/// Follow the `href` target of the element matching the given CSS selector *without* causing a
/// click interaction.
///
/// Note that since this *may* result in navigation, we give up the handle to the element.
pub async fn follow(mut self) -> Result<Client, error::CmdError> {
pub async fn follow(mut self) -> Result<Client<T>, error::CmdError> {
let cmd = WebDriverCommand::GetElementAttribute(self.element, "href".to_string());
let href = self.client.issue(cmd).await?;
let href = match href {
Expand All @@ -1104,7 +1113,7 @@ impl Element {
}

/// Find and click an `option` child element by its `value` attribute.
pub async fn select_by_value(mut self, value: &str) -> Result<Client, error::CmdError> {
pub async fn select_by_value(mut self, value: &str) -> Result<Client<T>, error::CmdError> {
let locator = format!("option[value='{}']", value);
let locator = webdriver::command::LocatorParameters {
using: webdriver::common::LocatorStrategy::CSSSelector,
Expand All @@ -1122,7 +1131,7 @@ impl Element {
}

/// Switches to the frame contained within the element.
pub async fn enter_frame(self) -> Result<Client, error::CmdError> {
pub async fn enter_frame(self) -> Result<Client<T>, error::CmdError> {
let Self {
mut client,
element,
Expand All @@ -1137,7 +1146,7 @@ impl Element {
}
}

impl Form {
impl <T: ExtensionCommand +'static> Form <T> {
/// Find a form input using the given `locator` and set its value to `value`.
pub async fn set(
&mut self,
Expand Down Expand Up @@ -1180,15 +1189,15 @@ impl Form {
/// Submit this form using the first available submit button.
///
/// `false` is returned if no submit button was not found.
pub async fn submit(self) -> Result<Client, error::CmdError> {
pub async fn submit(self) -> Result<Client<T>, error::CmdError> {
self.submit_with(Locator::Css("input[type=submit],button[type=submit]"))
.await
}

/// Submit this form using the button matched by the given selector.
///
/// `false` is returned if a matching button was not found.
pub async fn submit_with(mut self, button: Locator<'_>) -> Result<Client, error::CmdError> {
pub async fn submit_with(mut self, button: Locator<'_>) -> Result<Client<T>, error::CmdError> {
let locator = WebDriverCommand::FindElementElement(self.form, button.into());
let res = self.client.issue(locator).await?;
let submit = self.client.parse_lookup(res)?;
Expand All @@ -1207,7 +1216,7 @@ impl Form {
/// Submit this form using the form submit button with the given label (case-insensitive).
///
/// `false` is returned if a matching button was not found.
pub async fn submit_using(self, button_label: &str) -> Result<Client, error::CmdError> {
pub async fn submit_using(self, button_label: &str) -> Result<Client<T>, error::CmdError> {
let escaped = button_label.replace('\\', "\\\\").replace('"', "\\\"");
let btn = format!(
"input[type=submit][value=\"{}\" i],\
Expand All @@ -1225,7 +1234,7 @@ impl Form {
///
/// Note that since no button is actually clicked, the `name=value` pair for the submit button
/// will not be submitted. This can be circumvented by using `submit_sneaky` instead.
pub async fn submit_direct(mut self) -> Result<Client, error::CmdError> {
pub async fn submit_direct(mut self) -> Result<Client<T>, error::CmdError> {
let mut args = vec![via_json!(&self.form)];
self.client.fixup_elements(&mut args);
// some sites are silly, and name their submit button "submit". this ends up overwriting
Expand Down Expand Up @@ -1260,7 +1269,7 @@ impl Form {
mut self,
field: &str,
value: &str,
) -> Result<Client, error::CmdError> {
) -> Result<Client<T>, error::CmdError> {
let mut args = vec![via_json!(&self.form), Json::from(field), Json::from(value)];
self.client.fixup_elements(&mut args);
let cmd = webdriver::command::JavascriptCommandParameters {
Expand Down Expand Up @@ -1292,7 +1301,7 @@ impl Form {
}

/// Get back the [`Client`] hosting this `Form`.
pub fn client(self) -> Client {
pub fn client(self) -> Client<T> {
self.client
}
}
Loading