What is the best approach to instantiating up objects on creating steps in a Java Page Object Model?
Does anyone know how Cucumber scripts are compiled?
I would imagine if everything is built and complied the 2nd or 3rd option below may be the best approach.
If only the steps related to the test under execution are compiled then I would imagine it would be the first.
I Have the following example:
private LoginPage loginPage = new LoginPage(driver);
#Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
new HomePage(driver).clickLoginButton();
loginPage.enterEmail("test#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
#Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
new HomePage(driver).clickLoginButton();
loginPage.enterEmail("test02#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
#Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
new HomePage(driver).clickLoginButton();
loginPage.enterEmail("test03#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
All of the above steps (plus more in the same LoginSteps.java class) start with
new HomePage(driver).clickLoginButton();
Is this the best approach, or is it better to create a single instance?
private LoginPage loginPage = new LoginPage(driver);
private HomePage homePage = new HomePage(driver);
#Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
homePage.clickLoginButton();
loginPage.enterEmail("test#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
#Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
homePage.clickLoginButton();
loginPage.enterEmail("test02#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
#Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
homePage.clickLoginButton();
loginPage.enterEmail("test03#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
OR is it even more efficient to have the instantiation all pages in a 'baseSteps' class which LoginSteps extends?
public class LoginSteps extends BaseSteps {
#Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
homePage.clickLoginButton();
loginPage.enterEmail("test#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
#Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
homePage.clickLoginButton();
loginPage.enterEmail("test02#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
#Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
homePage.clickLoginButton();
loginPage.enterEmail("test03#test.com");
loginPage.enterPassword("Password1");
loginPage.clickLogin();
}
}
public baseSteps {
protected LoginPage loginPage = new LoginPage(driver);
protected HomePage homePage = new HomePage(driver);
}
I would benchmark to see which version is most efficient.
How the Cucumber code is compiled depends on your build tool. I would assume that everything is compiled before it is being used. But a JVM that optimizes itself after some execution may behave different after a while.
I am really interested in hearing why you are thinking about performance at this level tho? Are you expecting to execute this code for hours at a time? Most of my scenarios are short, executed on the second scale and then thrown away.
Are your Cucumber tests running against the GUI? If so, it won't make a difference whether you create a single instance or multiple instances of a given Page Object. This kind of tests are slow by nature, and such a tweak will be imperceptible.
Whatever the case may be, the priority should always be a good design. As well as simplifying the future maintenance of the code, a good design will allow you to make improvements to the system performance. However, this is not true the other way around.
Cucumber and the Page Object Model fit together really well. But they should be arranged in separate layers, with Cucumber on top of the Page Object Model, acting as a client of the latter. Watching your code examples I don't understand why the Page Objects are created as part of the step definitions code. Why doesn't the clickLoginButton method itself return an instance of LoginPage?
In my opinion the Page Object Model should be supported by two basic principles:
1. API. Every Page Object should define an API with the methods corresponding to the actions a user can perform on that page.
2. Navigation. Every action should take the user to either the same page or a different page. So every method should return either the Page Object itself or a different Page Object.
The first one is fulfilled by your code, but not the second one, or at least it's not clear enough.
I would think of HomePage and LoginPage classes as something like this (without going into too much detail):
public class HomePage
{
public HomePage(WebDriver driver) {
// ...
}
public LoginPage clickLoginButton() {
perform click login button
return new LoginPage(this.driver);
}
}
public class LoginPage
{
public LoginPage(WebDriver driver) {
// ...
}
public LoginPage enterEmail(String email) {
perform enter email
return this;
}
public LoginPage enterPassword(String password) {
perform enter password
return this;
}
public NextPage clickLogin() {
perform click login
return new NextPage();
}
}
As for the Cucumber layer, I would think of it as follows (again, without going into too much detail):
private static final String PASSWORD = "Password1";
private static final String EMAIL_CUSTOMER_WITH_TWO_STORED_CARDS = "test#test.com";
private static final String EMAIL_CUSTOMER_WITH_EXPIRED_CARD = "test02#test.com";
private static final String EMAIL_CUSTOMER_WITH_THREE_STORED_CARDS = "test03#test.com";
#Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
this.performLogin(EMAIL_CUSTOMER_WITH_TWO_STORED_CARDS);
}
#Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
this.performLogin(EMAIL_CUSTOMER_WITH_EXPIRED_CARD);
}
#Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
this.performLogin(EMAIL_CUSTOMER_WITH_THREE_STORED_CARDS);
}
private void performLogin(String email) {
nextPage = homePage.clickLoginButton()
.enterEmail(email)
.enterPassword(PASSWORD)
.clickLogin();
}
See the (optional) method chaining in the last method, as a consequence of the Page Objects returned by the methods of the Page Object Model.
I have deliberately omitted the declaration of the variables homePage, loginPage and nextPage. LoginSteps does not share variables with the rest of Steps classes, even if you make them extend from a common BaseSteps class, unless you declared them as static:
public BaseSteps {
protected static HomePage homePage;
protected static LoginPage loginPage;
protected static NextPage nextPage;
...
}
Alternatively you could consider integrating a dependency injection framework, but I haven't tried myself.
Keeping the variables as private instance attributes won't work (even though they are not shared with other Steps classes) because Cucumber instantiates the Steps class for each test scenario making use of a step definition contained in the class.
Finally, there should be a hook somewhere in the Cucumber layer to initialize the entry point to the Page Object Model.
#Before
public void setUp() {
homePage = new HomePage(driver);
}
That should be the only Page Object explicitly created from the Cucumber layer.
Related
I have implemented parallel testing using Selenium and TestNG for a web application, however, my webapp got a restriction where only one session can be handled at the time (it is not supporting multi-sessions, when you login with the same user it will disconnect your older session), the solution of which I have thought to solve it is to create a unique user for each test (using API), so I have implemented it but when I am running the tests in parallel (using testng.xml file with thread-count="2") I am creating the same user twice! with the same credentials, I want to be able to create a unique user where it will create one unique user for each run.
This is my code:
public class BaseApiTest extends BaseTest {
protected String token;
protected String CREATED_ADMIN_TEST_USER;
protected String CREATED_ADMIN_TEST_PASSWORD;
private static final AtomicReference<String> ACCESS_TOKEN = new AtomicReference<>();
#BeforeClass(alwaysRun=true)
public void baseApiSetup() throws InterruptedException, ApiException {
generateToken();
createAdminUser();
}
private void generateToken() {
........
..........
...........
token = "Bearer " + ACCESS_TOKEN.get();
context.setAttribute("api-key", token);
context.setAttribute("HOST", HOST);
T_Logger.info("Host url address is: =[{}]", HOST);
T_Logger.info("new api key token =[{}]", token);
}
private void createAdminUser() throws ApiException, InterruptedException {
UsersAPIUnitTest usersAPITU = new UsersAPIUnitTest(context);
usersAPITU.createUser();
CREATED_ADMIN_TEST_USER = UsersAPIUnitTest.getEmail();
CREATED_ADMIN_TEST_PASSWORD = UsersAPIUnitTest.getPassword();
}
}
and this is used by the login page with the newly created user:
protected void adminSignIn() {
loginPage.login(CREATED_ADMIN_TEST_USER, CREATED_ADMIN_TEST_PASSWORD, true);
writeToLoggerSignIn(CREATED_ADMIN_TEST_USER);
}
and then I am starting to run my tests.
Expected: each test class will contain its own unique user
Actual: all the users that are being created are the same user with the same credentials
========================EDIT===============================
This is how I create new user:
public String createUserCms(String name, String email, String phone, String password) throws ApiException {
NewUserPayload body = new NewUserPayload();
body.setStatus(true);
body.setName(name);
body.setEmail(email);
body.setPhone(phone);
body.setPassword(password);
body.setPasswordConfirmation(password);
printBody(body);
return usersApi.createUser(token, body);
}
I have been working on a generic solution for this problem so that all automation teams can use it, still work in progress. I'm sure you would have explored other options, but creating users every time you run a test is not a good idea, rather you have to create the users once and use them otherwise you end up adding more users into your system (if you are cleaning up the users #afterMethod that's fine)
But for now, in your case, you have to make sure that you pass a unique name/email when creating the user and return that username/email address.
If you can share your createUser method snippet, we can help you more.
I want to Run Test in a same class with two credentials, and both are in same TestRunner class, I want to declare in #Beforemethod class so that no need to write same code in Every #Test.
If I understood, You want to run same test twice, but with different credentials.
If this is the case use dataProvider:
#DataProvider(name="login")
public Object[][] getData() {
return new Object[][] {
{"test2#test.com", "test",false},
{"test#test.com", "abcabc",true}
};
}
and here is method how to call it.
#Test(dataProvider="login")
public void testLogin(String usernameEmail, String password,boolean flag) throws InterruptedException {
if(flag){
Assert.assertTrue(!errorMessage.isDisplayed());
}else{
Assert.assertTrue(errorMessage.isDisplayed());
}
}
Hope I understood and it would help You,
Updated answer with pseudo code of a sort, so You could create method without annotation just simple method inside test class or in different one, depending on Your logic, and provide parameter to that method within test methods here is example:
Don't know how You data is set in #BeforeMethod, because there is no code example, but here is something:
#Test
public void testLogin_1(){
login(email, password)
}
#Test
public void testLogin_2(){
login(email2, password2)
}
private void login(String email, String password){
inputEmail(email);
inputPassword(password);
clickSubmit();
Assert.assertEquals();
// do some asserts so if You want to assert some error cases.
}
Hope this helps,
Suppose I have a test case like -
*Scenario: Facebook login test
GIVEN I am a Facebook user
WHEN I enter my user name & password
THEN login should be successful*
How could I get the scenario name from the step definition methods corresponding to "I am a Facebook user" or "I enter my user name & password" or "login should be successful" ?
Step definitions methods are -
#Given("^I am a Facebook user$")
public void method1() {
//some coding
//I want to get the scenario name here
}
#When("^I enter my user name & password$")
public void method2() {
//some coding
//I want to get the scenario name here
}
#Then("^login should be successful$")
public void method3() {
//some coding
//I want to get the scenario name here
}
You can use the #Before hook to get the current executing Scenario object.
#Before
public void beforeHook(Scenario scenario) {
this.sce = scenario
System......(scenario.getName())
System......(scenario.getId())
}
You can access the stored scenario object in your step definitions.
No #Bappa, it's possible, though your stepdefinition class is singleton and your tests are in parallel, see it be attacked with below approach by enhancing it with thread-safe static hash map variable used for storage:
public class StepDefinitions{
private static HashMap<Integer,String> scenarios;
public StepDefinitions(){ //or even inside of your singleton's getInstance();
if(scenarios == null)
scenarios = new HashMap<Integer,String();
}
#Before
public void beforeHook(Scenario scenario) {
addScenario(scenario.getName());
}
#When("your step definition")
public void stepDefinition1(){
String scenario = getScenario(); //problem-o-solved here...
}
private void addScenario(String scenario){
Thread currentThread = Thread.currentThread();
int threadID = currentThread.hashCode();
scenarios.put(threadID,scenario);
}
private synchronized String getScenario(){
Thread currentThread = Thread.currentThread();
int threadID = currentThread.hashCode();
return scenarios.get(threadID);
}
Inside the step definition, you can use CucumberHelper.scenario.getName().
Based on this API you can use getID, getSourceTagNames, getStatus and getClass methods.
Step1: Created a class A with two methods:
#Test(dataProvider="testdata")
InputToForm()
#DataProvider(name="testdata")
readDataFromExcel()
Step2: Created a class B, where I need to call the method InputToForm() with the data.
Please read about inheritance and more about core java in general before you jump into automation, else you will be stuck at every small issue.
You can resolve your issue by simply using inheritance, as mentioned below.
Your data provider in a different class
public class DataLibrary {
#DataProvider(name="loginData")
public Object[][] loginData(){
return new Object[][]{
{"username", "password"}
};
}
}
And here goes your login class.
public class Login extends DataLibrary {
#Test(dataProvider = "loginData")
public void login(String username, String password){
System.out.println("username="+username+" password="+password);
}
}
I've just started using WebDriver, and I'm trying to learn the best practices, in particular using PageObjects and PageFactory.
It's my understanding that PageObjects should expose the various operations on a web page, and isolate the WebDriver code from the test class. Quite often, the same operation can result in navigating to different pages depending on the data used.
For example, in this hypothetical Login scenario, providing admin credentials takes you to the AdminWelcome page, and providing Customer credentials takes you to the CustomerWelcome page.
So the easiest way to implement this is to expose two methods which return different PageObjects...
Login PageObject
package example;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class Login {
#FindBy(id = "username")
private WebElement username;
#FindBy(id = "password")
private WebElement password;
#FindBy(id = "submitButton")
private WebElement submitButton;
private WebDriver driver;
public Login(WebDriver driver){
this.driver = driver;
}
public AdminWelcome loginAsAdmin(String user, String pw){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, AdminWelcome.class);
}
public CustomerWelcome loginAsCustomer(String user, String pw){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, CustomerWelcome.class);
}
}
And do the following in the test class:
Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = loginPage.loginAsAdmin("admin", "admin");
or
Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = loginPage.loginAsCustomer("joe", "smith");
Alternative approach
Instead of duplicating code, I was hoping there was a cleaner way of exposing a single login() method which returned the relevant PageObject.
I thought about creating a hierarchy of pages (or having them implement an interface) so that I could use that as the return type, but it feels clumsy. What I came up with was the following:
public <T> T login(String user, String pw, Class<T> expectedPage){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, expectedPage);
}
Which means you can do the following in the test class:
Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome =
loginPage.login("admin", "admin", AdminWelcome.class);
or
Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome =
loginPage.login("joe", "smith", CustomerWelcome.class);
This is flexible - you could add an ExpiredPassword page and not have to change the login() method at all - just add another test and pass in the appropriate expired credentials and the ExpiredPassword page as the expected page.
Of course, you could quite easily leave the loginAsAdmin() and loginAsCustomer() methods and replace their contents with a call to the generic login() (which would then be made private). A new page (e.g. the ExpiredPassword page) would then require another method (e.g. loginWithExpiredPassword()).
This has the benefit that the method names actually mean something (you can easily see that there are 3 possible results of logging in), the PageObject's API is a bit easier to use (no 'expected page' to pass in), but the WebDriver code is still being reused.
Further improvements...
If you did expose the single login() method, you could make it more obvious which pages can be reached from logging in by adding a marker interface to those pages (this is probably not necessary if you expose a method for each scenario).
public interface LoginResult {}
public class AdminWelcome implements LoginResult {...}
public class CustomerWelcome implements LoginResult {...}
And update the login method to:
public <T extends LoginResult> T login(String user, String pw,
Class<T> expectedPage){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, expectedPage);
}
Either approach seems to work well, but I'm not sure how it would scale for more complicated scenarios. I haven't seen any code examples like it, so I'm wondering what everyone else does when actions on a page can result in different outcomes depending on the data?
Or is it common practice to just duplicate the WebDriver code and expose lots of different methods for each permutation of data/PageObjects?
Bohemian's answer is not flexible - you cannot have a page action returning you to the same page (such as entering a bad password), nor can you have more than 1 page action resulting in different pages (think what a mess you'd have if the Login page had another action resulting in different outcomes). You also end up with heaps more PageObjects just to cater for different results.
After trialing this some more (and including the failed login scenario), I've settled on the following:
private <T> T login(String user, String pw, Class<T> expectedPage){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, expectedPage);
}
public AdminWelcome loginAsAdmin(String user, String pw){
return login(user, pw, AdminWelcome.class);
}
public CustomerWelcome loginAsCustomer(String user, String pw){
return login(user, pw, CustomerWelcome.class);
}
public Login loginWithBadCredentials(String user, String pw){
return login(user, pw, Login.class);
}
This means you can reuse the login logic, but prevent the need for the test class to pass in the expected page, which means the test class is very readable:
Login login = PageFactory.initElements(driver, Login.class);
login = login.loginWithBadCredentials("bad", "credentials");
// TODO assert login failure message
CustomerWelcome customerWelcome = login.loginAsCustomer("joe", "smith");
// TODO do customer things
Having separate methods for each scenario also makes the Login PageObject's API very clear - and it's very easy to tell all of the outcomes of logging in. I didn't see any value in using interfaces to restrict the pages used with the login() method.
I'd agree with Tom Anderson that reusable WebDriver code should be refactored into fine-grained methods. Whether they are exposed finely-grained (so the test class can pick and choose the relevant operations), or combined and exposed to the test class as a single coarsely-grained method is probably a matter of personal preference.
You are polluting your API with multiple types - just use generics and inheritance:
public abstract class Login<T> {
#FindBy(id = "username")
private WebElement username;
#FindBy(id = "password")
private WebElement password;
#FindBy(id = "submitButton")
private WebElement submitButton;
private WebDriver driver;
private Class<T> clazz;
protected Login(WebDriver driver, Class<T> clazz) {
this.driver = driver;
this.clazz = clazz
}
public T login(String user, String pw){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, clazz);
}
}
and then
public AdminLogin extends Login<AdminWelcome> {
public AdminLogin(WebDriver driver) {
super(driver, AdminWelcome.class);
}
}
public CustomerLogin extends Login<CustomerWelcome> {
public CustomerLogin(WebDriver driver) {
super(driver, CustomerWelcome.class);
}
}
etc for all types on login pages
Note the work-around for type erasure of being able to pass an instance of Class<T> to the PageFactory.initElements() method, by passing an instance of the class into the constructor, which is known as the "type token" pattern.