I've got a generic question concerning error pages.
Imagine a simple use case, good (1) and bad (2) authentication.
In case (1), we've got the index page.
In case (2), we've got a specific error page.
The point is, I've got a page object LoginPage, and the submitLoginForm should return the next page. I click on it with a bad login form filled in.
Then, we've got 2 options for handling it:
- should we create a LoginErrorPage and give LoginPage a submitNonValidLoginForm returning this LoginErrorPage ?
- should we useLoginPage with submitLoginForm returning the 'right' navigation page IndexPage, and in the Junit test, assert on the driver real state (hasn't got IndexPage elements but some others).
I hope I'm clear !
Thank you
From my personal experience I can say it tends to be better to have different Page Objects for (conceptually) different pages, even when we're talking about the same URL with different content.
So I suggest following your first option, creating a LoginError Page Object. Another thing is that the page validation should be done in your Page Object, not as a test because your creating a dependency between the test and Selenium directly.
I.E (in a very pseudocodish way)
class BasePage {
constructor (driver, context, isLoaded = false) {
this->webDriver = driver
//clicking links or submitting forms from other page objects
//will trigger the page load at driver level so we don't want to trigger a page reload
if (isLoaded) {
this->loadPage()
}
this->validatePage()
}
loadPage() {
this->webDriver->get(this->getPageUrl)
}
abstract validatePage()
abstract getPageUrl()
}
class LoginPage extends BasePage{
validatePage() {
this->elementUsername = this->webDriver->findElement(WebDriverBy::id('username'))
this->elementPassword = this->webDriver->findElement(WebDriverBy::id('password'))
this->elementSubmit = this->webDriver->findElement(WebDriverBy::id('submit'))
}
getPageUrl() {
return '/login/'
}
fillUser(value) {
this->elementUsername->sendKeys(value)
}
fillPassword(value) {
this->elementPassword->sendKeys(value)
}
submitValid() {
this->elementSubmit->submit()
return new DashboardPage(this->webDriver, this->context, true)
}
submitInvalid() {
this->elementSubmit->submit()
return new LoginErrorPage(this->webDriver, this->context, true)
}
}
class DashboardPage extends BasePage {
validatePage() {
this->webDriver->findElement(WebDriverBy::id('welcomeMessage'))
}
getPageUrl() {
return '/dashboard/'
}
}
At this point your tests will only have to sort out the webdriver fixture but don't have to know anything about your pages
testValidCredentials:
login = new LoginPage(..)
login->fillUser('john')
login->fillPassword('aa')
dashboard = login->submitValid()
testInvalidCredentials:
login = new LoginPage(..)
login->fillUser('john')
login->fillPassword('aa')
loginError = login->submitInvalid()
testWelcomeMessage:
dashboard = new DashboardPage(..)
// a bad (but short enough) example, don't actually do this
assert(true, regexp('welcome', dashboard->getSource))
L.E.
From a testing perspective you have to know your expected result. Another approach would be to have a single submit that accepts expected page object as param
testInvalidCredentials:
login = new LoginPage(..)
login->fillUser('john')
login->fillPassword('aa')
loginError = login->submit('LoginErrorPage')
assertContains('invalid login', loginError->getErrorMessages())
But after writing 100 tests you'll find this to be too verbose and, if the page received after a successful submit changes, you'll have a lot of rewriting to do.
Related
I have been struggling to figure out the best way to represent a single page application within TestCafe, and was wondering if anyone out there could help me?
Currently I am structuring it like the following (fake page names of course). I have greatly simplified it here for the sake of discussion, but the problem you should start to see is that as the app grows larger, the main page starts importing more and more. And each of those imports have imports, which might have more imports. So the cascading affect is causing TestCafe to drastically slow down when launching tests.
Does it make more sense to force the tests themselves to import all of the 'sections' they work with? What about for longer workflow tests that hit a bunch of sections? Does it still make sense then?
Any advice would be greatly appreciated!
import {Selector, t} from 'testcafe';
import {
ConsumerSection,
ManufacturerSection,
SupplierSection,
<AndSoOn>
} from './CarPageSections';
export class CarPage extends BasePage {
// BasePage contains all of the Header, Footer, NavBar, SideBar, Action Flyouts
CarSelectionTimer: Selector;
ModelSelectionModal: ModelSelectionModal;
SomeOtherModal: SomeOtherModal;
// Section Selectors
sectionPanels = {
ConsumerSection: null as ConsumerSection,
ManufacturerSection: null as ManufacturerSection,
SupplierSection: null as SupplierSection,
<AndSoOn>: null as <AndSoOn>
};
sections = {
ConsumerSection: null as SectionControl,
ManufacturerSection: null as SectionControl,
SupplierSection: null as SectionControl,
<AndSoOn>: null as SectionControl
};
constructor() {
this.CarSelectionTimer = Selector('#car-selection-timer');
// Sections
this.sections = {
ConsumerSection: new SectionControl('Consumer'),
ManufacturerSection: new SectionControl('Manufacturer'),
SupplierSection: new SectionControl('Supplier'),
<AndSoOn>: new SectionControl('<AndSoOn>')
};
this.sectionPanels = {
ConsumerSection: new ConsumerSection(this.sections.ConsumerSection.control),
ManufacturerSection: new ManufacturerSection(this.sections.ManufacturerSection.control),
SupplierSection: new SupplierSection(this.sections.SupplierSection.control),
<AndSoOn>: new <AndSoOn>(this.sections.<AndSoOn>.control)
};
this.ModelSelectionModal = new ModelSelectionModal();
this.SomeOtherModal = new SomeOtherModal();
}
async SomeActionToPerformOnThePage(params) {
// DO STUFF
}
async SomeOtherActionToPerformOnThePage(params) {
// DO STUFF
}
}
Considerations to handle:
Constructors with parameters like ConsumerSection(control) above.
Using files to export multiple objects / classes to simplify importing in tests (or other models).
Questions to consider:
Should every model be decoupled from every other model?
Without coupling models, how do you make it as easy as possible to work with? In other test frameworks, you can hand back a new page type upon a given method/action: i.e. LoginPage.Submit() returns HomePage().
It's difficult to determine the cause of the issue without your full page model. Your issue looks similar to this one: https://github.com/DevExpress/testcafe/issues/4054. Please check that Github thread and apply the recommendations from it.
If this does not help, please share your full page model. If you cannot share it here, you can send it at support#devexpress.com
We have rich UI with lot of web elements & multi levels of elements on UI segregated into sections. As part of evaluation for a new test automation framework, I am looking to see if we can use TESTCAFE.
Currently we are using Nightwatch (Java Script) framework which support Sections in Page Objects. Now we are moving to TestCafe (Java Script) framework. Could anyone give me an example on how we can maintain SECTIONS in PageObjects using TestCafe ?? If TestCafe doesn't support, how do we achieve same in TestCafe.
NighWatch Page Object with Sections Example:
Multiple level of sections in page_objects in nightwatch.js
In TestCafe page model is just a JavaScript class (https://devexpress.github.io/testcafe/documentation/guides/concepts/page-model.html#page-model-example), so you can create several nested classes that will reflect your page structure.
For example, if there are a title, toolbar and list objects on the page, you can use the following code for your page model:
class ListModel {
getItem(i) {
//find an item
}
constructor (selector) {
this.selector = selector;
}
}
class ToolbarModel {
action(){
//perform an action
}
constructor (selector) {
this.selector = selector;
}
}
class PageModel {
constructor (selector) {
this.selector = selector;
this.title = selector.find('#tile');
this.toolBar = new ToolbarModel(selector.find('#toolbar'));
this.list = new ListModel(selector.find('#list'));
}
}
Forgive me for my ignorance but I've just started out with Aurelia/ES6 and a lot baffles me at the moment. I'm completely new to client side frameworks, so hopefully what I'm trying to achieve is possible within the framework.
So as the title indicates I'm fetching data within a class:
import {inject} from "aurelia-framework";
import {HttpClient} from "aurelia-http-client";
let baseUrl = "/FormDesigner/";
#inject(HttpClient)
export class FormData{
constructor(httpClient)
{
this.http = httpClient;
}
GetFormById(formId)
{
return this.http.get(`${baseUrl}/GetFormById/${formId}`)
.then(resp => resp.content);
};
}
Now I can see/receive the data which is great but after digging into the docs I cannot seem to figure out:
Load a separate related module/view by Id into the main view (app.html)
If no data, error and no Id passed then redirect to no-form view
Scenario:
User A navigates to "FormDesigner/#/form/3E7689F1-64F8-A5DA0099D992" at that point "A" lands on the form page, now if successful and data has been returned pass the formId into a different method elsewhere and then load in a module/view - Pages, possibly using <compose></compose>
This is probably really simple but the documentation (in my opinion) seems rather limited to someone that's new.
Really appreciate any guidance/high level concepts, as always, always much appreciated!
Regards,
Sounds like you might want to just partake in the routing lifecycle
If you are navigating to a module you can create an activate method on the view model which will be called when routing starts.
In this method you can return a promise (while you fetch data) and redirect if the fetch fails
In fact if the promise is rejected, the routing will be cancelled
If successful you can use whatever method you need to load in your module (assuming it can't just be part of the module that is being loaded since routing won't be cancelled)
Something like
activate(args, config) {
this.http.get(URL).then(resp => {
if (resp.isOk) {
// Do stuff
} else {
// Redirect
}
});
}
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.
I want to create a maintenance Page for my cake website by checking a Database Table for a maintenance flag using a sub-function of my AppController "initilize()" method. If the flag is set, i throw my custom MaintenanceException(Currently containing nothing special):
class MaintenanceException extends Exception{
}
To handle it, I implemented a custom App Exception Renderer:
class AppExceptionRenderer extends ExceptionRenderer {
public function maintenance($error)
{
return "MAINTENANCE";
}
}
I am able to see this maintenance Text on my website if I set my DB flag to true, but I could not find any information in cake's error handling documentation (http://book.cakephp.org/3.0/en/development/errors.html) on how I can actually tell the Exception renderer to render view "maintenance" with Template "infopage".
Can I even us that function using the ExceptionRenderer without a custom error controller? And If not, how should a proper ErrorController implementation look like? I already tried this:
class AppExceptionRenderer extends ExceptionRenderer {
protected function _getController(){
return new ErrorController();
}
public function maintenance($error)
{
return $this->_getController()->maintenanceAction();
}
}
together with:
class ErrorController extends Controller {
public function __construct($request = null, $response = null) {
parent::__construct($request, $response);
if (count(Router::extensions()) &&
!isset($this->RequestHandler)
) {
$this->loadComponent('RequestHandler');
}
$eventManager = $this->eventManager();
if (isset($this->Auth)) {
$eventManager->detach($this->Auth);
}
if (isset($this->Security)) {
$eventManager->detach($this->Security);
}
$this->viewPath = 'Error';
}
public function maintenanceAction(){
return $this->render('maintenance','infopage');
}
}
But this only throws NullPointerExceptions and a fatal error. I am really dissapointed by the cake manual as well, because the code examples there are nowhere close to give me an impression of how anything could be done and what functionality I actually have.
Because I had some more time today, I spent an hour digging into the cake Source and found a solution that works well for me (and is propably the way it should be done, altough the cake documentation does not really give a hint):
Step 1: Override the _template(...)-Method of the ExceptionRenderer in your own class. In my case, I copied the Method of the parent and added the following Code at the beginning of the method:
$isMaintenanceException = $exception instanceof MaintenanceException;
if($isMaintenanceException){
$template = 'maintenance';
return $this->template = $template;
}
This tells our Renderer, that the error Template called "maintentance"(which should be located in Folder: /Error) is the Error Page content it should render.
Step 2: The only thing we have to do now (And its is kinda hacky in my opinion, but proposed by the cake documentation in this exact way) is to set the layout param in our template to the name of the base layout we want to render with. So just add the following code on top of your error template:
$this->layout = "infopage";
The error controller I created is actually not even needed with this approach, and I still don't know how the cake error controller actually works. maybe I will dig into this if I have more time, but for the moment.