Coder Social home page Coder Social logo

Comments (8)

stevepryde avatar stevepryde commented on July 21, 2024

I don't really have any experience with Actix, but I'll see if I can help.

Actix has its own async runtime, doesn't it? thirtyfour uses reqwest by default, which uses tokio internally, whether using sync or async, and tokio might not play nice with the runtime from Actix. I'm not sure whether the async-std client surf would fare any better.

Were you running selenium or using the webdriver (chromedriver or geckodriver for example) directly?

Does Actix have its own http client? It seems to provide actix_web::client. It might be possible to implement the two traits, RemoteConnectionAsyncCreate and RemoteConnectionAsync for this http client, and that should work.

If you look at src/http_async/reqwest_async.rs you'll see the implementation for reqwest. It should be fairly straightforward to adapt this for Actix_web::client. Then it's just a matter of defining your WebDriver type like this:

type WebDriver = GenericWebDriver<ActixHttpClient>;

You may need to specify no-default-features in order to avoid clashes with the built-in WebDriver type.

Let me know if you need more info. I realise this is a bit clunky at the moment.

from thirtyfour.

stevepryde avatar stevepryde commented on July 21, 2024

I have just published v0.15.0 which simplifies the traits a little.

You now only need to implement a single trait for the Actix http client. The src/http_async/reqwest_async.rs source will still provide a pretty good example of what you want. If you adapt this for Actix_web::client I think that will work. The WebDriver type definition is still as above.

from thirtyfour.

OskarPersson avatar OskarPersson commented on July 21, 2024

Thank you for the quick update!

I got it working by implementing a quick and dirty (sync) version for ureq:

use std::fmt::Debug;
use thirtyfour::sync::http_sync::connection_sync::WebDriverHttpClientSync;
use thirtyfour::sync::prelude::*;
use thirtyfour::sync::GenericWebDriver;
use thirtyfour::{
    common::command::{Command, RequestMethod},
    error::{WebDriverError, WebDriverResult},
    SessionId,
};
use ureq;

/// Synchronous connection to the remote WebDriver server.
#[derive(Debug)]
pub struct UreqDriver {
    url: String,
    client: ureq::Agent,
}

type WebDriver = GenericWebDriver<UreqDriver>;

impl WebDriverHttpClientSync for UreqDriver {
    fn create(remote_server_addr: &str) -> WebDriverResult<Self> {
        Ok(UreqDriver {
            url: remote_server_addr.trim_end_matches('/').to_owned(),
            client: ureq::agent(),
        })
    }

    /// Execute the specified command and return the data as serde_json::Value.
    fn execute(
        &self,
        session_id: &SessionId,
        command: Command,
    ) -> WebDriverResult<serde_json::Value> {
        let request_data = command.format_request(session_id);
        let url = self.url.clone() + &request_data.url;
        let mut request = match request_data.method {
            RequestMethod::Get => self.client.get(&url),
            RequestMethod::Post => self.client.post(&url),
            RequestMethod::Delete => self.client.delete(&url),
        };

        let resp = if let Some(x) = request_data.body {
            request.send_json(x)
        } else {
            request.call()
        };

        match resp.status() {
            200..=399 => Ok(resp.into_json()?),
            400..=599 => {
                let status = resp.status();
                let body: serde_json::Value =
                    resp.into_json().unwrap_or(serde_json::Value::Null);
                Err(WebDriverError::parse(status, body))
            }
            _ => unreachable!(),
        }
    }
}

Though a big part of my issues above was that I used actix_web::test::init_service instead of actix_web::test::TestServer which I guess doesn't create a real server and therefore wasn't accessable at all 🙃

Here is what I do instead:

use crate::routes::routes;
use actix_web::{test, App};

#[actix_rt::test]
async fn test_index_get() -> WebDriverResult<()> {
    let app = test::start(|| App::new().configure(routes));

    let port = app.addr().port();
    let req = app.get("/");
    let resp = req.send().await.unwrap();
    assert!(resp.status().is_success());

    let caps = DesiredCapabilities::chrome();
    let driver = WebDriver::new("http://localhost:4444", &caps)?;

    driver.get(format!("http://localhost:{}", port))?;

    Ok(())
}

Using sync is not currently an issue for me but async would of course be an improvement. Especially since I could get rid of ureq (or some other client outside of actix) as an extra dependency. Also, I don't seem to be able to import thirtyfour::sync without the blocking feature (which adds tokio and and reqwest which isn't used in my case), is that correct?

Finally, here are some of the errors I couldn't solve when trying to implement the actix-web client:

error[E0277]: `tests::e2e::tests::ActixDriverAsync` doesn't implement `std::fmt::Debug`
  --> src/tests/e2e.rs:25:10
   |
