mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-20 16:26:33 +00:00
Add ability to run tests on remote webdriver. (#1744)
* Add ability run tests on remote webdriver * Add parsing `webdriver.json` for configure browser capabilities * Add docs for configuring of browser capabilities * Remove webdriver dependency
This commit is contained in:
@ -2,9 +2,11 @@ use crate::shell::Shell;
|
|||||||
use curl::easy::Easy;
|
use curl::easy::Easy;
|
||||||
use failure::{bail, format_err, Error, ResultExt};
|
use failure::{bail, format_err, Error, ResultExt};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
|
use rouille::url::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{self, json};
|
use serde_json::{self, json, Map, Value as Json};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -12,6 +14,40 @@ use std::process::{Child, Command, Stdio};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// Options that can use to customize and configure a WebDriver session.
|
||||||
|
type Capabilities = Map<String, Json>;
|
||||||
|
|
||||||
|
/// Wrapper for [`Capabilities`] used in `--w3c` mode.
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct SpecNewSessionParameters {
|
||||||
|
#[serde(rename = "alwaysMatch", default = "Capabilities::default")]
|
||||||
|
pub always_match: Capabilities,
|
||||||
|
#[serde(rename = "firstMatch", default = "first_match_default")]
|
||||||
|
pub first_match: Vec<Capabilities>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpecNewSessionParameters {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
always_match: Capabilities::new(),
|
||||||
|
first_match: vec![Capabilities::new()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_match_default() -> Vec<Capabilities> {
|
||||||
|
vec![Capabilities::default()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for [`Capabilities`] used in `--legacy` mode.
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct LegacyNewSessionParameters {
|
||||||
|
#[serde(rename = "desiredCapabilities", default = "Capabilities::default")]
|
||||||
|
pub desired: Capabilities,
|
||||||
|
#[serde(rename = "requiredCapabilities", default = "Capabilities::default")]
|
||||||
|
pub required: Capabilities,
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a headless browser tests against a server running on `server`
|
/// Execute a headless browser tests against a server running on `server`
|
||||||
/// address.
|
/// address.
|
||||||
///
|
///
|
||||||
@ -20,50 +56,68 @@ use std::time::{Duration, Instant};
|
|||||||
/// etc. It will return `Ok` if all tests finish successfully, and otherwise it
|
/// etc. It will return `Ok` if all tests finish successfully, and otherwise it
|
||||||
/// will return an error if some tests failed.
|
/// will return an error if some tests failed.
|
||||||
pub fn run(server: &SocketAddr, shell: &Shell) -> Result<(), Error> {
|
pub fn run(server: &SocketAddr, shell: &Shell) -> Result<(), Error> {
|
||||||
let (driver, args, mut client_args) = Driver::find()?;
|
let driver = Driver::find()?;
|
||||||
println!(
|
let mut drop_log: Box<dyn FnMut()> = Box::new(|| ());
|
||||||
"Running headless tests in {} with `{}`",
|
let driver_url = match driver.location() {
|
||||||
driver.browser(),
|
Locate::Remote(url) => Ok(url.clone()),
|
||||||
driver.path().display()
|
Locate::Local((path, args)) => {
|
||||||
);
|
// Allow tests to run in parallel (in theory) by finding any open port
|
||||||
|
// available for our driver. We can't bind the port for the driver, but
|
||||||
|
// hopefully the OS gives this invocation unique ports across processes
|
||||||
|
let driver_addr = TcpListener::bind("127.0.0.1:0")?.local_addr()?;
|
||||||
|
|
||||||
// Allow tests to run in parallel (in theory) by finding any open port
|
// Spawn the driver binary, collecting its stdout/stderr in separate
|
||||||
// available for our driver. We can't bind the port for the driver, but
|
// threads. We'll print this output later.
|
||||||
// hopefully the OS gives this invocation unique ports across processes
|
let mut cmd = Command::new(path);
|
||||||
let driver_addr = TcpListener::bind("127.0.0.1:0")?.local_addr()?;
|
cmd.args(args)
|
||||||
|
.arg(format!("--port={}", driver_addr.port().to_string()));
|
||||||
|
let mut child = BackgroundChild::spawn(&path, &mut cmd, shell)?;
|
||||||
|
drop_log = Box::new(move || child.print_stdio_on_drop = false);
|
||||||
|
|
||||||
// Spawn the driver binary, collecting its stdout/stderr in separate
|
// Wait for the driver to come online and bind its port before we try to
|
||||||
// threads. We'll print this output later.
|
// connect to it.
|
||||||
let mut cmd = Command::new(driver.path());
|
let start = Instant::now();
|
||||||
cmd.args(&args)
|
let max = Duration::new(5, 0);
|
||||||
.arg(format!("--port={}", driver_addr.port().to_string()));
|
let mut bound = false;
|
||||||
let mut child = BackgroundChild::spawn(driver.path(), &mut cmd, shell)?;
|
while start.elapsed() < max {
|
||||||
|
if TcpStream::connect(&driver_addr).is_ok() {
|
||||||
// Wait for the driver to come online and bind its port before we try to
|
bound = true;
|
||||||
// connect to it.
|
break;
|
||||||
let start = Instant::now();
|
}
|
||||||
let max = Duration::new(5, 0);
|
thread::sleep(Duration::from_millis(100));
|
||||||
let mut bound = false;
|
}
|
||||||
while start.elapsed() < max {
|
if !bound {
|
||||||
if TcpStream::connect(&driver_addr).is_ok() {
|
bail!("driver failed to bind port during startup")
|
||||||
bound = true;
|
}
|
||||||
break;
|
Url::parse(&format!("http://{}", driver_addr)).map_err(Error::from)
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(100));
|
}?;
|
||||||
}
|
println!(
|
||||||
if !bound {
|
"Running headless tests in {} on `{}`",
|
||||||
bail!("driver failed to bind port during startup")
|
driver.browser(),
|
||||||
}
|
driver_url.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut client = Client {
|
let mut client = Client {
|
||||||
handle: Easy::new(),
|
handle: Easy::new(),
|
||||||
driver_addr,
|
driver_url,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
|
println!("Try find `webdriver.json` for configure browser's capabilities:");
|
||||||
|
let capabilities: Capabilities = match File::open("webdriver.json") {
|
||||||
|
Ok(file) => {
|
||||||
|
println!("Ok");
|
||||||
|
serde_json::from_reader(file)
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
println!("Not found");
|
||||||
|
Ok(Capabilities::new())
|
||||||
|
}
|
||||||
|
}?;
|
||||||
shell.status("Starting new webdriver session...");
|
shell.status("Starting new webdriver session...");
|
||||||
// Allocate a new session with the webdriver protocol, and once we've done
|
// Allocate a new session with the webdriver protocol, and once we've done
|
||||||
// so schedule the browser to get closed with a call to `close_window`.
|
// so schedule the browser to get closed with a call to `close_window`.
|
||||||
let id = client.new_session(&driver, &mut client_args)?;
|
let id = client.new_session(&driver, capabilities)?;
|
||||||
client.session = Some(id.clone());
|
client.session = Some(id.clone());
|
||||||
|
|
||||||
// Visit our local server to open up the page that runs tests, and then get
|
// Visit our local server to open up the page that runs tests, and then get
|
||||||
@ -114,7 +168,7 @@ pub fn run(server: &SocketAddr, shell: &Shell) -> Result<(), Error> {
|
|||||||
// If the tests harness finished (either successfully or unsuccessfully)
|
// If the tests harness finished (either successfully or unsuccessfully)
|
||||||
// then in theory all the info needed to debug the failure is in its own
|
// then in theory all the info needed to debug the failure is in its own
|
||||||
// output, so we shouldn't need the driver logs to get printed.
|
// output, so we shouldn't need the driver logs to get printed.
|
||||||
child.print_stdio_on_drop = false;
|
drop_log();
|
||||||
} else {
|
} else {
|
||||||
println!("failed to detect test as having been run");
|
println!("failed to detect test as having been run");
|
||||||
if output.len() > 0 {
|
if output.len() > 0 {
|
||||||
@ -136,48 +190,66 @@ pub fn run(server: &SocketAddr, shell: &Shell) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Driver {
|
enum Driver {
|
||||||
Gecko(PathBuf),
|
Gecko(Locate),
|
||||||
Safari(PathBuf),
|
Safari(Locate),
|
||||||
Chrome(PathBuf),
|
Chrome(Locate),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Locate {
|
||||||
|
Local((PathBuf, Vec<String>)),
|
||||||
|
Remote(Url),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Driver {
|
impl Driver {
|
||||||
/// Attempts to find an appropriate WebDriver server binary to execute tests
|
/// Attempts to find an appropriate remote WebDriver server or server binary
|
||||||
/// with. Performs a number of heuristics to find one available, including:
|
/// to execute tests with.
|
||||||
|
/// Performs a number of heuristics to find one available, including:
|
||||||
///
|
///
|
||||||
|
/// * Env vars like `GECKODRIVER_REMOTE` address of remote webdriver.
|
||||||
/// * Env vars like `GECKODRIVER` point to the path to a binary to execute.
|
/// * Env vars like `GECKODRIVER` point to the path to a binary to execute.
|
||||||
/// * Otherwise, `PATH` is searched for an appropriate binary.
|
/// * Otherwise, `PATH` is searched for an appropriate binary.
|
||||||
///
|
///
|
||||||
/// In both cases a lists of auxiliary arguments is also returned which is
|
/// In the last two cases a list of auxiliary arguments is also returned
|
||||||
/// configured through env vars like `GECKODRIVER_ARGS` and
|
/// which is configured through env vars like `GECKODRIVER_ARGS` to support
|
||||||
/// `GECKODRIVER_CLIENT_ARGS` to support extra arguments to invocation the
|
/// extra arguments to the driver's invocation.
|
||||||
/// driver and a browser respectively.
|
fn find() -> Result<Driver, Error> {
|
||||||
fn find() -> Result<(Driver, Vec<String>, Vec<String>), Error> {
|
let env_args = |name: &str| {
|
||||||
let env_vars = |name: String| {
|
env::var(format!("{}_ARGS", name.to_uppercase()))
|
||||||
env::var(name)
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
let env_args = |name: &str| env_vars(format!("{}_ARGS", name.to_uppercase()));
|
|
||||||
let env_client_args = |name: &str| env_vars(format!("{}_CLIENT_ARGS", name.to_uppercase()));
|
|
||||||
|
|
||||||
let drivers = [
|
let drivers = [
|
||||||
("geckodriver", Driver::Gecko as fn(PathBuf) -> Driver),
|
("geckodriver", Driver::Gecko as fn(Locate) -> Driver),
|
||||||
("safaridriver", Driver::Safari as fn(PathBuf) -> Driver),
|
("safaridriver", Driver::Safari as fn(Locate) -> Driver),
|
||||||
("chromedriver", Driver::Chrome as fn(PathBuf) -> Driver),
|
("chromedriver", Driver::Chrome as fn(Locate) -> Driver),
|
||||||
];
|
];
|
||||||
|
|
||||||
// First up, if env vars like GECKODRIVER are present, use those to
|
// First up, if env vars like GECKODRIVER_REMOTE are present, use those
|
||||||
// allow forcing usage of a particular driver.
|
// to allow forcing usage of a particular remote driver.
|
||||||
|
for (driver, ctor) in drivers.iter() {
|
||||||
|
let env = format!("{}_REMOTE", driver.to_uppercase());
|
||||||
|
let url = match env::var(&env) {
|
||||||
|
Ok(var) => match Url::parse(&var) {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(_) => continue,
|
||||||
|
},
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
return Ok(ctor(Locate::Remote(url)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, if env vars like GECKODRIVER are present, use those to
|
||||||
|
// allow forcing usage of a particular local driver.
|
||||||
for (driver, ctor) in drivers.iter() {
|
for (driver, ctor) in drivers.iter() {
|
||||||
let env = driver.to_uppercase();
|
let env = driver.to_uppercase();
|
||||||
let path = match env::var_os(&env) {
|
let path = match env::var_os(&env) {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
return Ok((ctor(path.into()), env_args(driver), env_client_args(driver)));
|
return Ok(ctor(Locate::Local((path.into(), env_args(driver)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, check PATH. If we can find any supported driver, use that by
|
// Next, check PATH. If we can find any supported driver, use that by
|
||||||
@ -188,11 +260,11 @@ impl Driver {
|
|||||||
.with_extension(env::consts::EXE_EXTENSION)
|
.with_extension(env::consts::EXE_EXTENSION)
|
||||||
.exists()
|
.exists()
|
||||||
});
|
});
|
||||||
let (name, ctor) = match found {
|
let (driver, ctor) = match found {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
return Ok((ctor(name.into()), env_args(name), env_client_args(name)));
|
return Ok(ctor(Locate::Local((path.into(), env_args(driver)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: download an appropriate driver? How to know which one to
|
// TODO: download an appropriate driver? How to know which one to
|
||||||
@ -200,10 +272,11 @@ impl Driver {
|
|||||||
|
|
||||||
bail!(
|
bail!(
|
||||||
"\
|
"\
|
||||||
failed to find a suitable WebDriver binary to drive headless testing; to
|
failed to find a suitable WebDriver binary or remote running WebDriver to drive
|
||||||
configure the location of the webdriver binary you can use environment
|
headless testing; to configure the location of the webdriver binary you can use
|
||||||
variables like `GECKODRIVER=/path/to/geckodriver` or make sure that the binary
|
environment variables like `GECKODRIVER=/path/to/geckodriver` or make sure that
|
||||||
is in `PATH`
|
the binary is in `PATH`; to configure the address of remote webdriver you can
|
||||||
|
use environment variables like `GECKODRIVER_REMOTE=http://remote.host/`
|
||||||
|
|
||||||
This crate currently supports `geckodriver`, `chromedriver`, and `safaridriver`,
|
This crate currently supports `geckodriver`, `chromedriver`, and `safaridriver`,
|
||||||
although more driver support may be added! You can download these at:
|
although more driver support may be added! You can download these at:
|
||||||
@ -223,14 +296,6 @@ an issue against rustwasm/wasm-bindgen!
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&self) -> &Path {
|
|
||||||
match self {
|
|
||||||
Driver::Gecko(path) => path,
|
|
||||||
Driver::Safari(path) => path,
|
|
||||||
Driver::Chrome(path) => path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn browser(&self) -> &str {
|
fn browser(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Driver::Gecko(_) => "Firefox",
|
Driver::Gecko(_) => "Firefox",
|
||||||
@ -238,11 +303,19 @@ an issue against rustwasm/wasm-bindgen!
|
|||||||
Driver::Chrome(_) => "Chrome",
|
Driver::Chrome(_) => "Chrome",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn location(&self) -> &Locate {
|
||||||
|
match self {
|
||||||
|
Driver::Gecko(locate) => locate,
|
||||||
|
Driver::Safari(locate) => locate,
|
||||||
|
Driver::Chrome(locate) => locate,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Client {
|
struct Client {
|
||||||
handle: Easy,
|
handle: Easy,
|
||||||
driver_addr: SocketAddr,
|
driver_url: Url,
|
||||||
session: Option<String>,
|
session: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +330,7 @@ enum Method<'a> {
|
|||||||
// copied the `webdriver-client` crate when writing the below bindings.
|
// copied the `webdriver-client` crate when writing the below bindings.
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
fn new_session(&mut self, driver: &Driver, args: &mut Vec<String>) -> Result<String, Error> {
|
fn new_session(&mut self, driver: &Driver, mut cap: Capabilities) -> Result<String, Error> {
|
||||||
match driver {
|
match driver {
|
||||||
Driver::Gecko(_) => {
|
Driver::Gecko(_) => {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -270,15 +343,21 @@ impl Client {
|
|||||||
#[serde(rename = "sessionId")]
|
#[serde(rename = "sessionId")]
|
||||||
session_id: String,
|
session_id: String,
|
||||||
}
|
}
|
||||||
args.push("-headless".to_string());
|
cap.entry("moz:firefoxOptions".to_string())
|
||||||
|
.or_insert_with(|| Json::Object(serde_json::Map::new()))
|
||||||
|
.as_object_mut()
|
||||||
|
.expect("moz:firefoxOptions wasn't a JSON object")
|
||||||
|
.entry("args".to_string())
|
||||||
|
.or_insert_with(|| Json::Array(vec![]))
|
||||||
|
.as_array_mut()
|
||||||
|
.expect("args wasn't a JSON array")
|
||||||
|
.extend(vec![Json::String("-headless".to_string())]);
|
||||||
|
let session_config = SpecNewSessionParameters {
|
||||||
|
always_match: cap,
|
||||||
|
first_match: vec![Capabilities::new()],
|
||||||
|
};
|
||||||
let request = json!({
|
let request = json!({
|
||||||
"capabilities": {
|
"capabilities": session_config,
|
||||||
"alwaysMatch": {
|
|
||||||
"moz:firefoxOptions": {
|
|
||||||
"args": args,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
let x: Response = self.post("/session", &request)?;
|
let x: Response = self.post("/session", &request)?;
|
||||||
Ok(x.value.session_id)
|
Ok(x.value.session_id)
|
||||||
@ -319,19 +398,26 @@ impl Client {
|
|||||||
#[serde(rename = "sessionId")]
|
#[serde(rename = "sessionId")]
|
||||||
session_id: String,
|
session_id: String,
|
||||||
}
|
}
|
||||||
args.push("headless".to_string());
|
cap.entry("goog:chromeOptions".to_string())
|
||||||
// See https://stackoverflow.com/questions/50642308/
|
.or_insert_with(|| Json::Object(serde_json::Map::new()))
|
||||||
// for what this funky `disable-dev-shm-usage`
|
.as_object_mut()
|
||||||
// option is
|
.expect("goog:chromeOptions wasn't a JSON object")
|
||||||
args.push("disable-dev-shm-usage".to_string());
|
.entry("args".to_string())
|
||||||
args.push("no-sandbox".to_string());
|
.or_insert_with(|| Json::Array(vec![]))
|
||||||
let request = json!({
|
.as_array_mut()
|
||||||
"desiredCapabilities": {
|
.expect("args wasn't a JSON array")
|
||||||
"goog:chromeOptions": {
|
.extend(vec![
|
||||||
"args": args,
|
Json::String("headless".to_string()),
|
||||||
},
|
// See https://stackoverflow.com/questions/50642308/
|
||||||
}
|
// for what this funky `disable-dev-shm-usage`
|
||||||
});
|
// option is
|
||||||
|
Json::String("disable-dev-shm-usage".to_string()),
|
||||||
|
Json::String("no-sandbox".to_string()),
|
||||||
|
]);
|
||||||
|
let request = LegacyNewSessionParameters {
|
||||||
|
desired: cap,
|
||||||
|
required: Capabilities::new(),
|
||||||
|
};
|
||||||
let x: Response = self.post("/session", &request)?;
|
let x: Response = self.post("/session", &request)?;
|
||||||
Ok(x.session_id)
|
Ok(x.session_id)
|
||||||
}
|
}
|
||||||
@ -430,9 +516,9 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn doit(&mut self, path: &str, method: Method) -> Result<String, Error> {
|
fn doit(&mut self, path: &str, method: Method) -> Result<String, Error> {
|
||||||
let url = format!("http://{}{}", self.driver_addr, path);
|
let url = self.driver_url.join(path)?;
|
||||||
self.handle.reset();
|
self.handle.reset();
|
||||||
self.handle.url(&url)?;
|
self.handle.url(url.as_str())?;
|
||||||
match method {
|
match method {
|
||||||
Method::Post(data) => {
|
Method::Post(data) => {
|
||||||
self.handle.post(true)?;
|
self.handle.post(true)?;
|
||||||
|
@ -46,6 +46,35 @@ test` with the appropriate browser flags and `--headless`:
|
|||||||
wasm-pack test --headless --chrome --firefox --safari
|
wasm-pack test --headless --chrome --firefox --safari
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuring Headless Browser capabilities
|
||||||
|
|
||||||
|
Add the file `webdriver.json` to the root of your crate. Each browser has own
|
||||||
|
section for capabilities. For example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"moz:firefoxOptions": {
|
||||||
|
"prefs": {
|
||||||
|
"media.navigator.streams.fake": true,
|
||||||
|
"media.navigator.permission.disabled": true
|
||||||
|
},
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
"goog:chromeOptions": {
|
||||||
|
"args": [
|
||||||
|
"--use-fake-device-for-media-stream",
|
||||||
|
"--use-fake-ui-for-media-stream"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Full list supported capabilities can be found:
|
||||||
|
|
||||||
|
* for Chrome - [here](https://peter.sh/experiments/chromium-command-line-switches/)
|
||||||
|
* for Firefox - [here](https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions)
|
||||||
|
|
||||||
|
Note that the `headless` argument is always enabled for both browsers.
|
||||||
|
|
||||||
### Debugging Headless Browser Tests
|
### Debugging Headless Browser Tests
|
||||||
|
|
||||||
Omitting the `--headless` flag will disable headless mode, and allow you to
|
Omitting the `--headless` flag will disable headless mode, and allow you to
|
||||||
@ -92,6 +121,15 @@ WebDriver.
|
|||||||
This is installed by default on Mac OS. It should be able to find your Safari
|
This is installed by default on Mac OS. It should be able to find your Safari
|
||||||
installation by default.
|
installation by default.
|
||||||
|
|
||||||
|
### Running the Tests in the Remote Headless Browser
|
||||||
|
|
||||||
|
Tests can be run on a remote webdriver. To do this, the above environment
|
||||||
|
variables must be set as URL to the remote webdriver. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
CHROMEDRIVER=http://remote.host/
|
||||||
|
```
|
||||||
|
|
||||||
### Running the Tests in the Headless Browser
|
### Running the Tests in the Headless Browser
|
||||||
|
|
||||||
Once the tests are configured to run in a headless browser and the appropriate
|
Once the tests are configured to run in a headless browser and the appropriate
|
||||||
|
Reference in New Issue
Block a user