Serenity BDD screenplay verify multiple text elements on the webpage - serenity-bdd

How do I verify multiple text elements and links in the bdd using Serenity BDD ?
I am using below code but using this approach i have to write same copy of code for every element on the webpage which is timeconsuming, is there any alternate way to parametrize and verify values
private static final String APIBUILDER = "app-data-api-card .card-header";
#Subject("the displayed notebook")
public static class APIBUILDER implements Question<String> {
#Override
public String answeredBy(Actor actor) {
return BrowseTheWeb.as(actor).findBy(APIBUILDER).getText();
}
public static Question<String> value() { return new APIBUILDER(); }

You can use the Ensure library
static By FIRST_NAME_FIELD = By.id("first_name");
static By LAST_NAME_FIELD = By.id("last_name");
actor.attemptsTo(
Ensure.that(FIRST_NAME_FIELD).text().isEqualTo("Foo")
Ensure.that(LAST_NAME_FIELD).text().isEqualTo("Bar")
);
If you want soft assertions, you could also do this:
Ensure.enableSoftAssertions();
actor.attemptsTo(
Ensure.that(FIRST_NAME_FIELD).text().isEqualTo("Foo")
Ensure.that(LAST_NAME_FIELD).text().isEqualTo("Bar")
);
Ensure.reportSoftAssertions();

Actually the example with soft assertions doesn't work as intended. When the first check fails then the next one is not executed.

Related

Selenium WebDriver based framework using POM with java

I am trying to write code to validate a webpage (Test Form with 3 required fields firstname, lastname, phone and 2 buttons submit and clear form) using POM with Selenium WebDriver with Java.
This is the code which I have written so far. I want to confirm whether I am going in the right way.
public class TestForm {
WebDriver driver;
By firstName=By.id("fname");
By lastName=By.id("lname");
By phoneno=By.id("phone");
By submit=By.id("submit");
By clearForm=By.xpath("//tagname[#type='button']");
public TestForm(WebDriver driver)
{
this.driver=driver;
}
public void typeFirstName(String fname)
{
driver.findElement(firstName).sendKeys(fname);
}
public void typeLastName(String lname)
{
driver.findElement(lastName).sendKeys(lname);
}
public void typePhone(String phone)
{
driver.findElement(phoneno).sendKeys(phone);
}
public void clickSubmit()
{
driver.findElement(submit).click();
}
public void clickClearForm()
{
driver.findElement(clearForm).click();
}
}
public class VerifyTestForm {
#Test
public void verifyValidTestForm()
{
WebDriver driver=new FirefoxDriver();
driver.manage().window().maximize();
driver.get("url of the application");
TestForm form=new TestForm(driver);
form.typeFirstName("John");
form.typeLastName("Adams");
form.typePhone("1234567890");
form.clickSubmit();
form.clickClearForm();
driver.quit();
}
}
Most code look good, except following items:
1) By clearForm=By.xpath("//tagname[#type='button']");
tagname should be a correct tag, like button or 'input'
2) After click Submit, the page still stay the form page, If so call clickClearForm should work.
form.clickSubmit();
form.clickClearForm();
3) There is no any check point/validation in your code, all are operateion on page.
// Assume an new page will open after click Submit button
// You need to check the new page opened by check page title if it'll change
// or check an element belongs to the new page is displayed
Assert(driver.getTitle()).toEqual('xxx')
Assert(driver.findElement(xxx).isDisplay()).toBe(true)
// above code may not exact correct, dependent on you choose Junit, TestNG
// or third-party Assert library.
// After click `Clear/Reset` button, you should check all fields reset to default value
Assert(form.readFirstName()).toEqual("")
4) For test class name VerifyTestForm, it's better start or end with Test, like Testxxx or xxxTest
As your code is correct but it is not the way to implementing Page object Model.
You have to use concept of DataProvider to implement framework.
Make a excel sheet and extract the data by using DataProvider.
Make a new class file from where you can read your excel data.
Make a function which return 2d data of the file.
So By using this, The way to implement the framework.
Page object Model generally says that we should have to make the separate page of each module which we are using and return the reference of the last page.

