GithubHelp home page GithubHelp logo

dgageot / simplelenium Goto Github PK

View Code? Open in Web Editor NEW
60.0 8.0 25.0 584 KB

Simple layer on top of Selenium and PhantomJs

License: Other

Java 82.46% CoffeeScript 0.42% HTML 0.50% JavaScript 15.92% CSS 0.45% Shell 0.24%

simplelenium's Introduction

Simplelenium

A simple and robust layer on top of Selenium and PhantomJS.

It also supports Chrome and Firefox. SauceLabs support is ongoing.

Goal

Testing web pages with Selenium can prove difficult. I've seen a lot of projects with an unstable build because of Selenium. To be fair, it's more because of the way Selenium is used. Although experience showed me that using Selenium properly is harder that one might think.

In fact I think that proper usage of Selenium must be left out of tester hands and baked into a small, effective library. Simplelenium is my attempt to do so and it served me well.

Simplelenium deals properly and out-of-the-box with timing issues and StaleElementReferenceExceptions. It supports running tests in parallel without you thinking about it. It doesn't open annoying windows since it's default behaviour is to use PhantomJS, a headless browser.

Give it a try and you'll be surprised at how Selenium testing can be fun again (was it ever?).

Setup (Maven)

Add Simplelenium as a maven test dependency to your project and you are all set to go. Simplelenium requires java 8.

<dependency>
  <groupId>net.code-story</groupId>
  <artifactId>simplelenium</artifactId>
  <version>2.2</version>
  <scope>test</scope>
</dependency>

The first time you run a test, it will download PhantomJS automatically for you so that nothing has to be installed on the machine. mvn clean install is all one should need!

Build status

Build Status

Quick Start

import net.codestory.simplelenium.SeleniumTest;
import org.junit.Test;

public class QuickStartTest extends SeleniumTest {
  @Test
  public void web_driver_site() {
    goTo("http://docs.seleniumhq.org/projects/webdriver/");

    find("#q").fill("StaleElementReferenceExceptions");
    find("#submit").click();

    find("a.gs-title")
      .should()
      .haveMoreItemsThan(5)
      .contain("Issue 1887 - selenium - Element not found in the cache")
      .not().contain("Selenium Rocks!");
  }
}

Notice the fluent api that doesn't rely on static imports. This will make your life easier.

Lots of finders, actions and verifications are supported. Notice that no timing information is provided. The default settings should be ok the vast majority of times.

Finders

Finding elements start with either a find("cssSelector") or a find(org.openqa.selenium.By) . There's no other choice. That's simple. You can use the full power of cssSelector, which should be enough most of the time, or use standard Selenium org.openqa.selenium.By sub-classes.

Searching is not done until a verification is made on the elements. Simplelenium is both lazy and tolerant to slow pages and ongoing refreshes. You don't have to worry about it. Just write what the page should look like and if it happens within a sound period of time, the next verification is made.

We'll dig into more details in the last section.

Verifications

The most simple verification is to check that elements are found:

find(".name").should().exist();

Of course more complex verifications are supported:

find(".name").should().contain("a word", "anything");
find(".name").should().match(Pattern.compile("regexp"));
find(".name").should().beEmpty();
find(".name").should().beEnabled();
find(".name").should().beDisplayed();
find(".name").should().beSelected();
find(".name").should().haveMoreItemsThan(min);
find(".name").should().haveSize(10);
find(".name").should().haveLessItemsThan(max);
find(".name").should().haveDimension(width, height);
find(".name").should().beAtLocation(x, y);

Verifications can be inverted:

find(".name").should().not().contain("a word");

Verifications can be chained:

find(".name")
  .should()
  .contain("a word")
  .contain("anything")
  .beSelected()
  .not().beDisplayed();

The way Simplelenium deals with timing issue is basic, yet efficient:

  • It tries to make the search
  • Then the verification
  • If it passes, then we're cool
  • If not, it tries again immediately with a new search to avoid Staled elements
  • It does so for at least 5 seconds

The "magic" comes from:

  • Not searching until you need to check something
  • Searching again if the check fails
  • Doing a lot of retries as quickly as possible
  • Using the fact that you tell what the page should look like and consider all the failures as false negatives. That is until the maximum delay is reached.

Default timeout can be set using this syntax:

find(".name").should().within(1, MINUTE).contain("a word");

Narrowing search

Sometimes, searching elements is more difficult than using a simple css selector. Simplelenium supports narrowing searches with additional filters, like those:

find("...").withText().beingEmpty().should()...;
find("...").withText().containing("text").should()...;
find("...").withName().startingWith("text").should()...;
find("...").withId().equalTo("text").should()...;
find("...").with("name").matching(Pattern.compile(".*value")).should()...;
find("...").withTagName().equalTo("h1").should()...;
find("...").withClass().containingWord("blue").should()...;
find("...").withCss("color").not().endingWith("grey").should()...;
find("...").withText().startsWith("Prefix").endingWith("Suffix").should()...;
...

Also multiple results can be filtered out this way:

find("...").first();
find("...").second();
find("...").third();
find("...").fourth();
find("...").nth(5);
find("...").limit(10);
find("...").skip(3);
find("...").skip(5).limit(20);
find("...").last();

