Skip to content

Commit 1441635

Browse files
Merge pull request #9 from equalsraf/tb-other-browsers
Support for other browsers
2 parents 96a20d4 + 752a6f2 commit 1441635

File tree

9 files changed

+212
-35
lines changed

9 files changed

+212
-35
lines changed

src/bin/www.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ extern crate webdriver_client;
22
use webdriver_client::*;
33
use webdriver_client::messages::{LocationStrategy, ExecuteCmd};
44
use webdriver_client::firefox::GeckoDriver;
5+
use webdriver_client::chrome::ChromeDriver;
56

67
extern crate rustyline;
78
use rustyline::error::ReadlineError;
@@ -10,6 +11,8 @@ use rustyline::Editor;
1011
extern crate clap;
1112
use clap::{App, Arg};
1213

14+
extern crate stderrlog;
15+
1316
fn execute_function(name: &str, args: &str, sess: &DriverSession) -> Result<(), Error> {
1417
match name {
1518
"back" => try!(sess.back()),
@@ -27,6 +30,11 @@ fn execute_function(name: &str, args: &str, sess: &DriverSession) -> Result<(),
2730
println!("#{} {}", idx, elem.outer_html()?);
2831
}
2932
}
33+
"frames" => {
34+
for (idx, elem) in sess.find_elements("iframe", LocationStrategy::Css)?.iter().enumerate() {
35+
println!("#{} {}", idx, elem.raw_reference());
36+
}
37+
}
3038
"windows" => {
3139
for (idx, handle) in sess.get_window_handles()?.iter().enumerate() {
3240
println!("#{} {}", idx, handle)
@@ -42,6 +50,14 @@ fn execute_function(name: &str, args: &str, sess: &DriverSession) -> Result<(),
4250
other => println!("{}", other),
4351
}
4452
}
53+
"switchframe" => {
54+
let arg = args.trim();
55+
if arg.is_empty() {
56+
try!(sess.switch_to_frame(JsonValue::Null));
57+
} else {
58+
try!(sess.switch_to_frame(try!(Element::new(sess, arg.to_string()).reference())));
59+
}
60+
}
4561
_ => println!("Unknown function: \"{}\"", name),
4662
}
4763
Ok(())
@@ -59,18 +75,48 @@ fn main() {
5975
.help("Attach to a running webdriver")
6076
.value_name("URL")
6177
.takes_value(true))
78+
.arg(Arg::with_name("driver")
79+
.short("D")
80+
.long("driver")
81+
.possible_values(&["geckodriver", "chromedriver"])
82+
.default_value("geckodriver")
83+
.takes_value(true))
84+
.arg(Arg::with_name("verbose")
85+
.short("v")
86+
.multiple(true)
87+
.help("Increases verbose"))
6288
.get_matches();
6389

90+
stderrlog::new()
91+
.module("webdriver_client")
92+
.verbosity(matches.occurrences_of("verbose") as usize)
93+
.init()
94+
.expect("Unable to initialize logging in stderr");
95+
6496
let sess = match matches.value_of("attach-to") {
6597
Some(url) => HttpDriverBuilder::default()
6698
.url(url)
6799
.build().unwrap()
68100
.session()
69101
.expect("Unable to attach to WebDriver session"),
70-
None => GeckoDriver::spawn()
71-
.expect("Unable to start geckodriver")
72-
.session()
73-
.expect("Unable to start Geckodriver session"),
102+
None => match matches.value_of("driver").unwrap() {
103+
"geckodriver" => {
104+
GeckoDriver::spawn()
105+
.expect("Unable to start geckodriver")
106+
.session()
107+
.expect("Unable to start Geckodriver session")
108+
}
109+
"chromedriver" => {
110+
ChromeDriver::spawn()
111+
.expect("Unable to start chromedriver")
112+
.session()
113+
.expect("Unable to start chromedriver session")
114+
}
115+
unsupported => {
116+
// should be unreachable see Arg::possible_values()
117+
panic!("Unsupported driver: {}", unsupported);
118+
}
119+
}
74120
};
75121

76122
let mut rl = Editor::<()>::new();

