I am creating a UI test automation framework using POM design pattern. After reading SeleniumHQ page for Page Objects, I am contemplating what all methods should I create inside a Page Object.
Let's take a simple example of login page object consisting of username, password textboxes and submit button. SeleniumHQ link has created the below methods :
1. typeUsername(String username)
2. typePassword(String password)
3. submitLogin()
4. submitLoginExceptionFailure()
5. loginAs(String username, String password)
I am a bit perplexed while looking at these methods. Why should I create first 3 methods(typeUsername, typePassword,submitLogin), when I already am creating a loginAs method. Any thoughts around it ?
SeleniumHQ Link - https://github.com/SeleniumHQ/selenium/wiki/PageObjects
Pasting the code PageObject code for LoginPage :
public class LoginPage {
private final WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
// 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");
}
}
// The login page contains several HTML elements that will be represented as WebElements.
// The locators for these elements should only be defined once.
By usernameLocator = By.id("username");
By passwordLocator = By.id("passwd");
By loginButtonLocator = By.id("login");
// The login page allows the user to type their username into the username field
public LoginPage typeUsername(String username) {
// This is the only place that "knows" how to enter a username
driver.findElement(usernameLocator).sendKeys(username);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
// The login page allows the user to type their password into the password field
public LoginPage typePassword(String password) {
// This is the only place that "knows" how to enter a password
driver.findElement(passwordLocator).sendKeys(password);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
// The login page allows the user to submit the login form
public HomePage submitLogin() {
// This is the only place that submits the login form and expects the destination to be the home page.
// A seperate method should be created for the instance of clicking login whilst expecting a login failure.
driver.findElement(loginButtonLocator).submit();
// Return a new page object representing the destination. Should the login page ever
// go somewhere else (for example, a legal disclaimer) then changing the method signature
// for this method will mean that all tests that rely on this behaviour won't compile.
return new HomePage(driver);
}
// The login page allows the user to submit the login form knowing that an invalid username and / or password were entered
public LoginPage submitLoginExpectingFailure() {
// This is the only place that submits the login form and expects the destination to be the login page due to login failure.
driver.findElement(loginButtonLocator).submit();
// Return a new page object representing the destination. Should the user ever be navigated to the home page after submiting a login with credentials
// expected to fail login, the script will fail when it attempts to instantiate the LoginPage PageObject.
return new LoginPage(driver);
}
// Conceptually, the login page offers the user the service of being able to "log into"
// the application using a user name and password.
public HomePage loginAs(String username, String password) {
// The PageObject methods that enter username, password & submit login have already defined and should not be repeated here.
typeUsername(username);
typePassword(password);
return submitLogin();
}
}
You might want to check if typing the username only and then clicking the submit button shows a correct error message, or only the password, etc.
I usually look at the page and try to summarize what "actions" the user can do on that page, every action becomes a method. The different actions might be on different "levels". e.g. on a blog website, the user can enter the blog-title and the blog content, that are two actions the user can do, but looking from an other abstraction layer the user wants to "create" a post. So that function might again call other functions.
Basically its like any other programming, you have multiple abstraction layers, that is why you have Page Objects in the first place.
And just use iterative development, create a function that does what you want to test and if you find yourself reusing the same code (or identifier) in other functions, separate those out in a new function
Finely chopping your code into more methods allows for greater atomization. Nothing stops you from grouping the typeUsername(), typePassword() and submitLogin() into login() method.
The upside of doing so is knowing more precisely that your login failed at, say, typing password as opposed to "somewhere on the login page".
Related
I am trying to add new field to the user profile (student number) and allow users to login using either email or the new field (student number) with the same password for both.
I have overridden login.jsp to allow both Email and Student Number.
My idea is to override the login action command with something similar to the code below:
#Component(
property = {
"javax.portlet.name=com_liferay_login_web_portlet_LoginPortlet",
"mvc.command.name=/login/login"
},
service = MVCActionCommand.class
)
public class CustomLoginActionCommand extends BaseMVCActionCommand {
#Override
protected void doProcessAction(ActionRequest actionRequest,
ActionResponse actionResponse) throws Exception {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
WebKeys.THEME_DISPLAY);
HttpServletRequest request = PortalUtil.getOriginalServletRequest(
PortalUtil.getHttpServletRequest(actionRequest));
HttpServletResponse response = PortalUtil.getHttpServletResponse(
actionResponse);
String login = ParamUtil.getString(actionRequest, "login");
String password = actionRequest.getParameter("password");
boolean rememberMe = ParamUtil.getBoolean(actionRequest, "rememberMe");
String authType = CompanyConstants.AUTH_TYPE_EA;
String email = "";
if(isValidEmail(login)){ //if the user trying to login with his email
email = login ;
}
else if(isNumeric(login)){ //check if the user trying to login with his student number
//fetch User by Student Number (login)
//e.g. fetchUserByStudentNumber(login)
//get the Email Adress for the retrieved user object and use it to login
email = user.getEmailAddress();
}
else{
// Exception
}
AuthenticatedSessionManagerUtil.login(request, response, email, password, rememberMe, authType);
actionResponse.sendRedirect(themeDisplay.getPathMain());
}
}
is this the right way to achive similar requierment?
in Liferay 7.4 U46+, we can extend supported system services with Liferay Objects. so I have two options to extend the User Profile, 1- by adding a new field to the User object. or 2- by creating a new "custom field". which option is better?
in both options, how to force unique values in the added field (student number)?
how to retrieve user object by using added field (fetchUserByStudentNumber)?
Appreciate your feedback!
Thanks
Overwriting the portal login command is possible, but I would rather use a custom Authenticator to not overwrite other logic implemented in the MVC action component. As you want booth (mail and student number), you could implement authenticateByEmailAddress like in Password-Based-Authentication-Pipelines and check both authentication results with a boolean OR approach.
Extending portal model objects should rather be implemented via Custom Fields. Fetching a user like in fetchUserByStudentNumber you will probably need the ExpandoValue service and a dynamic query. Maybe there are better approached, but this is what comes into my mind first.
Im looking for a clean way to test all features on the webpage with 2 different users.
One user is the admin, the second one a normal user.
Here is the overview of my selenium tests:
As you can see, we have 3 different features on the webpage:
UnlockInstruction
Tac
UploadCodes
Each of these features has its own Test class with its own webDriver so im able to run the tests in parallel.
Each of these test files, is calling the Login Class inside the SetUp.
What the Login Class is doing is:
Open Website with goToUrl
Gets the username and password which is stored in a Password Manager tool
Use selenium to enter username, password and click on login
Wait until page after login is loaded and go back to test methods
Everything works perfectly when i test it for one user. All the test run in parallel.
Now i want to test all the same features with the admin user.
The only way which comes into my mind is, to just create another Login class, which gets the other users credentials and copy also the 3 test classes, so all the 6 tests run in parallel.
But in my opinion its not clean, because i would copy 4 files which would have nearly 1:1 the same code.
I'd make the user id and password arguments to the fixtures.
Use a parameterized TestFixture (one with arguments)
Give the same argument types to the constructor so that NUnit can pass them to you.
Your four files will then result in six instances being constructed.
[TestFixture("normalUser", "normalUserPassword")]
[TestFixture("adminUser", "adminUserPassword")]
public class SomeFixture
{
private string User { get; }
private string Password { get; }
public SomeFixture(string user, string password)
{
User = user;
Password = password;
}
[OneTimeSetUp]
public void SetUpTheFixture()
{
// Create the driver
}
...
}
If you prefer to lookup the password, then just make the user id the only argument and look up the password when you need it.
[TestFixture("normalUser")]
[TestFixture("adminUser")]
public class SomeFixture
{
private string User { get; }
public SomeFixture(string user)
{
User = user;
}
[OneTimeSetUp]
public void SetUpTheFixture()
{
// Look up password
// Create driver
}
...
}
I'm reading: https://github.com/SeleniumHQ/selenium/wiki/PageObjects
In the example I read:
public LoginPage typePassword(String password) {
// This is the only place that "knows" how to enter a password
driver.findElement(passwordLocator).sendKeys(password);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
public LoginPage submitLoginExpectingFailure() {
// This is the only place that submits the login form and expects the destination to be the login page due to login failure.
driver.findElement(loginButtonLocator).submit();
...
// Return a new page object representing the destination. Should the user ever be navigated to the home page after submiting a login with credentials
// expected to fail login, the script will fail when it attempts to instantiate the LoginPage PageObject.
return new LoginPage(driver);
}
Why does the method submitLoginExpectingFailure() return new LoginPage(driver) instead of just returning this?
Both don't navigate to another page object.
I guess it's because When the credentials are incorrect it should again redirect to login page. So as per flow they are again creating LoginPage
There is no need to create a new Object for the Login Page.They are checking
// 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");
}
in the constructor.Instead they could have called an function to do so.It's a way of design not more than that.
I think the reason that we expect that after submitLoginExpectingFailure() was performed we are still on the LoginPage and as we create new LoginPage object this check automatically performs in the class constructor here:
public class LoginPage {
private final WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
// 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");
}
}
}
One thing to consider is that in the code as presented, the constructor conducts a check that the browser is indeed on the login page. Therefore, as also pointed out by the other answers, the version re-creating the page object does a little more.
I typically put such a check in a separate method of my page object. This method then just checks that the browser is in the state as expected by the page object.
I typically invoke this method whenever some page interaction takes place and I want to verify that the browser is on a given page. I tend to not invoke this method in the constructor, as I sometimes find it convenient to be able to create a page object even if the browser is not yet in the corresponding state.
Another thing to consider is that this code base may be a bit incomplete. Typically, you'd have a short notification of the incorrect login. This might correspond to a new state (IncorrectLoginPage), which makes returning the corresponding new page object the natural thing to do for an incorrect login.
To answer your question in general:
Use 'return this' if you're staying on the same page.
Use a new page object if your browser should navigate to a new page (or state).
Consider keeping your constructors simple and factor out state checks into separate methods.
I wrote a bit more about states and page objects, and separate 'self checks' on my blog and corresponding ACM Queue paper beyond page objects.
How do I pass the values between two steps in cucumber JVM?
In the following scenario, I want to access username provided When step in Then step.
How do I pass the values between two steps in cucumber JVM? Currently I'm accessing those by saving that value into a public variable. Is the approach correct (or) any other way I can access those between the steps?
Scenario:
Given user is on login page
When user enters username as user1 and password as pass1
And clicked on login button
Then post login page is displayed
#When("^user enters username as ([^\"]*) and password as ([^\"]*)$")
public void enterLoginDetails(String username,String password){
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
}
In the following step definition, I want to access username from the previous step definition
#Then("^post login page is displayed$")
public void postLoginValidation(){
// i would like access username and verify username is displayed
}
Thanks in Advance
Best Solution :
If you are using scenario outline:
When user enters username as <username> and password as <password>
Then post login page is displayed with <username>
Examples:
| username|password|
|Kiran|1234|
Step Defination Code:
#When("user enters username as (.*) and password as (.*)")
public void enterLoginDetails(String userName,String password)
{
//You will get userName=Kiran and Password=1234
}
#Then("post login page is displayed with (.*)")
public void postLoginValidation(String userName)
{
//You will be access the same username which is you are passing while login
////You will get userName=Kiran
}
public your class {
private String usr;
#When("^user enters username as ([^\"]*) and password as ([^\"]*)$")
public void enterLoginDetails(String username,String password){
usr = username;
...
}
#Then("^post login page is displayed$")
public void postLoginValidation(){
//do something with usr
}
}
You could share state between steps using variables, as suggested by Bala.
Another solution in Java is to use dependency injection. Cucumber-JVM support many different dependency injection frameworks.
One of them is Spring. Dependencies can be made available where they are needed using annotations. Using Spring is a good option if your project is already using Spring. Otherwise it might be too big and cumbersome.
An easy to use alternative to Spring, is to use PicoContainer.
For more information on either, please have a look at:
http://www.thinkcode.se/blog/2017/06/24/sharing-state-between-steps-in-cucumberjvm-using-spring
http://www.thinkcode.se/blog/2017/04/01/sharing-state-between-steps-in-cucumberjvm-using-picocontainer
So let me put it this way.
I have a LogInViewModel and a LogInView. There is a Login() method in the ViewModel that gets called if the user clicks on a button in the View. Now I want the dashboard to show if the login was successful. How do I do this? I can't find a clear answer to this in the documentation.
I assume that your dashboard is essentially your shell. In which case, you can bootstrap your LoginViewModel and in the Login method, after a successful login, you can show the DashboardViewModel and close the LoginViewModel using the Caliburn.Micro WindowManager.
Something like (using MEF):
Bootstrapper.cs
public class Bootstrapper : Caliburn.Micro.Bootstrapper<ILoginViewModel>
{
...
}
LoginViewModel.cs
public class LoginViewModel : Screen, ILoginViewModel
{
private readonly IWindowManager windowManager;
private readonly IDashboardViewModel dashboardViewModel;
[ImportingConstructor]
public LoginViewModel(IWindowManager windowManager, IDashboardViewModel dashboardViewModel)
{
this.windowManager = windowManager;
this.dashboardViewModel = dashboardViewModel;
}
public void Login()
{
// if login success...
this.windowManager.ShowDialog(this.dashboardViewModel);
this.TryClose();
}
}
I've just added a very simple login example SL4 project in my "lab repository" for Caliburn.Micro.
https://github.com/jenspettersson/Caliburn.Micro.Labs/tree/master/src/Login
It uses the Show class that Rob Eisenberg uses in his "Game Library" example to switch between views.
In the Login() method, it tells my Shell (your dashboard?) to show my LoginResultViewModel and sets the login result message.
yield return Show.Child<LoginResultViewModel>().In<IShell>().Configured(c => c.ResultMessage = "Successfully logged in!");
Check the code in my github repo.
I havent used Caliburn.Micro very much lately, so I am by no means an expert, but this way works for me.
//J
Edit: This answers how to navigate between views, if you want to show a "popup" to display if the login was successful, go with the other recomendations.