Actions

Often, you have to interact with the page, not just make verifications. Simplelenium supports a lot of actions. Here are some of them:

find("...").fill("name");
find("...").submit();
find("...").click();
find("...").click(x, y);
find("...").pressReturn();
find("...").sendKeys("A", "B", "C");
find("...").clear();
find("...").doubleClick();
find("...").clickAndHold();
find("...").contextClick();
find("...").release();

find("...").select("text");
find("...").deselect();
find("...").deselectByValue("value");
find("...").deselectByVisibleText("text");
find("...").deselectByIndex(index);
find("...").selectByIndex(index);
find("...").selectByValue("value");

If that's not enough, three generic methods give you access to the Selenium Api underneath but in a managed fashion:

To do anything with the underlying WebElement:

find("...").execute(Consumer<? super WebElement> action);

To execute actions on the element:

find("...").executeActions(String description, BiConsumer<WebElement, Actions> actionsOnElement);

To execute selections on the element:

find("...").executeSelect(String description, Consumer<Select> selectOnElement);

Those three methods should hopefully not be used often but it's great to know that the full power of Selenium is there underneath.

Advanced topics

Let's say you are not impressed, what else can Simplelenium do to make writing tests easier?

Page Objects and Section Objects

Using Page Objects and Section Objects, one can encapsulate both the extraction of web elements and the verification, in a more domain oriented fashion. This also removes a lot of boilerplate code and decreases code duplication.

Let's take a look at a small example:

import net.codestory.simplelenium.DomElement;
import net.codestory.simplelenium.PageObject;
import net.codestory.simplelenium.SeleniumTest;
import org.junit.Test;

public class QuickStartTest extends SeleniumTest {
  Home home;

  @Override
  protected String getDefaultBaseUrl() {
    return "http://localhost:8080/base/";
  }

  @Test
  public void check_page() {
    goTo(home);

    home.shouldDisplayHello();
    home.shouldLinkToOtherPages();
  }

  static class Home implements PageObject {
    DomElement title;
    DomElement greeting;
    DomElement links = find("a.sections");

    @Override
    public String url() {
      return "/home";
    }

    void shouldDisplayHello() {
      title.should().contain("Home page");
      greeting.should().contain("Hello");
    }

    void shouldLinkToOtherPages() {
      links.should().haveSize(5).and().contain("Section1", "Section5");
    }
  }
}

How cool is that? All you have to do is implement PageObject. Page Objects are automatically injected into tests. So are DomElements present as fields into Page Objects. By default elements a searched by name or id but one can use standard find(...) methods to override this behaviour. Same as usual.

If you make the additional effort to return this in Page Objects methods, you than have a nice fluent api.

@Test
public void check_page() {
  home
    .goTo()
    .shouldDisplayHello()
    .shouldLinkToOtherPages();
}

static class Home implements PageObject {
  @Override
  public String url() {
    return "/home";
  }

   Home goTo() {
    goTo(url());
    return this;
  }

   Home shouldDisplayHello() {
    ...
    return this;
  }

   Home shouldLinkToOtherPages() {
    ...
    return this;
  }
}

Page Objects represent a Page with a url. For sections of pages, you can implement SectionObject instead. It makes it easy to split a page into multiple reusable parts that carry their own finders and verifications.

Sections are injected automatically into tests, page objects and other sections.

Running tests in parallel

Simplelenium is good at running tests in parallel. In fact without you doing anything on the code side, it should just work.

Simplelenium keeps a distinct WebDriver for each thread. You don't have to think about it. Let's say you configure surefire to run tests in parallel at class or method level. Easy! You don't have to copy this configuration, with a different syntax, into your test framework. It will just work.

Running tests in parallel with multiple JVMs also works well. We use a lock on the filesystem when we download PhantomJS. I told you, you don't have to think about it.

Tests without JUnit

Sometimes, running the tests with JUnit is not what you want. You'd like to do your own threading and own test lifecycle. You can then use the FluentTest class:

import org.junit.Test;

import static java.util.stream.IntStream.range;

public class FluentTestTest {
  @Test
  public void parallel() {
    String baseUrl = ...;

    range(0, 20).parallel().forEach(index -> {
      new FluentTest(baseUrl)
        .goTo("/")
        .find("h1").should().contain("Hello World").and().not().contain("Unknown")
        .find("h2").should().contain("SubTitle")
        .find(".age").should().contain("42")
        .goTo("/list")
        .find("li").should().contain("Bob").and().contain("Joe");
    });
  }
}

How cool is that?

Run tests with Chrome or Firefox

Even if Simplelenium supports PhantomJs out of the box and by default, tests can be run on Chrome or Firefox.

Run tests with '-Dbrowser=chrome', '-Dbrowser=firefox', '-Dbrowser=phantom_js' to choose which browser you want to use.

If you choose chrome, Simplelenium will download chromedriver automatically for you.

If you don't choose phantomjs, then you have to manually install Firefox or Chrome. Make sure you install them where FirefoxDriver and ChromeDriver expect them to be. If you used homewbrew to install Chrome, like I do, ChromeDriver will figure this out.