src/chrome.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use super::*;
2+
3+
use std::process::{Command, Child, Stdio};
4+
use std::thread;
5+
use std::time::Duration;
6+
7+
use super::util;
8+
9+
pub struct ChromeDriverBuilder {
10+
port: Option<u16>,
11+
kill_on_drop: bool,
12+
}
13+
14+
impl ChromeDriverBuilder {
15+
pub fn new() -> Self {
16+
ChromeDriverBuilder {
17+
port: None,
18+
kill_on_drop: true,
19+
}
20+
}
21+
pub fn port(mut self, port: u16) -> Self {
22+
self.port = Some(port);
23+
self
24+
}
25+
pub fn kill_on_drop(mut self, kill: bool) -> Self {
26+
self.kill_on_drop = kill;
27+
self
28+
}
29+
pub fn spawn(self) -> Result<ChromeDriver, Error> {
30+
let port = util::check_tcp_port(self.port)?;
31+
32+
let child = Command::new("chromedriver")
33+
.arg(format!("--port={}", port))
34+
.stdin(Stdio::null())
35+
.stderr(Stdio::null())
36+
.stdout(Stdio::null())
37+
.spawn()?;
38+
39+
// TODO: parameterize this
40+
thread::sleep(Duration::new(1, 500));
41+
Ok(ChromeDriver {
42+
child: child,
43+
url: format!("http://localhost:{}", port),
44+
kill_on_drop: self.kill_on_drop,
45+
})
46+
}
47+
}
48+
49+
/// A chromedriver process
50+
pub struct ChromeDriver {
51+
child: Child,
52+
url: String,
53+
kill_on_drop: bool,
54+
}
55+
56+
impl ChromeDriver {
57+
pub fn spawn() -> Result<Self, Error> {
58+
ChromeDriverBuilder::new().spawn()
59+
}
60+
pub fn build() -> ChromeDriverBuilder {
61+
ChromeDriverBuilder::new()
62+
}
63+
}
64+
65+
impl Drop for ChromeDriver {
66+
fn drop(&mut self) {
67+
if self.kill_on_drop {
68+
let _ = self.child.kill();
69+
}
70+
}
71+
}
72+
73+
impl Driver for ChromeDriver {
74+
fn url(&self) -> &str {
75+
&self.url
76+
}
77+
}

src/firefox.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,8 @@ use super::*;
33
use std::process::{Command, Child, Stdio};
44
use std::thread;
55
use std::time::Duration;
6-
use std::net::TcpListener;
76

8-
9-
/// Find a TCP port number to use. This is racy but see
10-
/// https://bugzilla.mozilla.org/show_bug.cgi?id=1240830
11-
///
12-
/// If port is Some, check if we can bind to the given port. Otherwise
13-
/// pick a random port.
14-
fn check_tcp_port(port: Option<u16>) -> io::Result<u16> {
15-
TcpListener::bind(&("localhost", port.unwrap_or(0)))
16-
.and_then(|stream| stream.local_addr())
17-
.map(|x| x.port())
18-
}
7+
use super::util;
198

