TestCafe: Page Models for Single Page Applications - testing

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

Related

Pass global Object/Model to custom element in Aurelia

referring to the following post StackOverflow Question I have a quite different scenario where I want to know if Aurelia has a solution for.
Scenario:
I have a user model:
export class User{
#bindable name: string;
#bindable address: Address
As you can see, "Address" is a sub-model.
I have a main view-model "registration". In this view model I have a model "user":
export class RegistrationView{
#bindable user: User
public attached(){
this.user = userService.fetchUserFromApi();
}
In addition to that I have a custom-element "user-address" where I have a "user-address"-model (because I want to have dedicated encapsulated custom-elements).
export class userAddress{
#bindable userAddress: Address
Now I want to request the user model only once from the API and send the user address it to the custom-element:
<template>
<require from="user-address"></require>
<user-address user.address.bind="${dj.address}"></user-address>
Finally I would (to have dedicated encapsulated custom-elements that I can use everywhere) check in attached method if the user is already load and if not then the custom-element would load all needed data:
export class userAddress{
#bindable userId: string
#bindable address: Address
public attached(){
if(!(typeof this.address === "undefined")){
this.address = this.addressAPIService.getAddressByUserId(id)
}
}
Problem 1: I know, that the mentioned template dj.address.bind doesn't work. But now my question is, how can I handle that situation?
Problem 2: How do I assure, that the user object is only requested once?
Does my concept makes sense and does it is the idea of Aurelia?
If I understand your problem correctly, you simply need some form of client-side persistence.
If you need this persistence even after the user closed the browser, you'll want to use either localStorage or some encapsulation thereof. There are many good plugins available such as localForage, LokiJS and a recently developed (still in beta) aurelia plugin aurelia-store
You probably want to encapsulate the retrieval of your user in a UserService of some sort. This is nothing specific to Aurelia, just generally how you want to do this in most types of applications.
Example
So in your viewmodel you might have something like this (skipping some of the implementation details such as checking the params, configuring the router etc for brevity):
#autoinject()
export class UserViewModel {
public user: User;
constructor(private userService: UserService){}
// happens before bind or attached, so your child views will always have the user in time
public async activate(params: any): Promise<void> {
this.user = await this.userService.getUserById(params.id);
}
}
And in your userservice:
// singleton will ensure this service lives as long as the app lives
#singleton()
export class UserService {
// create a simple cache object to store users
private cache: any = Object.create(null);
constructor(private client: HttpClient) {}
public async getUserById(id: number): Promise<User> {
let user = this.cache[id];
if (user === undefined) {
// immediately store the user in cache
user = this.cache[id] = await this.client.fetch(...);
}
return user;
}
}
Let your view model just be dumb and call the UserService whenever it needs to load a user, and let your service be clever and only fetch it from the API when it's not already cached.
I'd also like to point out that attached() is not when you want to be grabbing data. attached() is when you do DOM stuff (add/remove elements, style, other cosmetic things). bind() is best restricted to grabbing/manipulating data you already have on the client.
So when to fetch data?
In your routed view models during the routing lifecycle. That'll be configureRouter, canActivate, activate, canDeactivate and deactivate. These will resolve recursively before any of the DOM gets involved.
Not in your custom elements. Or you'll soon find yourself in maintenance hell with notification mechanisms and extra bindings just so components can let eachother know "it's safe to render now because I have my data".
If your custom elements can assume tehy have their data once bind() occured, everything becomes a lot simpler to manage.
And what about API calls invoked by users?
More often than you think, you can let an action be a route instead of a direct method. You can infinitely nest router-views and they really don't need to be pages, they can be as granular as you like.
It adds a lot of accessibility when little sub-views can be directly accessed via specific routes. It gives you extra hooks to deal with authorization, warnings for unsaved changes and the sorts, it gives the user back/forward navigation, etc.
For all other cases:
Call a service from an event-triggered method like you normally would during activate(), except whereas normally the router defers page loading until the data is there, now you have to do it yourself for that element.
The easiest way is by using if.bind="someEntityThatCanBeUndefined". The element will only render when that object has a value. And it doesnt need to deal with the infrastructure of fetching data.

pass data to another route without messing with url

DISCLAIMER: I'm a noob.. sorry
Say I have 2 different components that are siblings:
comp1 and comp2
I wish to route from comp1 to comp2 with a bunch of data. How can I achieve this without getting a fugly url-bar containing everything?
I've tried using a separate class, lets call it DataTransmitter:
data-transmitter.js:
export class DataTransmitter {
constructor() {
this.val= "a";
}
}
comp1.js:
import { DataTransmitter } from './data-transmitter';
#inject(DataTransmitter)
export class comp1{
constructor(DataTransmitter){
this.DataTransmitter = DataTransmitter;
}
someMethod(){
this.DataTransmitter.val = "b";
console.log('comp1: ' + this.DataTransmitter.val);
}
}
comp2.js:
import { DataTransmitter } from './data-transmitter';
#inject(DataTransmitter)
export class comp2{
constructor(DataTransmitter){
this.DataTransmitter = DataTransmitter;
}
someMethod(){
console.log('comp2: ' + this.DataTransmitter.val);
}
}
This gives me the output:
comp1: b
comp2: a
I've also tried messing around with EventAggregator, but no success.
Is there some way of routing with parameters WITHOUT having a url that looks like site/comp2?data=stuff&things=otherstuff&params=values&more=etc?
You absolutely want to use a singleton class and then inject it inside of whatever components you need your data. The link that Gaby posted is definitely what you want to do.
The reason your posted code does not work is because you're attempting to use the inject decorator, but you're not importing it. Please see this working example of what you are trying to do on Gist.run here. I have two components, you can click to route between them and set the value. You'll notice the set value remains when you navigate back and forth.

If method returns data then load in module(view) - Aurelia

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
}
});
}

