lw_webdriver/session.rs
1//! Sessions allow you to control tabs
2
3use json::*;
4use std::time::Duration;
5use std::result::Result;
6use crate::enums::*;
7use crate::timeouts::*;
8use crate::tab::*;
9use crate::error::*;
10use std::process::{Command, Stdio};
11use std::thread;
12use log::{debug, info, warn, error};
13use std::rc::Rc;
14use crate::http_requests::*;
15
16/// This is the more important object.
17/// Tabs can be accessed within the session.
18///
19/// # Example
20///
21/// ```rust
22/// use lw_webdriver::{session::Session, enums::Browser};
23///
24/// let mut session = Session::new(Browser::Firefox, false).unwrap();
25///
26/// // accessing default tab
27/// session.tabs[0].navigate("http://example.com/").unwrap();
28///
29/// // creating a new tab and access it
30/// session.open_tab().unwrap();
31/// session.tabs[1].navigate("https://mubelotix.dev/").unwrap();
32/// ```
33pub struct Session {
34 id: Rc<String>,
35 /// Contains every manually created tabs and default tab.
36 /// Do not contains tabs created by web pages with javascript unless you call [update_tabs()](https://to.do/).
37 pub tabs: Vec<Tab>,
38 webdriver_process: Option<std::process::Child>,
39}
40
41impl Session {
42 /// Create a session of a specific [browser](https://to.do/).
43 /// Headless mean that the browser will be opened but not displayed (useful for servers).
44 /// The crate will request a webdriver server at http://localhost:4444.
45 /// If no webdriver is listening, one will be launched, but the program ([geckodriver](https://to.do/) or [chromedriver](https://to.do/))
46 /// must be located at the same place than the running program.
47 ///
48 /// # Example
49 ///
50 /// ```rust
51 /// # use lw_webdriver::{session::Session, enums::Browser};
52 /// let mut session = Session::new(Browser::Firefox, false).unwrap();
53 /// ```
54 pub fn new(browser: Browser, headless: bool) -> Result<Self, WebdriverError> {
55 info!{"Creating a session..."};
56 let result = Session::new_session(browser, headless);
57
58 if let Err(WebdriverError::FailedRequest) = result {
59 warn!{"No webdriver launched."}
60 if cfg!(unix) {
61 if browser == Browser::Firefox {
62 info!{"Launching geckodriver..."}
63 let p = Command::new("./geckodriver")
64 .stdout(Stdio::null())
65 .stderr(Stdio::null())
66 .spawn()
67 .expect("Failed to start process.");
68 thread::sleep(Duration::from_millis(2000));
69 let result = Session::new_session(browser, headless);
70 if let Ok(mut result) = result {
71 info!{"Session created successfully."}
72 result.webdriver_process = Some(p);
73 return Ok(result);
74 } else if let Err(e) = result {
75 error!("Failed to create session. error : {:?}.", e);
76 return Err(e);
77 }
78 } else {
79 info!{"Launching chromedriver..."}
80 let p = Command::new("./chromedriver")
81 .arg("--port=4444")
82 .stdout(Stdio::null())
83 .stderr(Stdio::null())
84 .spawn()
85 .expect("Failed to start process");
86 thread::sleep(Duration::from_millis(2000));
87 let result = Session::new_session(browser, headless);
88 if let Ok(mut result) = result {
89 info!{"Session created successfully."}
90 result.webdriver_process = Some(p);
91 return Ok(result);
92 } else if let Err(e) = result{
93 error!("Failed to create session. error : {:?}.", e);
94 return Err(e);
95 }
96 }
97 } else {
98 panic!("Please launch the webdriver manually.")
99 }
100 } else {
101 return result;
102 }
103
104 result
105 }
106
107 fn new_session(browser: Browser, headless: bool) -> Result<Self, WebdriverError> {
108 // Detect platform
109 let platform = Platform::current();
110 if let Platform::Unknow = platform {
111 return Err(WebdriverError::UnsupportedPlatform);
112 }
113
114 // Generate capabilities
115 let post_data = match browser {
116 Browser::Firefox => {
117 if headless {
118 object!{
119 "capabilities" => object!{
120 "alwaysMatch" => object!{
121 "platformName" => platform.to_string(),
122 "browserName" => browser.to_string(),
123 "moz:firefoxOptions" => object! {
124 "args" => array!{"-headless"}
125 },
126 }
127 }
128 }
129 } else {
130 object!{
131 "capabilities" => object!{
132 "alwaysMatch" => object!{
133 "platformName" => platform.to_string(),
134 "browserName" => browser.to_string()
135 }
136 }
137 }
138 }
139 },
140 Browser::Chrome => {
141 if headless {
142 object!{
143 "capabilities" => object!{
144 "alwaysMatch" => object!{
145 "platformName" => platform.to_string(),
146 "browserName" => browser.to_string(),
147 "goog:chromeOptions" => object! {
148 "args" => array!{"-headless"}
149 }
150 }
151 }
152 }
153 } else {
154 object!{
155 "capabilities" => object!{
156 "alwaysMatch" => object!{
157 "platformName" => platform.to_string(),
158 "browserName" => browser.to_string()
159 }
160 }
161 }
162 }
163 }
164 };
165
166 // Send request
167 let session_id = new_session(&post_data.to_string())?;
168 let mut session = Session {
169 id: Rc::new(session_id),
170 tabs: Vec::new(),
171 webdriver_process: None
172 };
173
174 session.update_tabs()?;
175
176 Ok(session)
177 }
178
179 /// Create a new tab in the session.
180 /// The tab will be directly accessible from the session (no call to [update_tabs()](https://to.do/) needed).
181 ///
182 /// # Example
183 ///
184 /// ```rust
185 /// # use lw_webdriver::{session::Session, enums::Browser};
186 /// let mut session = Session::new(Browser::Firefox, false).unwrap();
187 ///
188 /// assert_eq!(session.tabs.len(), 1); // default tab is already opened
189 /// session.open_tab().unwrap();
190 /// assert_eq!(session.tabs.len(), 2); // new tab is accessible
191 /// ```
192 pub fn open_tab(&mut self) -> Result<usize, WebdriverError> {
193 let tab_id = new_tab(&self.id)?;
194 let new_tab = Tab::new_from(tab_id, Rc::clone(&self.id));
195 self.tabs.push(new_tab);
196
197 Ok(self.tabs.len() - 1)
198 }
199
200 /// When a tab is created with [open_tab()](https://to.do/) method, it is accessible directly.
201 /// But sometimes a tab is created by someone else (from a web page with javascript) and you don't want to care about it!
202 /// This tab will not be accessible by your program because you never asked it.
203 /// However if you want to access every open tab, call this function.
204 ///
205 /// # Example
206 ///
207 /// ```rust
208 /// # use lw_webdriver::{session::Session, enums::Browser};
209 /// # use std::thread::sleep;
210 /// # use std::time::Duration;
211 /// let mut session = Session::new(Browser::Firefox, false).unwrap();
212 ///
213 /// // only the default tab is open
214 /// assert_eq!(session.tabs.len(), 1);
215 ///
216 /// // load a website
217 /// session.tabs[0].navigate("https://mubelotix.dev/webdriver_tests/open_tab.html").unwrap();
218 ///
219 /// // observe what is happening
220 /// sleep(Duration::from_secs(5));
221 ///
222 /// // a tab has been opened by another tab but you never asked for it
223 /// // you can see two tabs displayed
224 /// // but this crate don't show the useless one
225 /// assert_eq!(session.tabs.len(), 1);
226 ///
227 /// // if you want to access it, call this function
228 /// session.update_tabs().unwrap();
229 ///
230 /// // now you can access two tabs!
231 /// assert_eq!(session.tabs.len(), 2);
232 /// ```
233 pub fn update_tabs(&mut self) -> Result<(), WebdriverError> {
234 let tabs_id = get_open_tabs(&self.id)?;
235 for tab_id in tabs_id {
236 if self.tabs.iter().position(|element| *element.id == tab_id).is_none() {
237 self.tabs.push(Tab::new_from(tab_id, Rc::clone(&self.id)));
238 }
239 }
240
241 Ok(())
242 }
243
244 /// This is a simple method getting [timeouts](https://to.do/) of the session.
245 pub fn get_timeouts(&self) -> Result<Timeouts, WebdriverError> {
246 Ok(get_timeouts(&self.id)?)
247 }
248
249 /// This is a simple method setting [timeouts](https://to.do/) of the session.
250 pub fn set_timeouts(&mut self, timeouts: Timeouts) -> Result<(), WebdriverError> {
251 Ok(set_timeouts(&self.id, timeouts)?)
252 }
253}
254
255impl PartialEq for Session {
256 fn eq(&self, other: &Self) -> bool {
257 self.get_id() == other.get_id()
258 }
259}
260
261impl WebdriverObject for Session {
262 fn get_id(&self) -> &String {
263 &self.id
264 }
265}
266
267impl Drop for Session {
268 #[allow(unused_must_use)]
269 fn drop(&mut self) {
270 self.tabs.clear();
271 if self.webdriver_process.is_some() {
272 warn!("Killing webdriver process (may fail silently)");
273 self.webdriver_process.take().unwrap().kill();
274 }
275 }
276}