209
pub struct GeckoDriverBuilder {
2110
port: Option<u16>,
@@ -44,7 +33,7 @@ impl GeckoDriverBuilder {
4433
self
4534
}
4635
pub fn spawn(self) -> Result<GeckoDriver, Error> {
47-
let port = check_tcp_port(self.port)?;
36+
let port = util::check_tcp_port(self.port)?;
4837

4938
let child = Command::new("geckodriver")
5039
.arg("-b")

src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern crate serde;
1616
use serde::Serialize;
1717
use serde::de::DeserializeOwned;
1818

19+
#[macro_use]
1920
extern crate serde_json;
2021
pub use serde_json::Value as JsonValue;
2122

@@ -34,7 +35,9 @@ pub mod messages;
3435
use messages::*;
3536
pub use messages::LocationStrategy;
3637

38+
mod util;
3739
pub mod firefox;
40+
pub mod chrome;
3841

3942
#[derive(Debug)]
4043
pub enum Error {
@@ -318,10 +321,17 @@ impl DriverSession {
318321
Ok(v.value)
319322
}
320323

324+
/// Valid values are element references as returned by Element::reference() or null to switch
325+
/// to the top level frame
321326
pub fn switch_to_frame(&self, handle: JsonValue) -> Result<(), Error> {
322327
let _: Empty = try!(self.client.post(&format!("/session/{}/frame", self.session_id), &SwitchFrameCmd::from(handle)));
323328
Ok(())
324329
}
330+
331+
pub fn switch_to_parent_frame(&self) -> Result<(), Error> {
332+
let _: Empty = try!(self.client.post(&format!("/session/{}/frame/parent", self.session_id), &Empty {}));
333+
Ok(())
334+
}
325335
}
326336

327337
impl Drop for DriverSession {
@@ -338,7 +348,7 @@ pub struct Element<'a> {
338348
}
339349

340350
impl<'a> Element<'a> {
341-
fn new(s: &'a DriverSession, reference: String) -> Self {
351+
pub fn new(s: &'a DriverSession, reference: String) -> Self {
342352
Element { session: s, reference: reference }
343353
}
344354

@@ -381,11 +391,16 @@ impl<'a> Element<'a> {
381391
Ok(v.value.into_iter().map(|er| Element::new(self.session, er.reference)).collect())
382392
}
383393

394+
/// Returns a reference that can be passed on to the API
384395
pub fn reference(&self) -> Result<JsonValue, Error> {
385396
serde_json::to_value(&ElementReference::from_str(&self.reference))
386397
.map_err(|err| Error::from(err))
387398
}
388399

400+
/// The raw reference id that identifies this element, this can be used
401+
/// with Element::new()
402+
pub fn raw_reference(&self) -> &str { &self.reference }
403+
389404
/// Gets the `innerHTML` javascript attribute for this element. Some drivers can get
390405
/// this using regular attributes, in others it does not work. This method gets it
391406
/// executing a bit of javascript.

src/messages.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,20 @@ pub struct WebDriverError {
3535

3636
#[derive(Serialize)]
3737
pub struct NewSessionCmd {
38-
required: JsonValue,
38+
capabilities: JsonValue,
3939
}
4040

4141
impl NewSessionCmd {
4242
pub fn new() -> Self {
4343
NewSessionCmd {
44-
required: JsonValue::Null,
44+
capabilities: json!({
45+
"alwaysMatch": {
46+
// ask chrome to be w3c compliant
47+
"goog:chromeOptions": {
48+
"w3c": true
49+
}
50+
}
51+
}),
4552
}
4653
}
4754

@@ -117,6 +124,8 @@ impl Serialize for ElementReference {
117124
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
118125
let mut ss = s.serialize_struct("ElementReference", 1)?;
119126
ss.serialize_field("element-6066-11e4-a52e-4f735466cecf", &self.reference)?;
127+
// even in w3c compliance mode chromedriver only accepts a reference name ELEMENT
128+
ss.serialize_field("ELEMENT", &self.reference)?;
120129
ss.end()
121130
}
122131
}
@@ -197,17 +206,3 @@ pub struct ExecuteCmd {
197206
pub script: String,
198207
pub args: Vec<JsonValue>,
199208
}
200-
201-
#[cfg(test)]
202-
mod tests {
203-
use serde_json::{from_str, to_string};
204-
use super::*;
205-
206-
#[test]
207-
fn element_ref_serialize() {
208-
let r: ElementReference = from_str("{\"element-6066-11e4-a52e-4f735466cecf\": \"ZZZZ\"}").unwrap();
209-
assert_eq!(r.reference, "ZZZZ");
210-
let r2 = from_str(&to_string(&r).unwrap()).unwrap();
211-
assert_eq!(r, r2);
212-
}
213-
}

src/util.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
use std::io;
3+
use std::net::TcpListener;
4+
5+
/// Find a TCP port number to use. This is racy but see
6+
/// https://bugzilla.mozilla.org/show_bug.cgi?id=1240830
7+
///
8+
/// If port is Some, check if we can bind to the given port. Otherwise
9+
/// pick a random port.
10+
pub fn check_tcp_port(port: Option<u16>) -> io::Result<u16> {
11+
TcpListener::bind(&("localhost", port.unwrap_or(0)))
12+
.and_then(|stream| stream.local_addr())
13+
.map(|x| x.port())
14+
}
15+

tests/webdriver_client_integration.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,33 @@ fn execute_async() {
254254

255255
// TODO: Test window handles
256256

257-
// TODO: Test Frames
257+
#[test]
258+
fn test_frame_switch() {
259+
let (server, sess) = setup();
260+
let page1 = server.url("/page3.html");
261+
sess.go(&page1).expect("Error going to page1");
262+
263+
// switching to parent from parent is harmless
264+
sess.switch_to_parent_frame().unwrap();
265+
266+
let frames = sess.find_elements("iframe", LocationStrategy::Css).unwrap();
267+
assert_eq!(frames.len(), 1);
268+
269+
sess.switch_to_frame(frames[0].reference().unwrap()).unwrap();
270+
let frames = sess.find_elements("iframe", LocationStrategy::Css).unwrap();
271+
assert_eq!(frames.len(), 2);
272+
273+
for f in &frames {
274+
sess.switch_to_frame(f.reference().unwrap()).unwrap();
275+
let childframes = sess.find_elements("iframe", LocationStrategy::Css).unwrap();
276+
assert_eq!(childframes.len(), 0);
277+
sess.switch_to_parent_frame().unwrap();
278+
}
279+
280+
sess.switch_to_parent_frame().unwrap();
281+
let frames = sess.find_elements("iframe", LocationStrategy::Css).unwrap();
282+
assert_eq!(frames.len(), 1);
283+
}
258284

259285
#[test]
260286
fn test_http_driver() {

tests/www/inner_frame.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<iframe>
2+
</iframe>
3+
4+
<iframe>
5+
</iframe>

tests/www/page3.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test page title</title>
5+
</head>
6+
<body>
7+
<iframe src="inner_frame.html"></iframe>
8+
</body>
9+
</html>

0 commit comments

Comments
 (0)