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:
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:
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??
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 aHTML
list, but utilizing the POM.