Can "driver.findElement" be used as method and be called in a program

I am using "driver.findElement" multiple times. So can it be saved in a method and called multiple times?
driver.findElement(By.xpath("//tbody[#id='detailsstockid']/tr/td[12]/a/input")).click();
driver.findElement(By.id("supplier_name")).click();
driver.findElement(By.xpath("//select[#id='supplier_name']/option[7]")).click();
driver.findElement(By.id("catagory_name")).click();
driver.findElement(By.id("productname")).sendKeys("AutoProductNew");
driver.findElement(By.id("productcode")).sendKeys("ap02");
You just need a wrapper like this
public static WebElement findElement(By locator){
WebElement anElement = driver.findElement(locator);
return anElement;
}
findElement(By.id("id")).click();
Basically you can call whatever method available to the object
I think what you are trying to do is to simplify your code... maybe make it more human readable. This is a noble goal but I would suggest that you take a different approach. Without more info, I don't know what your typical script is like. From the example code you provided, I would assume that you are going to a few pages, clicking on things, filling in textboxes, etc. I would suggest that you look into the page object model.
There are a LOT of references on the web on this topic, here's a good one to start with.
http://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern
The basic concept is create one Class file per page. [Page doesn't always literally mean an entire page. For example, I would create a separate page object for each popup dialog.] What this allows you to do is to consolidate all the code specific to that page into a single location. If you know what an API is and understand it, you will basically create an API for each page/dialog you touch. If done properly, it will greatly simplify and organize your code and will make creating new scripts drastically easier and they will be much easier to read. The benefits are numerous... one big one is code maintenance is made SO much easier. Imagine you have 100 scripts that all start by logging into the site. Let's assume the design of the login page changes... without the page object model, you will have to update all 100 scripts individually. If you are using the page object model, you open the LoginPage.java class, make the change to the Login() method, and you just fixed all 100 scripts.
You may be asking... how does this answer my question? What it does is turns
driver.findElement(By.id("supplier_name")).click();
into
somePage.clickSupplierName();
in your actual script. I think this is more along the lines of what you are asking without answering your specific question. The .findElement code still exists, but is "hidden away" in the page object.
A simple example. I wrote a script, with supporting page objects, to navigate to Amazon.com, search for Star Wars, return the # of found items, and then navigate to the Today's Deals page. The entire code is below.
program.java (Main script)
public class program
{
public static void main(String[] args)
{
WebDriver driver = new FirefoxDriver();
driver.manage().window().maximize();
driver.get("https://www.amazon.com");
// HOME PAGE
HomePage homepage = new HomePage(driver);
homepage.search("star wars");
// SEARCH RESULTS PAGE
SearchResultsPage searchResultsPage = new SearchResultsPage(driver);
System.out.println(searchResultsPage.getNumberOfResults());
// navigate to the Today's Deals page
searchResultsPage.clickTodaysDealsLink();
// TODAY'S DEALS PAGE
// do more stuff
driver.close();
driver.quit();
System.out.println("Done");
}
}
HomePage.java (Home page page object)
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class HomePage
{
WebDriver driver;
private By searchTextboxLocator = By.id("twotabsearchtextbox");
public HomePage(WebDriver driver)
{
this.driver = driver;
}
public void search(String searchString)
{
driver.findElement(searchTextboxLocator).sendKeys(searchString + "\n");
}
}
SearchResultsPage.java (Search results page page object)
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class SearchResultsPage
{
WebDriver driver;
private By todaysDealsLinkLocator = By.linkText("Today's Deals");
private By numberOfResultsLocator = By.id("s-result-count");
public SearchResultsPage(WebDriver driver)
{
this.driver = driver;
}
public String getNumberOfResults()
{
// grabs only the total count of search results from the string in the form "1-16 of 7,488,146 results for "
return driver.findElement(numberOfResultsLocator).getText().split(" ")[2];
}
public void clickTodaysDealsLink()
{
driver.findElement(todaysDealsLinkLocator).click();
}
}

Is Page Object Model linking compatible with Cucumber's Gherkin?

With Test Automation's Page Object Model we link pages together like this:
WebDriver driver = new WebDriver()
HomePage homePage = new HomePage(driver);
LoginPage loginPage = homePage.GoToLoginPage();
WelcomePage welcomePage = loginPage.Login();
etc
etc
The big benefit of this is if the Devs change the homepage so it no longer links to the loginpage, I can update my homepage class and see all the tests I need to update (with errors) before even running a test.
With Gherkin however, each row above would form a separate 'Step' and therefore a separate method. Therefore, how can this linking be done?
Is the only way to place instances of the page object classes (e.g. homePage, loginPage, etc) into a cross gherkin statement persistant store (e.g. like a specflow POCO or 'World')?
Ok so having asked numerous dev and test automation experts, it seems the solution is to continue with linking [e.g. WelcomePage welcomePage = loginPage.loginWithValidUser(validUser)] is the way to go.
To persist instance of page objects across steps (e.g. welcomePage in example above) you can use dependency injection tool (creating functionality similar to World extensions in Ruby's implementation of cucumber).
Here is more info:
https://cukes.info/docs/reference/java-di
However, most projects will benefit from a Dependency Injection module
to organize your code better and to share state between Step
Definitions.
More info from SpecFlow (the .net official cucumber implementation):
http://specflow.org/getting-started/beyond-the-basics/
And finally, I have created a whole blog around this area that might help people out, since gherkin/page object interaction is a subject of great interest to me:
http://www.seligmanventures.com/dev-blog/test-automation-page-object-model-with-gherkin
When it comes to most websites (where url's can be used), in my opinion it is best practice to simply use the url instead of an action to get to that same url.
For instance:
# Suggested by OP:
driver = Selenium::Webdriver.for :chrome, prefs: prefs
homepage = Homepage.new(driver)
login = homepage.go_to_login
welcome = login.log_in_as('dave4429')
# My Suggestion:
homepage = Url.new('/')
login = Url.new('/login')
welcome = Url.new('/welcome')
This means that you start from a url instead of having to start at the homepage for every test. You would still have the methods that you suggested, but they would be used in other areas, in order to make sure that the user can access the page through means other than the url.
However, this is not a one stop shop solution. With mobile and desktop applications, your only option may be to go through the home screen, in which case, the method you suggested is definitely the one to go for.
"Page objects themselves should never make verifications or assertions. This is part of your test and should always be within the test’s code, never in an page object." - Selenium HQ
The example I gave was a very basic one, and I would most likely wrap these into modules and classes, to enable coding like this:
google = Project::Pages::Google.new
google.search_for('Hello, World!')
expect(google.found_result?).to_equal(true)
Edit
In addition to this, you seem to have a misconception about how Cucumber works with Gherkin.
You can have multiple lines of code per step, as the step itself is a description of the actions within the step.
For instance:
Given I am logged in as "dave4429"
When I have submitted the "Contact Us" form with the following data:
| dave4429#example.com | David McBlaine | I want to find out more about your Data Protection services, can I talk to a staff member or get a PDF? |
Then an email should be sent to "support#example.com" with the details specified
The definition for the "When" may look like this:
When(/^I have submitted the "Contact Us" form with the following data:$/) do |table|
rows = table.raw
row = rows[0]
contact_us.fill_form({email: row[0], username: row[1], message: row[2]})
contact_us.submit_message
expect(browser.title).to_equal("Message Sent!")
end
It all depends on how much you break down the steps within the definition.
Edit #2
It's also clear to me that you want to do method chaining, something in the way of contact_us.fill_form({email: row[0], username: row[1], message: row[2]}).submit_message, which again, isn't out of the question while using the techniques that I'm suggesting, but the question of whether this chaining should be for each individual page, or whether everything should be included in one class or module, can only be answered by your needs.
It's just my opinion that this would put too much into a single class, and that breaking down that class will allow for more control to be given to the testers, and less redundant code will be written.
Another option I have seen recently would be to store the Page Object instances as Static variables that can be accessed from any class?
After much discussion on this topic, an equally plausible alternative is to not return instances of new pages when using the page object pattern with gherkin. You will lose the benefit of linking that you normally get with POM, but the code will arguably read better and be less complex. Posting this alternative answer, so as a test community we can vote which method is peoples preference.
This can be a bit tricky with Cucumber and Selenium. I've developed a pattern that involves extension methods to the IWebDriver interface for Selenium allowing me to navigate to specific pages using the page objects. I register the IWebDriver object with the SpecFlow dependency injection framework, and then my step definition classes are free to initialize whichever page objects they need.
Registering Selenium Web Driver With SpecFlow
You just need to plug in to the before/after scenario hooks to manage the web driver object:
[Binding]
public class WebDriverFactory
{
private readonly IObjectContainer container;
public WebDriverFactory(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
var driver = new ChromeDriver(...);
// Configure Chrome
container.RegisterInstanceAs<IWebDriver>(driver);
}
[AfterScenario]
public void DestroyWebDriver()
{
var driver = container.Resolve<IWebDriver>();
if (driver == null)
return;
// Capture screenshot if you want
// var photographer = (ITakeScreenshot)driver;
driver.Quit();
driver.Dispose();
}
}
Then It's a matter of gluing step definitions and page objects together using some extensions on the IWebDriver interface.
Selenium Page Objects
Keep your page objects navigating to one another. For instance the HomePage allows you to navigate to the "Create blog post" page, and returns the page object for that page:
public class HomePage
{
private readonly IWebDriver driver;
private readonly WebDriverWait wait;
private IWebElement CreatePostLink => driver.FindElement(By.LinkText("Create New Blog Post"));
public HomePage(IWebDriver driver)
{
this.driver = driver;
wait = new WebDriverWait(driver, 30);
}
public AddEditBlogPostPage ClickCreatePostLink()
{
CreatePostLink.Click();
wait.Until(d => d.Title.Contains("Create new blog post"));
return new AddEditBlogPostPage(driver);
}
}
And subsequently, the AddEditBlogPostPage returns the BlogPostListingPage when you create a new blog post:
public class AddEditBlogPostPage
{
private readonly IWebDriver driver;
private IWebElement Title => driver.FindElement(By.Id("Title"));
private IWebElement PostDate => driver.FindElement(By.Id("Date"));
private IWebElement Body => driver.FindElement(By.Id("BodyText"));
private IWebElement SaveButton => driver.FindElement(By.XPath("//button[contains(., 'Save Blog Post')]"));
public AddEditBlogPostPage(IWebDriver driver)
{
this.driver = driver;
}
public BlogPostListingPage CreateBlogPost(BlogPostDataRow data)
{
Title.SendKeys(data.Title);
PostDate.SendKeys(data.Date.ToShortDateString());
Body.SendKeys(data.Body);
SaveButton.Click();
return new BlogPostListingPage(driver);
}
}
Step Definitions To Glue Things Together
The step:
When I create a new blog post:
| Field | Value |
| Title | Selenium Page Objects and Cucumber |
| Date | 11/1/2019 |
| Body | ... |
Would have this definition:
[Binding]
public class BlogPostSteps
{
private readonly IWebDriver driver;
public BlogPostSteps(IWebDriver driver)
{
this.driver = driver;
}
[When(#"I add a new blog post:")]
public GivenIAmAddingANewBlogPost(Table table)
{
var addBlogPostPage = driver.GoToCreateBlogPostPage();
var blogPostData = table.CreateInstance<BlogPostDataRow>();
addBlogPostPage.CreateBlogPost(blogPostData);
}
}
The driver.GoToCreateBlogPostPage(); is an extension method on IWebDriver that kicks off the navigation from one page object to another:
public static class SeleniumPageNavigationExtensions
{
public static AddEditBlogPostPage GoToCreateBlogPostPage(this IWebDriver driver)
{
var homePage = new HomePage(driver);
return homePage.ClickCreatePostLink();
}
}
This gives you the flexibility to keep your page objects "pure" and devoid of SpecFlow, Cucumber and Gherkin. You can use these same extension methods and page objects in other tests that do not utilize Gherkin or behavior driven development. This allows for easy reuse of your test classes. Your test projects should be just as purposefully architected as the actual application it tests.

What is the difference in setBrowserUrl() and url() in Selenium 2 web driver for phpunit?

In many examples, I have seen calls made to both webdriver->setBrowserURL(url) and webdriver->url(url). Why would I want to use one instead of the other. One such example shows using both in the same manner (taken from the phpunit manual):
<?php
class WebTest extends PHPUnit_Extensions_Selenium2TestCase
{
protected function setUp()
{
$this->setBrowser('firefox');
$this->setBrowserUrl('http://www.example.com/');
}
public function testTitle()
{
$this->url('http://www.example.com/');
$this->assertEquals('Example WWW Page', $this->title());
}
}
?>
Why would setBrowserUrl() be called once in setup -- and then url() be called with the identical url in the test case itself?
In other examples, I've seen url() called with just a path for the url. What is the proper usage here? I can find almost no documentation on the use of url().
setBrowserUrl() sets a base url, allowing you to use relative paths in your tests.
The example from the phpunit manual is kind of confusing - I believe setBrowserUrl() is being used during setup simply because it'll throw an error without it:
public function start()
{
if ($this->browserUrl == NULL) {
throw new PHPUnit_Framework_Exception(
'setBrowserUrl() needs to be called before start().'
);
}
$this->url will use this base if a relative path is given.

How could you write a salesforce test class for a simple user-agent lookup?

I'm looking to write a test for a function that just returns a value - that's it. I'm not sure how you could do that. I'm under the impression you have to use system.assert or something. New to SFDC, but have programmed in many other languages. Here's some sample code:
static String getBrowserName()
{
String userAgent = ApexPages.currentPage().getHeaders().get('User-Agent');
if (userAgent.contains('iPhone'))
return 'iPhone-Safari';
if (userAgent.contains('Salesforce'))
return 'Salesforce';
if (userAgent.contains('BlackBerry'))
return 'BlackBerry';
if (userAgent.contains('Firefox'))
return 'Firefox';
if (userAgent.contains('Safari'))
return 'Safari';
if (userAgent.contains('internet explorer'))
return 'ie';
return 'other';
}
How can you obtain 100% test coverage for that?
While Salesforce's lack of a mocking framework is infuriating because of the hoops you have to jump through when testing things like page controllers, it's important to think about what you want to test here. Assuming that what you specifically want to test is that given the user agent strings your code returns the appropriate string, then I think something like the following should work:
static String getBrowserName(string userAgentStringToTest)
{
PageReference pageRef = getPageReference(userAgentStringToTest);
String userAgent = getUserAgent(pageRef);
...
}
PageReference getPageReference(string userAgentStringToTest)
{
if(userAgentStringToTest.Length == 0)
{
return ApexPages.currentPage();
}
else
{
PageReference pageRef = new PageReference('someURL');
pageRef.getHeaders().put('User-Agent', userAgentStringToTest);
return pageRef;
}
}
String getUserAgent(PageReference pageRef)
{
pageRef.getHeaders().get('User-Agent');
}
You would then call the getBrowserName method with the empty string in your production code and with the string you want to test in your test code.
There are a few different flavours to this of course - you could overload the methods and have a parameterless method for the main code and a parameterized method for testing. It's not ideal, but I don't know of another way to do it on the force.com platform currently.
EDIT: Just for completeness, I'm adding sample tests to clarify things. My example showed how to refactor the production code to make it testable, but did not give an example of how to write a test like the OP asked for.
Your tests would look something like this:
static testMethod void checkIPhoneBrowser()
{
String actualBrowserName = getBrowserName('string containing iPhone somewhere');
String expectedBrowserName = 'iPhone-Safari';
System.assertEquals(expectedBrowserName , actualBrowserName );
}
static testMethod void checkIEBrowser()
{
String actualBrowserName = getBrowserName('string containing internet explorer somewhere');
String expectedBrowserName = 'ie';
System.assertEquals(expectedBrowserName , actualBrowserName );
}
...