Skip to content

Commit 793c1d7

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/feat/update-testcontainers-0.26'
2 parents 59cfdd0 + 2650494 commit 793c1d7

File tree

4 files changed

+195
-0
lines changed

4 files changed

+195
-0
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ postgres = []
5757
rabbitmq = []
5858
redis = []
5959
scylladb = []
60+
selenium = []
6061
solr = []
6162
surrealdb = []
6263
trufflesuite_ganachecli = []
@@ -140,6 +141,7 @@ azure_core = "0.30.1"
140141
azure_storage_blobs = "0.21.0"
141142
azure_storage = "0.21.0"
142143
base64 = "0.22.1"
144+
fantoccini = "0.21"
143145

144146
[[example]]
145147
name = "anvil"
@@ -188,3 +190,7 @@ required-features = ["zitadel", "postgres"]
188190
[[example]]
189191
name = "azurite"
190192
required-features = ["azurite"]
193+
194+
[[example]]
195+
name = "selenium"
196+
required-features = ["selenium"]

examples/selenium.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use testcontainers::runners::AsyncRunner;
2+
use testcontainers_modules::selenium::Selenium;
3+
4+
#[tokio::main]
5+
async fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
6+
let node = Selenium::new_firefox().start().await?;
7+
let driver_port = node
8+
.get_host_port_ipv4(testcontainers_modules::selenium::DRIVER_PORT)
9+
.await?;
10+
let driver_url = format!("http://127.0.0.1:{driver_port}");
11+
12+
let client = fantoccini::ClientBuilder::native()
13+
.connect(&driver_url)
14+
.await?;
15+
16+
let result = client.execute("return 2 + 2", vec![]).await?;
17+
let value = result.as_i64().unwrap();
18+
assert_eq!(value, 4);
19+
20+
println!("Calculation result from Selenium: {}", value);
21+
22+
client.close().await?;
23+
24+
Ok(())
25+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ pub mod rqlite;
169169
#[cfg_attr(docsrs, doc(cfg(feature = "scylladb")))]
170170
/// **scylladb** (distributed NoSQL wide-column data store) testcontainer
171171
pub mod scylladb;
172+
#[cfg(feature = "selenium")]
173+
#[cfg_attr(docsrs, doc(cfg(feature = "selenium")))]
174+
/// **Selenium** (browser automation) testcontainer
175+
pub mod selenium;
172176
#[cfg(feature = "solr")]
173177
#[cfg_attr(docsrs, doc(cfg(feature = "solr")))]
174178
/// **Apache Solr** (distributed search engine) testcontainer

