seleniumQuery is a Java library/framework that brings a cross-driver jQuery-like interface for Selenium WebDriver.
// getting the value
String oldStreet = $("input.street").val();
// setting the value
$("input.street").val("4th St!");
On a regular WebElement
...
// an existing WebElement...
WebElement existingWebElement = driver.findElement(By.id("myId"));
// call jQuery functions
String elementVal = $(existingWebElement).val();
boolean isButton = $(existingWebElement).is(":button"); // enhanced selector!
for (WebElement child: $(existingWebElement).children()) {
System.out.println("That element's child: "+child);
}
Or an existing WebDriver
...
// an existing WebDriver...
WebDriver driver = new FirefoxDriver();
// set it up
$.driver().use(driver);
// and use all the goods
for (WebElement e: $(".myClass:contains('My Text!'):not(:button)")) {
System.out.println("That element: " + e);
}
Allows querying elements by:
- CSS3 Selectors -
$(".myClass")
,$("#table tr:nth-child(3n+1)")
; - jQuery/Sizzle enhancements -
$(":text:eq(3)")
,$(".myClass:contains('My Text!')")
; - XPath -
$("//div/*/label/preceding::*")
; - and even some own seleniumQuery selectors:
$("#myOldDiv").is(":not(:present)")
.
Built using Selenium WebDriver's native capabilities only:
- No
jQuery.js
is embedded at the page, no side-effects are generated;- Doesn't matter if the page uses jQuery or not (or even if the JavaScript global variable
$
is other library likePrototype.js
).
- Doesn't matter if the page uses jQuery or not (or even if the JavaScript global variable
- Capable of handling/testing JavaScript-disabled pages
- Test pages that use Unobtrusive JavaScript.
- Most functions don't even require the browser/driver to have JavaScript enabled!
Try it out now with the running example below:
import static io.github.seleniumquery.SeleniumQuery.$; // this will allow the short syntax
public class SeleniumQueryExample {
public static void main(String[] args) {
// sets Firefox as the driver (this is optional, if omitted, will default to HtmlUnit)
$.driver().useFirefox(); // The WebDriver will be instantiated only when first used
// or use ("decorate") any previously existing driver
$.driver().use(new FirefoxDriver());
// starts the driver (if not started already) and opens the URL
$.url("http://www.google.com/?hl=en");
// interact with the page
$(":text[name='q']").val("selenium"); // the keys are actually typed!
$(":button[name=btnG]").click(); // simulates a real user click (not just the JS event)
// Besides the short syntax and the jQuery behavior you already know,
// other very useful function in seleniumQuery is .waitUntil(),
// handy for dealing with user-waiting actions (specially in Ajax enabled pages):
String resultsText = $("#resultStats").waitUntil().is(":visible").then().text();
System.out.println(resultsText);
// should print something like: About 24,900,000 results (0.37 seconds)
$.quit(); // quits the currently used driver (firefox)
}
}
Download and execute the seleniumQuery showcase project
...and see it in action right now.
To get the latest version of seleniumQuery, add to your pom.xml
:
<dependency>
<groupId>io.github.seleniumquery</groupId>
<artifactId>seleniumquery</artifactId>
<version>0.16.0</version>
</dependency>
seleniumQuery aims to implement all relevant jQuery functions, as well as adding some of our own. Our main goal is to make emulating user actions and reading the state of pages easier than ever, with a consistent behavior across drivers.
Make your code/tests more readable and easier to maintain. Leverage your knowledge of jQuery.
// Instead of regular Selenium code:
WebElement element = driver.findElement(By.id("mySelect"));
new Select(element).selectByValue("ford");
// You can have the same effect writing just:
$("#mySelect").val("ford");
Get to know what jQuery functions seleniumQuery supports and what else it brings to the table on our seleniumQuery API wiki page.
Let the tool do the hard work and find elements easily:
- CSS3 Selectors -
$(".myClass")
,$("#table tr:nth-child(3n+1)")
- jQuery/Sizzle enhancements -
$(".claz:eq(3)")
,$(".claz:contains('My Text!')")
- XPath -
$("//div/*/label/preceding::*")
- and even some own seleniumQuery selectors:
$("#myOldDiv").is(":not(:present)")
.
You pick your style. Whatever is more interesting at the moment. Mixing is OK:
$("#tab tr:nth-child(3n+1)").find("/img[@alt='calendar']/preceding::input").val("2014-11-12")
Find more about them in seleniumQuery Selectors wiki page.
Other important feature is the leverage of WebDriver
's FluentWait
capabilities directly in the element (no boilerplate code!) through the use of the .waitUntil()
function:
// WebDriver cannot natively detect the end of an Ajax call.
// To test your application's behavior, you can and should always work with the
// Ajax's expected effects, visible for the end user.
// Below is an example of a <div> that should be hidden as effect of an Ajax call.
// The code will hold until the modal is gone. If it is never gone, seleniumQuery
// will throw a timeout exception.
$("#modalDiv :button:contains('OK')").click();
$("#modalDiv :button:contains('OK')").waitUntil().is(":not(:visible)");
// Or, fluently:
$("#modalDivOkButton").click().waitUntil().is(":not(:visible)");
And, that's right, the .is()
function above is your old-time friend that takes a selector as argument!
Check out what else .waitUntil()
can do in the seleniumQuery API wiki page.
seleniumQuery supports plugins through the .as(PLUGIN)
function, such as:
$("div").as(YOURPLUGIN).someMethodFromYourPlugin();
There are some default plugins. To check them out, call .as()
without arguments. Example:
// the .select() plugin
$("#citiesSelect").as().select().selectByVisibleText("New York");
// picks an <option> in the <select> based in the <option>'s visible text
For an example of how to create your own plugin, check the seleniumQuery Plugin wiki page.
How to setup the WebDriver
? Simply use our builder. The driver will be instantiated only when first used.
$.driver().useFirefox(); // Will set up firefox as driver
$.url("http://seleniumquery.github.io"); //the driver will be instantiated when this executes
Want FirefoxDriver
without JavaScript? Just:
$.driver().useFirefox().withoutJavaScript(); // when started, Firefox will have JS OFF
All you have to do is download their executables before. Setting them up in seleniumQuery is all too easy:
// Using Chrome
$.driver().useChrome(); // will look for chromedriver/exe to you, including in the classpath!
// Or if you want to set the path yourself
$.driver().useChrome().withPathToChromeDriver("path/to/chromedriver.exe")
// InternetExplorerDriver
$.driver().useInternetExplorer(); // we search IEDriverServer.exe for you
// Or you set the path yourself
$.driver().useInternetExplorer().withPathToIEDriverServerExe("C:\\IEDriverServer.exe");
// PhantomJS (GhostDriver)
$.driver().usePhantomJS(); // again, we'll find phantomjs[.exe] to you
// Or you may set the path yourself
$.driver().usePhantomJS().withPathToPhantomJS("path/to/phantomjs.exe");
So many possibilities to set up HtmlUnitDriver
... If only there was a simple way to use them. Oh, wait:
// HtmlUnit default (Chrome/JavaScript ON)
$.driver().useHtmlUnit();
// Want disabled JavaScript, just call .withoutJavaScript()
$.driver().useHtmlUnit().withoutJavaScript();
// HtmlUnit emulating Chrome
$.driver().useHtmlUnit().emulatingChrome();
$.driver().useHtmlUnit().emulatingChrome().withoutJavaScript();
// HtmlUnit emulating Firefox
$.driver().useHtmlUnit().emulatingFirefox(); // could disable JS here as well
// And IE
$.driver().useHtmlUnit().emulatingInternetExplorer11(); // JS is disableable as well
$.driver().useHtmlUnit().emulatingInternetExplorer(); // will pick latest IE
Explore the auto-complete. There are additional options to every driver, such as .withCapabilities(DesiredCapabilities)
or some specific, such as .withProfile(FirefoxProfile)
or .withOptions(ChromeOptions)
.
Finally, if you want to create the WebDriver
yourself:
WebDriver myDriver = ...;
$.driver().use(myDriver);
That's why it can work with disabled JavaScript!
But there is a more important aspect to it: Although our functions yield the same result as if you were using jQuery, remember we always execute them from the user perspective. In other words, when you call:
$(":input[name='email']").val("[email protected]");
We don't change the value
attribute directly like jQuery does. We actually do as a user would: We clear the input
and type, key by key, the string provided as argument!
And we go the extra mile whenever possible:
- Our
$().val()
even works oncontenteditable
elements ANDdocumentMode=on <iframe>
s: They don't havevalue
, but we type the text in them, again, key by key, as an user would; - If it is an
<input type="file">
we select the file; - When the element is a
<select>
, we choose the<option>
by the value given (same as$("selector").as().select().selectByValue("123")
).
On the same tone, when selecting/checking <option>
s or checkboxes or radios, try not to use $().prop("selected", true)
directly to them (which to work, of course, would need JS to be enabled on the driver).
Do as an user would: call .click()
! Or, better yet, use seleniumQuery's .as().select()
functions: $().as().select().selectByVisibleText("My Option")
or $().as().select().selectByValue("123")
.
If the dollar symbol, $
, gives you the yikes -- we know, it is used for internal class names --, it is important to notice that the $
symbol in seleniumQuery is not a class name, but a static
method (and field). Still, if you don't feel like using it, you can resort to sQ()
or good ol' jQuery()
and benefit from all the same goodies:
import static io.github.seleniumquery.SeleniumQuery.sQ;
import static io.github.seleniumquery.SeleniumQuery.jQuery;
...
String oldStreet = sQ("input.street").val();
sQ("input.street").val("4th St!");
String oldStreetz = jQuery("input.street").val();
jQuery("input.street").val("5th St!");
Typically, the $
is a static variable, thus every command you issue only affects the one same instance of WebDriver.
But... what if you want/need to use two WebDrivers at the same time?
We've got your back, see the example:
public class SeleniumQueryBrowserTest {
// using two drivers (chrome and firefox) at the same time
private SeleniumQueryBrowser chrome = new SeleniumQueryBrowser();
private SeleniumQueryBrowser firefox = new SeleniumQueryBrowser();
@Test
public void multiple_browser_instances_should_work_OK() {
chrome.$.driver().useHtmlUnit().emulatingChrome();
openUrl(SeleniumQueryBrowserTest.class, chrome.$);
firefox.$.driver().useHtmlUnit().emulatingFirefox();
openUrl(SeleniumQueryBrowserTest.class, firefox.$);
assertThat(chrome.$("#agent").text(), containsString("Chrome"));
assertThat(firefox.$("#agent").text(), containsString("Firefox"));
}
@After
public void tearDown() {
chrome.$.quit();
firefox.$.quit();
}
}
Find more on our wiki.
See releases.
The tool is just beginning, there's a lot of room for improvement. Some of its main functionalities were just made up (and didn't exist in
jQuery), like the .waitUntil()
, the .as()
plugins, the driver builder and so on. So if you come up with an idea of something that could
be useful, tell us, or, even better, do it yourself and join the team!
Goals:
- Have a uniform behavior thoughout targeted WebDriver implementations
- A given code should behave as similar as possible in all WebDrivers.
- Selenium itself takes care of that, but it does leave some room for improvement
- This is important to our functions as well, they should behave the same regardless of WebDriver implementation (browser) used
- A given code should behave as similar as possible in all WebDrivers.
- Mimic jQuery's interface and behavior, but...
- Do it all, when possible, from the user's perspective
- e.g.
$().val("")
types content instead of setting thevalue
attribute.
- e.g.
- Improve it a little (e.g. throw exception when invalid selectors, such as
"div:file"
are used)
- Do it all, when possible, from the user's perspective
- Add functions that tackle common problems when dealing with web (testing) automation, such as waiting (
$().waitUntil()
) - Add quick commands for common usage patterns (such as driver builder does)
- Simplify overall usage with convention over configuration
Non-goals:
- Add all jQuery's functions
- Replace WebDriver
- What went bad?
- Since the selector system supports not only pure CSS (it allows the extended CSS supported by jQuery- and implemented by Sizzle), its implementation is a challenge by itself.
- The first version used regexes, didn't work so well and never made it into a release
- The second version (released as 0.9.0) converts every CSS selector into a XPath expression and executes it.
- The advantage is that this makes Selenium bring every element the user wanted already, without the need to iterate over them or anything.
- The problem with this approach is that not every CSS can be translated into an equivalent XPath expression (e.g.
:selected
or:visible
)
- The third version (currently under development, called "secondgen") will parse the selector and...
- If the selector is plain CSS or XPath, use it directly
- If the selector is an extended CSS that can be translated fully to an XPath expression, than translate it and use it
- Otherwise, translate the CSS to the XPath expression that brings the smallest numbers of element possible and then iteratively filter the results before returning
- Since the selector system supports not only pure CSS (it allows the extended CSS supported by jQuery- and implemented by Sizzle), its implementation is a challenge by itself.
Feel free to request, suggest, create pull requests. As said, any opinions/help are more than welcome!