How do I tell Graphene to load my @Page?

1.7k views Asked by At

TL;DR: How do I tell Graphene which (relative) URL to load for the @Page-injected object?


I'm trying to setup my web frontend integration tests using the "State of the art", namely a combination of Arquillian, Arquillian Drone, Selenium 2 and Graphene 2.

With Arquillian, Drone and plain Selenium WebDriver (i.e. without any Graphene specific stuff), a test can look like this:

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;

import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;

import org.junit.Assert;
import org.junit.Test;

import org.junit.runner.RunWith;

import org.openqa.selenium.WebDriver;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RunWith(Arquillian.class)
public class LoginIT {
    private static final Logger LOG = LoggerFactory.getLogger(LoginIT.class);

    @Deployment
    public static WebArchive createDeployment() {

        return Maven.resolver().loadPomFromFile("pom.xml")
                    .resolve("de.zalando:purchasing-frontend-general:war:?")
                    .withoutTransitivity().asSingle(WebArchive.class);
    }

    @Drone
    WebDriver driver;

    @ArquillianResource
    URL deploymentUrl;

    @Test
    @RunAsClient
    public void testLoginPage() {
        LOG.info("executing test!");

        driver.get(deploymentUrl + "");

        final String title = driver.getTitle();
        LOG.info("title: " + title);
        Assert.assertEquals("Login page title is wrong",
                            "Zalando General Purchasing – Login", title);
    }
}

(It took me a while to get the deployment right, because of non-current documentation for ShrinkWrap.)

This simple test just checks if we are redirected from the home page to the login page, and that its title is right. It works.

Graphene 2 adds an important new Feature, "autowiring" Page abstractions. Then my test could look like this (omitting imports and the deployment part as above):

@RunWith(Arquillian.class)
public class LoginIT {
    private static final Logger LOG = LoggerFactory.getLogger(LoginIT.class);

    @Deployment
    public static WebArchive createDeployment() {
        // as above
    }

    @Page
    LoginPage loginPage;


    @Test
    @RunAsClient
    public void testLoginPage() {
        String title = loginPage.getTitle();
        LOG.info("title: " + title);
        Assert.assertEquals("Login page title is wrong",
                            "Zalando General Purchasing – Login", title);
    }
}

Using this "Page abstraction" object:

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class LoginPage {

    @FindBy(tagName = "title")
    private WebElement titleElement;

    public String getTitle() {
        return titleElement.getText();
    }
}

This actually works, my test doesn't fail and even outputs the right title. But this doesn't work at all (I don't know why I seemed to think it does, I must have looked at the output of the previous test) ...

I get an "org.openqa.selenium.NoSuchElementException: Unable to locate element with name: title" with a really long stack trace (through all of Arquillian, Graphene, the Java Reflection API, the Maven Surefire Plugin and two lines of my code).

Debugging into the method shows that it finally tries to access the Html-Element, and the HtmlPage at hand is an about:blank-page, not my login page.

Is this how it is supposed to work? And why doesn't it work for me?

Then I want to test other pages than the login page, and for this I need to use other start URLs.

With plain Drone+ Selenium Webdriver, I would simply use

driver.get(deploymentUrl + "/otherPage");

as the first line of the test. But what to do with the Graphene 2 @Page-Annotation?

How do I tell Graphene which URL (relative to the deploymentUrl) to load for the @Page-injected object?

2

There are 2 answers

2
Petr Mensik On

I personally use inheritance and generics to achieve this (however it's maybe not the best solution). You can look for example here. You need to define AbstractPage like this

public interface AbstractPage {

    public abstract String getURL();
}

@RunWith(Arquillian.class)
public abstract class AbstractWebDriverTest<P extends AbstractPage> {

    @Page
    private P page;

    @Before
    public void goToUrl() {
        driver.get(deploymentURL + page.getURL());
    }
}

Then each test class is going to implement AbstractPage with the page specified in <>. Take a look at that sample project, I hope that helps!

0
gooseka On

The behavior is expected. Graphene initializes Page Objects fields.

To load page automatically, you can encapsulate the location of the page object within @Location annotation. Together with @InitialPage, you can achieve the desired behavior.

Your Page Object declaration would look like:

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import import org.jboss.arquillian.graphene.page.Location;

@Location("login.html")
public class LoginPage {

    @FindBy(tagName = "title")
    private WebElement titleElement;

    public String getTitle() {
        return titleElement.getText();
    }
}

Your test:

@Test
@RunAsClient
public void testLoginPage(@InitialPage LoginPage loginPage) {
    String title = loginPage.getTitle();
    LOG.info("title: " + title);
    Assert.assertEquals("Login page title is wrong",
                        "Zalando General Purchasing – Login", title);
}

Now, the login.html will be loaded automatically relatively to your contextRoot as the first action during test execution. You can read more about this features at documentation.