Selenium Page Object pattern error pages handling

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.

Encapsulating common logic (domain driven design, best practices)

Updated: 09/02/2009 - Revised question, provided better examples, added bounty.
Hi,
I'm building a PHP application using the data mapper pattern between the database and the entities (domain objects). My question is:
What is the best way to encapsulate a commonly performed task?
For example, one common task is retrieving one or more site entities from the site mapper, and their associated (home) page entities from the page mapper. At present, I would do that like this:
$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);
$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));
Now that's a fairly trivial example, but it gets more complicated in reality, as each site also has an associated locale, and the page actually has multiple revisions (although for the purposes of this task I'd only be interested in the most recent one).
I'm going to need to do this (get the site and associated home page, locale etc.) in multiple places within my application, and I cant think of the best way/place to encapsulate this task, so that I don't have to repeat it all over the place. Ideally I'd like to end up with something like this:
$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();
Where the resulting site entities already have their associated entities created and ready for use.
The same problem occurs when saving these objects back. Say I have a site entity and associated home page entity, and they've both been modified, I have to do something like this:
$siteMapper->save($site);
$pageMapper->save($site->getHomePage());
Again, trivial, but this example is simplified. Duplication of code still applies.
In my mind it makes sense to have some sort of central object that could take care of:
Retrieving a site (or sites) and all nessessary associated entities
Creating new site entities with new associated entities
Taking a site (or sites) and saving it and all associated entities (if they've changed)
So back to my question, what should this object be?
The existing mapper object?
Something based on the repository pattern?*
Something based on the unit of work patten?*
Something else?
* I don't fully understand either of these, as you can probably guess.
Is there a standard way to approach this problem, and could someone provide a short description of how they'd implement it? I'm not looking for anyone to provide a fully working implementation, just the theory.
Thanks,
Jack
Using the repository/service pattern, your Repository classes would provide a simple CRUD interface for each of your entities, then the Service classes would be an additional layer that performs additional logic like attaching entity dependencies. The rest of your app then only utilizes the Services. Your example might look like this:
$site = $siteService->getSiteById(1); // or
$sites = $siteService->getAllSites();
Then inside the SiteService class you would have something like this:
function getSiteById($id) {
$site = $siteRepository->getSiteById($id);
foreach ($pageRepository->getPagesBySiteId($site->id) as $page)
{
$site->pages[] = $page;
}
return $site;
}
I don't know PHP that well so please excuse if there is something wrong syntactically.
[Edit: this entry attempts to address the fact that it is oftentimes easier to write custom code to directly deal with a situation than it is to try to fit the problem into a pattern.]
Patterns are nice in concept, but they don't always "map". After years of high end PHP development, we have settled on a very direct way of handling such matters. Consider this:
File: Site.php
class Site
{
public static function Select($ID)
{
//Ensure current user has access to ID
//Lookup and return data
}
public static function Insert($aData)
{
//Validate $aData
//In the event of errors, raise a ValidationError($ErrorList)
//Do whatever it is you are doing
//Return new ID
}
public static function Update($ID, $aData)
{
//Validate $aData
//In the event of errors, raise a ValidationError($ErrorList)
//Update necessary fields
}
Then, in order to call it (from anywhere), just run:
$aData = Site::Select(123);
Site::Update(123, array('FirstName' => 'New First Name'));
$ID = Site::Insert(array(...))
One thing to keep in mind about OO programming and PHP... PHP does not keep "state" between requests, so creating an object instance just to have it immediately destroyed does not often make sense.
I'd probably start by extracting the common task to a helper method somewhere, then waiting to see what the design calls for. It feels like it's too early to tell.
What would you name this method ? The name usually hints at where the method belongs.
class Page {
public $id, $title, $url;
public function __construct($id=false) {
$this->id = $id;
}
public function save() {
// ...
}
}
class Site {
public $id = '';
public $pages = array();
function __construct($id) {
$this->id = $id;
foreach ($this->getPages() as $page_id) {
$this->pages[] = new Page($page_id);
}
}
private function getPages() {
// ...
}
public function addPage($url) {
$page = ($this->pages[] = new Page());
$page->url = $url;
return $page;
}
public function save() {
foreach ($this->pages as $page) {
$page->save();
}
// ..
}
}
$site = new Site($id);
$page = $site->addPage('/');
$page->title = 'Home';
$site->save();
Make your Site object an Aggregate Root to encapsulate the complex association and ensure consistency.
Then create a SiteRepository that has the responsibility of retrieving the Site aggregate and populating its children (including all Pages).
You will not need a separate PageRepository (assuming that you don't make Page a separate Aggregate Root), and your SiteRepository should have the responsibility of retrieving the Page objects as well (in your case by using your existing Mappers).
So:
$siteRepository = new SiteRepository($myDbConfig);
$site = $siteRepository->findById(1); // will have Page children attached
And then the findById method would be responsible for also finding all Page children of the Site. This will have a similar structure to the answer CodeMonkey1 gave, however I believe you will benefit more by using the Aggregate and Repository patterns, rather than creating a specific Service for this task. Any other retrieval/querying/updating of the Site aggregate, including any of its child objects, would be done through the same SiteRepository.
Edit: Here's a short DDD Guide to help you with the terminology, although I'd really recommend reading Evans if you want the whole picture.