25 |     impl WebDriverHttpClientAsync for ActixDriverAsync {
   |          ^^^^^^^^^^^^^^^^^^^^^^^^ `tests::e2e::tests::ActixDriverAsync` cannot be formatted using `{:?}`
   |
  ::: /home/oskar/.cargo/registry/src/github.com-1ecc6299db9ec823/thirtyfour-0.15.0/src/http_async/connection_async.rs:16:37
   |
16 | pub trait WebDriverHttpClientAsync: Debug + Send + Sync {
   |                                     ----- required by this bound in `thirtyfour::http_async::connection_async::WebDriverHttpClientAsync`
   |
   = help: the trait `std::fmt::Debug` is not implemented for `tests::e2e::tests::ActixDriverAsync`
   = note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`

error[E0277]: `std::rc::Rc<awc::ClientConfig>` cannot be shared between threads safely
  --> src/tests/e2e.rs:25:10
   |
25 |     impl WebDriverHttpClientAsync for ActixDriverAsync {
   |          ^^^^^^^^^^^^^^^^^^^^^^^^ `std::rc::Rc<awc::ClientConfig>` cannot be shared between threads safely
   |
  ::: /home/oskar/.cargo/registry/src/github.com-1ecc6299db9ec823/thirtyfour-0.15.0/src/http_async/connection_async.rs:16:52
   |
16 | pub trait WebDriverHttpClientAsync: Debug + Send + Sync {
   |                                                    ---- required by this bound in `thirtyfour::http_async::connection_async::WebDriverHttpClientAsync`
   |
   = help: within `tests::e2e::tests::ActixDriverAsync`, the trait `std::marker::Sync` is not implemented for `std::rc::Rc<awc::ClientConfig>`
   = note: required because it appears within the type `awc::Client`
   = note: required because it appears within the type `tests::e2e::tests::ActixDriverAsync`

error[E0277]: `std::rc::Rc<awc::ClientConfig>` cannot be sent between threads safely
  --> src/tests/e2e.rs:25:10
   |
25 |     impl WebDriverHttpClientAsync for ActixDriverAsync {
   |          ^^^^^^^^^^^^^^^^^^^^^^^^ `std::rc::Rc<awc::ClientConfig>` cannot be sent between threads safely
   |
  ::: /home/oskar/.cargo/registry/src/github.com-1ecc6299db9ec823/thirtyfour-0.15.0/src/http_async/connection_async.rs:16:45
   |
16 | pub trait WebDriverHttpClientAsync: Debug + Send + Sync {
   |                                             ---- required by this bound in `thirtyfour::http_async::connection_async::WebDriverHttpClientAsync`
   |
   = help: within `tests::e2e::tests::ActixDriverAsync`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<awc::ClientConfig>`
   = note: required because it appears within the type `awc::Client`
   = note: required because it appears within the type `tests::e2e::tests::ActixDriverAsync`

from thirtyfour.

stevepryde avatar stevepryde commented on July 21, 2024

Your async example looks like you had the awc::ClientConfig wrapped in Rc. Is that in your code or is that in the actix http client code?

You may be able to resolve this by wrapping the client in Arc (and possibly Mutex as well?)
i.e.

client: Arc<Mutex<Client>>

That would automatically implement Send + Sync for you

from thirtyfour.

stevepryde avatar stevepryde commented on July 21, 2024

I don't have an easy way to work on this. If someone can provide a PR that would be great.

from thirtyfour.

9876691 avatar 9876691 commented on July 21, 2024

So I have thirtyfour working async in an actix project.

https://github.com/contor-systems/contor

An example test from https://github.com/contor-systems/contor/blob/master/tests/registration_test.rs is

mod common;

use dotenv::dotenv;
use std::path::Path;
use thirtyfour::prelude::*;

// let's set up the sequence of steps we want the browser to take
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn registration() -> WebDriverResult<()> {
    dotenv().ok();

    let driver = common::Config::new().get_driver().await?;

    driver.get("http://localhost:9095").await?;

    // Click the search button.
    let elem_button = driver
        .find_element(By::Css("button[type='submit']"))
        .await?;
    elem_button.click().await?;

    assert!(driver
        .page_source()
        .await?
        .contains("Invalid email or password"));

    let email = common::random_email();

    // Let's go and register
    driver
        .find_element(By::LinkText("SIGN UP"))
        .await?
        .click()
        .await?;
    driver
        .find_element(By::Id("email"))
        .await?
        .send_keys(&email)
        .await?;
    driver
        .find_element(By::Id("password"))
        .await?
        .send_keys(&email)
        .await?;
    driver
        .find_element(By::Id("confirm_password"))
        .await?
        .send_keys(&email)
        .await?;
    driver
        .find_element(By::Css("button[type='submit']"))
        .await?
        .click()
        .await?;

    let cookie = driver.get_cookie("session").await;

    assert!(cookie.is_ok());

    driver.get("http://localhost:9095/auth/sign_out").await?;

    driver
        .screenshot(Path::new("./target/registration.png"))
        .await?;

    let cookie = driver.get_cookie("session").await;

    assert!(cookie.is_err());

    Ok(())
}

from thirtyfour.

stevepryde avatar stevepryde commented on July 21, 2024

This looks good. It also tells me that there are probably no changes required on our end to support this.
Are you able to elaborate on what this part does?

let driver = common::Config::new().get_driver().await?;

I assume that sets up a new thirtyfour::WebDriver instance and returns it?

It also looks like this test assumes the web service is already running on localhost:9095 when the test is executed. I think what the OP wanted was something that could be done from within mod test in the server source code itself, and run as doctests. From my perspective, provided someone can instantiate the service and access it via localhost in a browser, thirtyfour should work fine since it is entirely independent of the web service and only operates the browser similar to how an end user would.

from thirtyfour.

9876691 avatar 9876691 commented on July 21, 2024

This looks good. It also tells me that there are probably no changes required on our end to support this.
Are you able to elaborate on what this part does?

let driver = common::Config::new().get_driver().await?;

I assume that sets up a new thirtyfour::WebDriver instance and returns it?

Yes, that's correct.

It also looks like this test assumes the web service is already running on localhost:9095 when the test is executed. I think what the OP wanted was something that could be done from within mod test in the server source code itself, and run as doctests. From my perspective, provided someone can instantiate the service and access it via localhost in a browser, thirtyfour should work fine since it is entirely independent of the web service and only operates the browser similar to how an end user would.

Agreed, I was previously using https://www.cypress.io/ and that's how that works.

So for example in github actions I do something like

      - name: Integraton Testing
        run: |
          target/x86_64-unknown-linux-musl/release/contor &
          chromedriver --whitelisted-ips="" &
          cargo test --release --target x86_64-unknown-linux-musl

from thirtyfour.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.