Summary
With the PageFactory, how do I ensure the correct page has been passed to the appropriate page object? For example making sure that a real login page is being passed to an instance of the LoginPage object.
Details
I notice on the PageObjects documentation that they explain how, in the constructor, that you can check to see if you are on the right page. For example
// Check that we're on the right page.
if (!"Login".equals(driver.getTitle())) {
// Alternatively, we could navigate to the login page, perhaps logging out first
throw new IllegalStateException("This is not the login page");
}
However, when reading the PageFactory docs, they don't explain how to check if the correct page has been passed in. They just go ahead and attempt to run the test. How can I best check this when using PageFactory?
You could put an assertion in your constructor that checks the URL is valid e.g.
MyPage(WebDriver driver) {
PageFactory.initElements(driver, this);
assert wait.until(currentURIIs(new URI("http://www.mydomain.com/mypage.html")));
}
The above is using the following expected condition:
public static ExpectedCondition<Boolean> currentURIIs(final URI pageURI) {
new ExpectedCondition<Boolean>() {
#Override
Boolean apply(WebDriver driver) {
new URI(driver.currentUrl) == pageURI;
}
}
}
You could of course search for an element you know is on the page, or any other uniquely identifying feature. Checking the current page URI is one possible option, there are many other things you could check to ensure you are on the correct page.
The test should call page.get(). Then the get function will call isLoaded(). If page.isLoaded() doesn't throw an exception, then it will assume that its loaded, and return.
Otherwise, it will call page.load(), and then page.isLoaded() again. (to make sure that its actually loaded).
So each of your classes that extend LoadablePageComponent need to have a isLoaded() and load() function.
Related
I'm new in Selenium learning. I'm getting null pointer exception when I try to use web element - Milestone_Tile_Text.click; in my code but it works fine when I use
LoginTestScript.fd.findElement(By.linkText("Milestone")).click();
Please see below code I have used PageFactory.initElements as well but not sure how to solve this error.
public class MilestoneTileModel
{
GenerateTestData objtestdata = new GenerateTestData() ;
public MilestoneTileModel() //constructor
{
PageFactory.initElements(LoginTestScript.fd, this);
}
#FindBy(xpath="//a[text()='Milestone']")
WebElement Milestone_Tile_Text;
public void Milestone_Tile_Click()
{
Milestone_Tile_Text.click();
LoginTestScript.fd.findElement(By.linkText("Milestone")).click();
LoginTestScript.fd.findElement(By.xpath("//*#id='CPH_btnAddNewMilestoneTop']")).click();
}
}
Timing issues might occur more often when you use an init method.
The timing issue is when you init an element the driver immediately try to find the elements, on failure you will get no warning but the elements will refer null.
The above can occur for example because the page was not fully rendered or the driver see an older version of the page.
A fix can be to define the elements as a property and on the get of the property use the driver to get the element from the page
Please note that selenium does not promise the driver sees the latest version of the page so even this might break and on some situations a retry will work.
First problem what I see: You didn't set LoginTestScript
Following documentation at first you need to set PageObject variable:
GoogleSearchPage page = PageFactory.initElements(driver, GoogleSearchPage.class);
The best way to rich that point is separate Page Object Model and scenario scipt
You fist file POM should contain:
LoginTestPOM
public class LoginTestPOM {
#FindBy(xpath="//a[text()='Milestone']")
WebElement MilestoneTileText;
public void clickMilestoneTitleText(){
MilestoneTitleText.click();
}
}
TestScript
import LoginTestPOM
public class TestLogin {
public static void main(String[] args) {
// Create a new instance of a driver
WebDriver driver = new HtmlUnitDriver();
// Navigate to the right place
driver.get("http://www.loginPage.com/");
// Create a new instance of the login page class
// and initialise any WebElement fields in it.
LoginTestPOM page = PageFactory.initElements(driver, LoginTestPOM.class);
// And now do the page action.
page.clickMilestoneTitleText();
}
}
This is basis of Page Object Pattern.
NOTE: I'm writing that code only in browser so it could contain some mistakes.
LINK: https://github.com/SeleniumHQ/selenium/wiki/PageFactory
The "ugly" solution without page object pattern is:
UglyTestScript
public class UglyTestLogin {
public static void main(String[] args) {
// Create a new instance of a driver
WebDriver driver = new HtmlUnitDriver();
// Navigate to the right place
driver.get("http://www.loginPage.com/");
// DON'T create a new instance of the login page class
// and DON'T initialise any WebElement fields in it.
// And do the page action.
driver.findElement(By.xpath("//a[text()='Milestone']").click()
}
}
I'm using Behat 3 and PhantomJS 2. Currently I have a scenario defined as such:
#javascript
Scenario: I visit the blog through the Blog & Events menu.
Given I am an anonymous user
And I am on the homepage
And I follow "Link Text"
Then I should be on "/path-to-page"
When I run this with Goutte it's fine. When I run this with vanilla Selenium, it's fine (it launches a browser I can see). However, when I configure Selenium to point the webdriver host to PhantomJS, it explodes on Then I should be on "/path-to-page" claiming it's still on /.
If I add the following wait step:
#javascript
Scenario: I visit the blog through the Blog & Events menu.
Given I am an anonymous user
And I am on the homepage
And I follow "Link Text"
And I wait 4 seconds
Then I should be on "/path-to-page"
Then my scenario passes in the green, all good.
Is there a way to get PhantomJS to wait for the page to load before checking the current path? I don't want to depend on arbitrary timeouts. I need a headless solution and PhantomJS seems to be pretty well supported, but if I can't do something as simple as clicking a link and verifying the page that was loaded without adding random waiting steps everywhere, I might need to re-evaluate my decision.
Try using this implicit wait in your feature context. In my experience it has helped.
/**
* #BeforeStep
*/
public function implicitlyWait($event)
{
// Set up implicit timeouts
$driver = $this->getSession()->getDriver()->getWebDriverSession();
$driver->timeouts()->implicit_wait(array("ms" => 10000));
}
I was having the same issue, and doing something like this fails because its using the state of the current url:
$this->getSession()->wait(10000, "document.readyState === 'complete'");
So my workaround for this was adding a variable to the page every time a step is done. When I link is clicked, the variable will no long exist, this will guarantee that am working with a different page.
/**
* #AfterStep
*/
public function setStepStatus()
{
$this->getSession()->evaluateScript('window.behatStepHasCompleted = true;');
}
/**
* #When /^(?:|I )wait for the page to be loaded$/
*/
public function waitForThePageToBeLoaded()
{
$this->getSession()->wait(10000, "!window.behatStepHasCompleted && document.readyState === 'complete'");
}
You can always make use of a closure function to encapsule your steps, just as mentioned in the docs. Through it, you can get your steps to run when they're ready. Let's implement a spinner function:
public function spinner($closure, $secs) {
for ($i = 0; $i <= $secs; $i++) {
try {
$closure();
return;
}
catch (Exception $e) {
if ($i == $secs) {
throw $e;
}
}
sleep(1);
}
}
What we're doing here is wait for a number of seconds for the closure function to run successfully. When the time's run out, throw an exception, for we want to know when something's not behaving correctly.
Now let's wrap your function to assert you're in the right page within the spinner:
public function iShouldBeOnPage($wantedUrl) {
$this->spinner(function() use($wantedUrl) {
$currentUrl = $this->getSession()->getCurrentUrl();
if ($currentUrl == $wantedUrl) {
return;
}
else {
throw new Exception("url is $currentUrl, not $wantedUrl");
}
}, 30);
What we're doing here is wait up to 30 seconds to be on the url we want to be after clicking the button. It will not wait for 30 secs, but for as many secs we need until current url is the url we need to be at. Applying it in your function within the *Context.php will result in it being applied in every step you call it within your Gherkin files.
Quick question about page objects in selenium webdriver. our site is very dynamic with lots of ajax and various authentication states. It is tough to figure out how to define each page object BUT lets say I have figured that out and defined several page objects that represent our site.
How do you handle crossing from page to page. So I get a page object for my home page and one for my account page and one for my results page. Then I need to write a test that traverses all my pages to simulate a user performing multiple actions.
How do you say give me a HomePage object to create a new use -> then get a account page object to go perform some user actions - then get a result page object to verify those actions all from a single script.
How are people doing this?
thanks
When you're simulating having the user enter a new URL into the URL bar of the browser, then it's the responsibility of the test class to create the page object it needs.
On the other hand, when you're doing some operation on the page that would cause the browser to point to another page -- for example, clicking a link or submitting a form -- then it's the responsibility of that page object to return the next page object.
Since I don't know enough about the relationships between your home page, account page, and result page to tell you exactly how it'd play out in your site, I'll use an online store app as an example instead.
Let's say you've got a SearchPage. When you submit the form on the SearchPage, it returns a ResultsPage. And when you click on a result, you get a ProductPage. So the classes would look something like this (abbreviated to just the relevant methods):
public class SearchPage {
public void open() {
return driver.get(url);
}
public ResultsPage search(String term) {
// Code to enter the term into the search box goes here
// Code to click the submit button goes here
return new ResultsPage();
}
}
public class ResultsPage {
public ProductPage openResult(int resultNumber) {
// Code to locate the relevant result link and click on it
return new ProductPage();
}
}
The test method to execute this story would look something like this:
#Test
public void testSearch() {
// Here we want to simulate the user going to the search page
// as if opening a browser and entering the URL in the address bar.
// So we instantiate it here in the test code.
SearchPage searchPage = new SearchPage();
searchPage.open(); // calls driver.get() on the correct URL
// Now search for "video games"
ResultsPage videoGameResultsPage = searchPage.search("video games");
// Now open the first result
ProductPage firstProductPage = videoGameResultsPage.openResult(0);
// Some assertion would probably go here
}
So as you can see, there's this "chaining" of Page Objects where each one returns the next one.
The result is that you end up with lots of different page objects instantiating other page objects. So if you've got a site of any considerable size, you could consider using a dependency injection framework for creating those page objects.
Well, I created my own Java classes which represent the pages:
Say, the below is code to represent home page. Here user can login:
public class HomePage{
private WebDriver driver;
private WebElement loginInput;
private WebElement passwordInput;
private WebElement loginSubmit;
public WebDriver getDriver(){
return driver;
}
public HomePage(){
driver = new FirefoxDriver();
}
public CustomerPage login(String username, String password){
driver.get("http://the-test-page.com");
loginInput = driver.findElement(By.id("username"));
loginInput.sendKeys(username);
passwordInput = driver.findElement(By.id("password"));
passwordInput.sendKeys(password);
loginSubmit = driver.findElement(By.id("login"));
loginSubmit.click();
return new CustomerPage(this);
}
}
And the page for Customer can look like this. Here I am demonstrating, how to get, say, logged in user:
public class CustomerPage{
private HomePage homePage;
private WebElement loggedInUserSpan;
public CustomerPage(HomePage hp){
this.homePage = hp;
}
public String getLoggedInUser(){
loggedInUserSpan = homePage.getDriver().findElement(By.id("usrLongName"));
return loggedInUserSpan.getText();
}
}
And the test can go like this:
#Test
public void testLogin(){
HomePage home = new HomePage();
CustomerPage customer = home.login("janipav", "extrasecretpassword");
Assert.assertEquals(customer.getLoggedInUser(), "Pavel Janicek");
}
You generally want to model what a user actually does when using your site. This ends up taking the form of a Domain Specific Language (DSL) when using page objects. It gets confusing with reusable page components though.
Now that Java 8 is out with default methods, reusable page components can be treated as mixins using default methods. I have a blog post with some code samples found here that explains this in more detail: http://blog.jsdevel.me/2015/04/pageobjects-done-right-in-java-8.html
I suggest you use a framework that provides support for these patterns. Geb is one of the best one out there. Below is an example taken from their manual
Browser.drive {
to LoginPage
assert at(LoginPage)
loginForm.with {
username = "admin"
password = "password"
}
loginButton.click()
assert at(AdminPage)
}
class LoginPage extends Page {
static url = "http://myapp.com/login"
static at = { heading.text() == "Please Login" }
static content = {
heading { $("h1") }
loginForm { $("form.login") }
loginButton(to: AdminPage) { loginForm.login() }
}
}
class AdminPage extends Page {
static at = { heading.text() == "Admin Section" }
static content = {
heading { $("h1") }
}
}
I enjoy writing Selenium Webdriver tests using the Page Object pattern. But was personally annoyed at the verbosity and repetition of having to always explicitly instantiate and return the next page or page component. So with the benefit of Python's metaclasses I wrote a library, called Keteparaha, that automatically figures out what should be returned from a selenium page object's method calls.
I have another newbie question regarding registering additional dependencies within TinyIoc for use within NancyFX.
I am continuing to get the following exceptions when running the application...
Unable to resolve type: AdvancedSearchService.Interfaces.IResponseFactory
Exception Details: TinyIoC.TinyIoCResolutionException: Unable to resolve type: AdvancedSearchService.Interfaces.IResponseFactory
Source Error:
Line 25: var container = TinyIoCContainer.Current;
Line 26:
Line 27: _responseFactory = container.Resolve<IResponseFactory>();
Line 28:
Line 29:
I am currently registering my dependencies incorrectly, but I cannot seem to figure out the correct way. Below is my code within my custom bootstrapper. Also note that I am not currently calling the base.ConfigureRequestContainer method because I cannot seem to figure out how to get the current context to pass into it.
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
container.Register<IRavenSessionManager>(new RavenSessionManager());
base.ConfigureApplicationContainer(container);
ConfigureRequestContainer(container);
}
protected void ConfigureRequestContainer(TinyIoCContainer applicationContainer)
{
var requestContainer = applicationContainer.GetChildContainer();
requestContainer.Register<ISearchRepository>(new SearchRepository(requestContainer.Resolve<IRavenSessionManager>().GetSession()));
requestContainer.Register<IResponseFactory>(new ResponseFactory(requestContainer.Resolve<ISearchRepository>()));
//base.ConfigureRequestContainer(requestContainer,[I NEED THE CONTEXT])
}
Any help would really be appreciated...apparently my ignorance has no limits :)
Ok, not 100% sure where to start.. you don't need the context because you're doing it wrong :-)
Firstly, why are you calling "configure request container" at all, and why are you creating a child container? You don't do that :-) There are two scopes, application scope, configured by overriding ConfigureApplicationContainer, and request scope, configured by overriding ConfigureRequestContainer, you don't call them yourself, you just override them depending on how you want to scope your objects.
Secondly, the default Nancy bootstrapper will "autoregister" everything it can in its default implementation of ConfigureApplicationContainer. By calling "base" after you've made a manual registration you are effectively copying over your original registration by autoregister. Either don't call base, or call it before you do your manual registrations. And, again, don't call ConfigureRequestContainer from your ConfigureApplicationContainer :-)
If you don't care about everything being application scoped (so singetons get the same instance for each request) then you don't need any of this, you can just rely on autoregister.
You're currently constructing your objects manually and putting them into the container, that seems a rather odd way to do it. Normally you'd just register the types and let the container handle instantiating as and when it needs to.
You're not overriding ConfigureRequestContainer, you are just creating a new method (with a different signature).
So, what you probably want is something like:
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
base.ConfigureApplicationContainer(container);
// Autoregister will actually do this for us, so we don't need this line,
// but I'll keep it here to demonstrate. By Default anything registered
// against an interface will be a singleton instance.
container.Register<IRavenSessionManager, RavenSessionManager>();
}
// Need to override this, not just make a new method
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
// Get our session manager - this will "bubble up" to the parent container
// and get our application scope singleton
var session = container.Resolve<IRavenSessionManager>().GetSession();
// We can put this in context.items and it will be disposed when the request ends
// assuming it implements IDisposable.
context.Items["RavenSession"] = session;
// Just guessing what this type is called
container.Register<IRavenSession>(session);
container.Register<ISearchRepository, SearchRepository>();
container.Register<IResponseFactory, ResponseFactory>();
}
I have a page that is loaded into a frame. in the code behind, i have a string variable called mode. What i want to do is when a hyperlink is clicked, open the page and set the mode
I was hoping to do it declaratively. I tried doing
NavigatUri="myPage?mode=edit"
and then adding the following to the code behind after the initialize component call
mode = this.NavigationContext.QueryString["mode"];
But I was getting a page not found error. I have a feeling I'm on another planet. I'm new to silverlight. How do i navigate to a page in this fashion and pass that argument?
Take a look at the UriMapper in you main page. The final UriMapping would normally be the catch all that looks like this:-
<uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
You will note the "/" at the beginning of the Uri and the corresponding page belongs in the "/Views" folder.
Use the attribute:-
NavigatUri="/myPage?mode=edit"
make sure your page in the Views folder.
However I'm pretty sure you already have that. Your real problem is your attempt to access the NavigationContext in the execution the page constructor. Its not available at that point in the pages lifecycle. You should not attempt to use it until OnNavigatedTo is executed.
public partial class MyPage : Page
{
public MyPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string mode = null;
if (NavigationContext.QueryString.ContainsKey("mode"))
{
mode = NavigationContext.QueryString["mode"];
}
// Do stuff with mode.
}
}
The reason you are seeing "page not found error" is thats because the Nav apps ErrorWindow just assumes any failure to load a page was because it wasn't found. Which assuming you've coded your pages correctly is probably a reasonable assumption.