2

note: Looking for simplest possible example of PageFactory and fragments. I've been referencing an article which uses additional libraries I wasn't aware of.

The desired structure:

martin fowler

Where there's an "Album Page" and "Album List" page. I'm attempting to deal with the "Album List Page", except for books.

Struggling with composition, the HomePage constructor employs PageFactory by invoking initElements with the WebDriver instance it receives. What should be passed to the Catalog instance? Just the WebElement for the desired fragment, here sideCategoryContainer?

HomePage(WebDriver webDriver) {
    LOG.fine(webDriver.getCurrentUrl());
    this.webDriver = webDriver;
    PageFactory.initElements(webDriver, this);
    LOG.fine(sideCategoryXPath);
    WebElement sideCategoryContainer = webDriver.findElement(By.xpath(sideCategoryXPath));
    LOG.fine(sideCategoryContainer.getAttribute("innerHTML"));
    Catalog catalogueFragment = new Catalog(sideCategoryContainer);
}

when a Catalog object is instantiated, does it need a WebDriver reference to invoke initElements through PageFactory? Or, is that implicit with the WebElement which is received in the constructor?

package dur.bounceme.net.SeleniumBase;

import java.util.List;
import java.util.logging.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class Catalog {

private static final Logger LOG = Logger.getLogger(Catalog.class.getName());
private WebElement container = null;
private final String listItem = "//ul";

@FindBy(xpath = "//ul/li/ul/li/a")
private final List<WebElement> listItems = null;

private Catalog() {
}

public Catalog(WebElement container) {
    this.container = container;
    LOG.info(container.getAttribute("innerHTML"));
    PageFactory.initElements((WebDriver) this.container, this);
    WebElement items = container.findElement(By.xpath(listItem));
    Category categoryFragment = new Category(items);
}

public void iterate() {
    LOG.fine(container.toString());
    LOG.fine(container.getTagName());
    LOG.fine(container.getText());
    LOG.fine(container.getLocation().toString());
    for (WebElement webElement : listItems) {
        LOG.fine(webElement.getText());
        LOG.info(webElement.getAttribute("href"));
    }
}

}

The notion being that a Catalog instance receives just a fragment, just an HTML list which it will iterate, passing each item, in turn, to a Category object. Just as it received its container, then Catalog will pass a container of WebElement to a Category instance, as:

streams

but, first, HomePage has to pass a fragment of HTML to its Catlog instance, which then will iterate the list, as above, passing each list fragment to a new Category object.

Below, the Catalog instance receives the container correctly, because it's able to output the HMTL list.

Executing: gradle :run
Arguments: [-PcmdLineArgs=, -PjvmLineArgs=, -c, /home/thufir/NetBeansProjects/com.toscrape.books.selenium/settings.gradle.kts]

> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE

> Task :run
1546003872486   mozrunner::runner   INFO    Running command: "/usr/lib/firefox/firefox" "-marionette" "--headless" "-foreground" "-no-remote" "-profile" "/tmp/rust_mozprofile.PfcniQDUAugm"
*** You are running in headless mode.
1546003874033   [email protected] WARN    Loading extension '[email protected]': Reading manifest: Invalid host permission: resource://pdf.js/
1546003874034   [email protected] WARN    Loading extension '[email protected]': Reading manifest: Invalid host permission: about:reader*
1546003878803   Marionette  INFO    Listening on port 32943
1546003878818   Marionette  WARN    TLS certificate errors will be ignored for this session
Dec 28, 2018 5:31:18 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: W3C
JavaScript warning: http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js, line 1: Using //@ to indicate sourceMappingURL pragmas is deprecated. Use //# instead
Dec 28, 2018 5:31:35 AM dur.bounceme.net.SeleniumBase.Catalog <init>
INFO: 


                    <li>
                        <a href="catalogue/category/books/travel_2/index.html">

                                Travel

                        </a>

                        </li>


..

                    <li>
                        <a href="catalogue/category/books/crime_51/index.html">

                                Crime

                        </a>

                        </li>


Exception in thread "main" java.lang.ClassCastException: org.openqa.selenium.remote.RemoteWebElement cannot be cast to org.openqa.selenium.WebDriver
    at dur.bounceme.net.SeleniumBase.Catalog.<init>(Catalog.java:26)
    at dur.bounceme.net.SeleniumBase.HomePage.<init>(HomePage.java:26)
    at dur.bounceme.net.SeleniumBase.App.scrapeBooks(App.java:22)
    at dur.bounceme.net.SeleniumBase.App.main(App.java:16)

> Task :run FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':run'.
> Process 'command '/usr/lib/jvm/java-8-openjdk-amd64/bin/java'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 25s
3 actionable tasks: 1 executed, 2 up-to-date



Build failure (see the Notifications window for stacktrace): gradle :run

What I run into is that in order to iterate the list initElements needs to be invoked -- yet, seemingly, this creates an error.

The documentation I found on this:

If you notice, I still use ‘FindBy‘ annotation. But instead of using plain WebElement, I use the Page fragments classes we have created. Above Google Search page object looks better than how it would have been if we had considered all the elements of the page in one single class. As you see we do NOT use any ‘new‘ keyword to create an instance of the fragments. Graphene makes it easy for us by automatically injecting the instance of the page fragments on the fly and delegates the behaviors of the page to the corresponding page fragment classes.

I'm not sure what Graphene is but I'm not inclined to add it at this point; trying to keep it as simple as possible. Unless adding Graphene increases the simplicity for me??

3
  • What is exactly your question? Dec 28, 2018 at 12:54
  • I was referencing an article which uses graphene: sqa.stackexchange.com/q/37081/8900 so I went down a false path a bit. Cannot grok how to use composition with the POM. I'm trying to just iterate through a HTML list, but utilizing the POM.
    – Thufir
    Dec 28, 2018 at 14:00
  • 2
    Try to keep your questions atomic and complete on its own - people are not looking on all the questions all the time. I would suggest you to re-write the question to mention exactly what you want to iterate, so that people can suggest a refactoring. Dec 28, 2018 at 14:08

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.