src/selenium/mod.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
use testcontainers::{
2+
core::{ContainerPort, WaitFor},
3+
Image,
4+
};
5+
6+
const FIREFOX_IMAGE: &str = "selenium/standalone-firefox";
7+
const FIREFOX_TAG: &str = "144.0-geckodriver-0.36-grid-4.38.0-20251101";
8+
9+
const CHROME_IMAGE: &str = "selenium/standalone-chrome";
10+
const CHROME_TAG: &str = "136.0-chromedriver-136.0-grid-4.38.0-20251101";
11+
12+
/// Port where the Selenium Grid is listening.
13+
pub const DRIVER_PORT: ContainerPort = ContainerPort::Tcp(4444);
14+
/// Port where the noVNC WebUI is listening.
15+
pub const WEB_UI_PORT: ContainerPort = ContainerPort::Tcp(7900);
16+
17+
/// Module to work with [Selenium] inside of tests.
18+
///
19+
/// Starts an instance of Selenium Standalone (Firefox or Chrome). This uses either:
20+
/// - the official [`selenium/standalone-firefox` docker image], or
21+
/// - the official [`selenium/standalone-chrome` docker image].
22+
///
23+
/// # Example
24+
///
25+
/// ```
26+
/// use testcontainers_modules::{
27+
/// selenium::{self, Selenium},
28+
/// testcontainers::runners::SyncRunner,
29+
/// };
30+
///
31+
/// let selenium_instance = Selenium::new_firefox().start().unwrap();
32+
///
33+
/// let driver_port = selenium_instance
34+
/// .get_host_port_ipv4(selenium::DRIVER_PORT)
35+
/// .unwrap();
36+
/// let web_ui_port = selenium_instance
37+
/// .get_host_port_ipv4(selenium::WEB_UI_PORT)
38+
/// .unwrap();
39+
///
40+
/// let driver_url = format!("http://localhost:{driver_port}");
41+
/// let web_ui_url = format!("http://localhost:{web_ui_port}");
42+
/// ```
43+
///
44+
/// [Selenium]: https://www.selenium.dev/
45+
/// [`selenium/standalone-firefox` docker image]: https://hub.docker.com/r/selenium/standalone-firefox
46+
/// [`selenium/standalone-chrome` docker image]: https://hub.docker.com/r/selenium/standalone-chrome
47+
#[derive(Debug, Clone)]
48+
pub struct Selenium {
49+
image: String,
50+
tag: String,
51+
}
52+
53+
impl Selenium {
54+
/// Creates a new instance of a Selenium Standalone Firefox container.
55+
///
56+
/// Image: [`selenium/standalone-firefox`](https://hub.docker.com/r/selenium/standalone-firefox)
57+
pub fn new_firefox() -> Self {
58+
Self {
59+
image: FIREFOX_IMAGE.to_owned(),
60+
tag: FIREFOX_TAG.to_owned(),
61+
}
62+
}
63+
64+
/// Creates a new instance of a Selenium Standalone Chrome container.
65+
///
66+
/// Image: [`selenium/standalone-chrome`](https://hub.docker.com/r/selenium/standalone-chrome)
67+
pub fn new_chrome() -> Self {
68+
Self {
69+
image: CHROME_IMAGE.to_owned(),
70+
tag: CHROME_TAG.to_owned(),
71+
}
72+
}
73+
}
74+
75+
impl Default for Selenium {
76+
fn default() -> Self {
77+
Self::new_firefox()
78+
}
79+
}
80+
81+
impl Image for Selenium {
82+
fn name(&self) -> &str {
83+
&self.image
84+
}
85+
86+
fn tag(&self) -> &str {
87+
&self.tag
88+
}
89+
90+
fn ready_conditions(&self) -> Vec<WaitFor> {
91+
vec![WaitFor::message_on_stdout("Started Selenium Standalone")]
92+
}
93+
94+
fn expose_ports(&self) -> &[ContainerPort] {
95+
&[DRIVER_PORT, WEB_UI_PORT]
96+
}
97+
}
98+
99+
#[cfg(test)]
100+
mod tests {
101+
use testcontainers::runners::AsyncRunner;
102+
103+
use super::*;
104+
105+
#[tokio::test]
106+
async fn selenium_firefox_check_status() -> Result<(), Box<dyn std::error::Error + 'static>> {
107+
let selenium = Selenium::new_firefox();
108+
check_status_impl(selenium).await?;
109+
Ok(())
110+
}
111+
112+
#[tokio::test]
113+
async fn selenium_chrome_check_status() -> Result<(), Box<dyn std::error::Error + 'static>> {
114+
let selenium = Selenium::new_chrome();
115+
check_status_impl(selenium).await?;
116+
Ok(())
117+
}
118+
119+
async fn check_status_impl(
120+
selenium: Selenium,
121+
) -> Result<(), Box<dyn std::error::Error + 'static>> {
122+
let node = selenium.start().await?;
123+
124+
let driver_port = node.get_host_port_ipv4(DRIVER_PORT).await?;
125+
let web_ui_port = node.get_host_port_ipv4(WEB_UI_PORT).await?;
126+
127+
// Check Selenium Grid Status
128+
let status_url = format!("http://127.0.0.1:{driver_port}/status");
129+
let response = reqwest::get(&status_url).await?;
130+
assert!(response.status().is_success());
131+
let body = response.text().await?;
132+
assert!(body.contains("\"ready\": true"));
133+
134+
// Check WebUI
135+
let web_ui_url = format!("http://127.0.0.1:{web_ui_port}");
136+
let response = reqwest::get(&web_ui_url).await?;
137+
assert!(response.status().is_success());
138+
139+
Ok(())
140+
}
141+
142+
#[tokio::test]
143+
async fn selenium_firefox_run_js() -> Result<(), Box<dyn std::error::Error + 'static>> {
144+
let node = Selenium::new_firefox().start().await?;
145+
let driver_port = node.get_host_port_ipv4(DRIVER_PORT).await?;
146+
let driver_url = format!("http://127.0.0.1:{driver_port}");
147+
148+
let client = fantoccini::ClientBuilder::native()
149+
.connect(&driver_url)
150+
.await?;
151+
152+
let result = client.execute("return 1 + 1", vec![]).await?;
153+
let value = result.as_i64().unwrap();
154+
assert_eq!(value, 2);
155+
156+
client.close().await?;
157+
158+
Ok(())
159+
}
160+
}

0 commit comments

Comments
 (0)