If you need to set the port used by ChromeDriver, use the chromedriver.port system property. The default is to use a random free port.

Custom Download URL

If you can't access default download url from where you are (thank you corporate IT proxy). You can override them by providing the following System properties :

What Simplelenium doesn't do

Reading properties from web elements

Sometimes, you want to read a property of a web element and use your own assertions framework to verify if it's ok. That's not how Simplelenium works. You should be able to expect what the element will look like and tell Simplelenium to check. Otherwise you might extract a value a bit too soon and there you are, back into timing hell, with false negative tests. You don't want that. Trust me.

Here's the Simplelenium way of doing this:

find("...").should().match(element -> /* Test something on every element found /*);

Release

mvn release:clean release:prepare release:perform

simplelenium's People

Contributors

andrelugomes avatar dgageot avatar graefenhain avatar jeanlaurent avatar oltruong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simplelenium's Issues

How can I switch to another window?

Hi,

When I click a link, it opens another tab, how can I switch selenium to another tab with simplelenium?

Things like this:

ArrayList tabs2 = new ArrayList (driver.getWindowHandles());
driver.switchTo().window(tabs2.get(1));
driver.close();
driver.switchTo().window(tabs2.get(0));

Release Chrome driver support

Hi David
Could you please release a new version including the Chrome driver support? I sent you a pull request last week and you already merged it. However, I can't release a new version and need it for my project.
Thanks & best regards,
Katja

change port for ChromeDriver

By default the ChromeDriver is started on any free port.
I want to run my tests in a Docker container, that's why I need to define the port, or at least use alway the same port. Could you please add that functionality?

Configure default retry timeout in top level APIs

I'd like to make it possible to configure the default timeout once per test instead of in each chain.
Is this something you've attempted before or have thoughts on how you'd like it implemented?

Right now the two places that seem like it should be overridable are SeleniumTest and PageObject.

So far it looks like the default Retry is only used in LazyDomSelector:
image

My plan is to somehow pass down a duration and timeunit down to LazyDomSelector from SeleniumTest and PageObject for it to use instead of wherever the default retry is used.

Does this sound like something you'd welcome a pull request for?

Thanks!

Any interest in Java 7 compatibility?

Hi!

I have a work-in-progress fork of simplelenium that works with Java 7. I replaced most of the Java 8isms with prolific use of Google Guava. Currently have 6 of the 88 tests failing.

Do you have any interest in maintaining a Java 7 version of simplelenium as well (simplelenium-java7 maybe?). Would it be okay if I published a separate maven artifact with the aforementioned name and same package namespace?

Thanks for simplelenium, it makes front-end testing in Java a delight!

Being able to test an element exists without relying on exceptions

Hi,

Regularly in my application, I need to click on one element if it exists, or another if it doesn't. The problem I have is that there's no method returning a boolean doing that. So I'm writing code like this which throws a quite generic AssertionError (for which I don't know if it failed because it doesn't exist or because element was not clickable), forcing me to write code like this :

private WorkFlowPage clickWorkFlowTab() {

    try{
        find(By.id("someElement")).click();
    }catch(AssertionError e)
    {
        find(By.id("someOtherElement")).click();
    }
    return this;
}

Since it returns a LazyDomElement, find method doesn't perform the search immediately, as mentioned in documentation. Is there a way to force the find to happen, so that I can test if DomElement is null or not, then proceed. Something similar to findEagerly below :

private WorkFlowPage clickWorkFlowTab() {

    DomElement workflowTab=findEagerly(By.id("someElement"));

    if(workflowTab!=null){
        workflowTab.click();
    }
    else {
        find(By.id("someOtherElement")).click();
    }

    return this;
}

One alternative could be to throw a specific exception if the element is not found, to identify clearly these cases, and leave the AssertionError for real assertion issues based on should().

Extracting Downloader into a dedicated lib ?

Hi,

I'd love to re-use the net.codestory.simplelenium.driver.Downloader (alongside its Configuration & LockFile dependencies) into my project, which is not really selenium-related nor testing-stuff-related thing
(In fact, it is only intended to be a PhantomJS provisioner)

Would you see any benefit of creating a new dedicated lib for this (you may have already faced this need ?) or should I fork & create it on my own ?

If you're interested in it, I can create a PR for this (however, it will require you to create a new repository for hosting it ... and I would be more in favour that you, the author for these 3 classes, host it than I do own it :-))
Tell me WDYT, I'll do what you prefer.

Selectbox

Hi,

If I have a select with multiple options and <option value="12345">toto</option> is selected, I want to check that toto is selected and 12345 is the value selected.

I didn't see any doc or methods about it.

Javascript errors

Any way to get javascript errors easily with Simplenium ?

I'm struggling with a (angularjs) ngDialog not showing problem in my integration tests and it would be nice to see if js errors occured

Bug for nth-child

Hi,

It seems like there is a bug for nth-child

no matter use first(), nth() or directly css "#delete-category-form tr.category-item:nth-child(1) td:nth-child(2)"

All of them could not get the expected element.

Maybe is selenium issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.