Alternatives to conditional statements while writing Cypress custom commands? test-of-tests - testing

This is more of an open ended question. Apologies for asking, however, I cant seem to find a good explanation in online searches, and this community has been one of the important learning resources.
I am writing end to end tests for a complex web application. In a nutshell, it involves...
Creating an application by giving specific inputs
Filling out a series of forms, which differ according to the input given during application creation.
Since the main concept of Cypress is to keep the spec file clean and add all the code in a custom command, and call the custom command in the spec file, I have been writing a lot of if-else conditional statements. For example...
//spec
describe("Test scenario 1", () => {
it("test case 1", function () {
cy.CustomCommand1(arg1,arg2) //arguments are fetched from a fixture file, which acts like input data. Fixture file changes for various test to cover multiple scenario.
})
})
//CustomCommand1
Cypress.Commands.Add('CustomCommand1',(arg1,arg2) => {
if(arg1==xyz || arg2==abc){
//fill some fields
//assert on things in the form and so on...
}
else{
//do something else
//assert on something else and so on...
}
})
This is how I have been using a single command for a single page on my web app, and adding logic for the appearance of various form fields and check boxes etc, filling them and asserting.
I have multiple pages and each displaying different set of fields on the basis of the input data while creation of application. Every page has its own custom command and there is hell of a logic built in there. A real custom command from my project looks like this...
Cypress.Commands.add('addEmployers', (employer) => {
cy.location('pathname').should('eq', '/welcome/employers')
cy.get('[data-testid="add-employer"]').click()
cy.get('#company_name').type(employer.company)
cy.get('#position').type(employer.position)
cy.get('#current').click()
cy.get(`[data-testid="${employer.type}"]`).click()
cy.get('#start_date').parent().type(`${employer.sdate}{enter}`)
if (employer.type == 'not_current') {
cy.get('#end_date').parent().type(`${employer.edate}{enter}`)
}
cy.get('[data-testid="street_address"]').type(employer.addr.street)
cy.selectCountry('#country',employer.addr.country)
if ((['Canada','United States'].includes(employer.addr.country))) {
cy.dropdownWithText('#province_state',employer.addr.province)
}
else {
cy.selectOTProvince('#other_province_state',employer.addr)
}
cy.get('#city').type(employer.addr.city)
cy.get('#postal_code').type(employer.addr.postal)
if(employer.type == 'yes_current' && employer.empcontact == 'Y') {
cy.get('#can_contact').click()
cy.get('[data-testid="can_contact_employer"]').click()
}
if(employer.type == 'yes_current' && employer.empcontact == 'N') {
cy.get('#can_contact').click()
cy.get('[data-testid="cannot_contact_employer"]').click()
}
if(employer.type == 'not_current' || (employer.type == 'yes_current' && employer.empcontact == 'Y')){
cy.ClickNext()
cy.get('#contact_first_name').type(employer.emp.fname)
cy.get('#contact_last_name').type(employer.emp.lname)
cy.EmailGen('employer').then(email => {
cy.wrap(email).as('employerEmail')
})
cy.get('#employerEmail').then(email =>{
cy.get('#contact_email').type(`${email}`)
cy.wrap(email).as('empemail')
})
})
}
})
And then I get to know that if we have conditional statements in a test, they would require tests too. (Test of Tests :shrug:)
Is there something fundamentally wrong in my approach. If yes, what changes should I bring to my approach to keep spec files clean and do bulk of the jobs in custom command?
Do let me know if any more details are required!

Related

TestCafe : How can I make TestCafe to run some application code only once before all the fixtures

In my application, I want to set up some test data from the UI before running any Fixture. I want to do this set up only once and don't want to do this before each fixture.
Can someone please help me on how to do this ?
I tried to use approach mentioned on below thread but I cannot use test controller - t inside before.
https://testcafe-discuss.devexpress.com/t/run-the-same-before-and-after-hook-for-all-fixtures-and-configure-a-baseurl/551
I have an idea you can check if you feel it works for you as below, you will still use beforeEach in this case as you wish to access to t:
let didSetup = false;
fixture`yourFixture`
.beforeEach(async t => {
if (!didSetup) {
// You set up things here
await yourSetup();
didSetup = true;
}
// Otherwise won't do anything
})

Why does Parse LiveQuery not always trigger update/create/enter event?

I am trying to achieve a board that enables real-time editing for cooperating users. I am running a parse server using Sashido, where I have LiveQueries enabled for among other things 'Sticky'. For the frontend I have the following code:
const query = new Parse.Query(Sticky);
... some query constraints
this.subscription = await query.subscribe();
this.subscription.on('open', () => {
console.log('SUBSCRIPTION: opened');
}
this.subscription.on('create', (sticky) => {
console.log('SUBSCRIPTION: created');
}
this.subscription.on('update', (sticky) => {
console.log('SUBSCRIPTION: updated');
}
this.subscription.on('enter', (sticky) => {
console.log('SUBSCRIPTION: entered');
}
this.stickies = await query.find();
When I open my application in two different browser tabs, I get the 'SUBSCRIPTION: opened'. When I edit or create Sticky instances, I expect to get the corresponding events and see changes in the Sashido database.
However, I always see the changes in the database, but half of the times when I create or edit Sticky instances, I do not get the update/create/enter events. Note: Sometimes they do get triggered, but I have not found a sequence of events that leads to them being triggered or not, it seems to happen at random.
Can someone see what I'm doing wrong?

Testing drag and drop with behat and mink

I'm trying to emulate drag and drop UI behaviour in a behat test. So far with no success, despite mink allegedly supporting that interaction.
Weirdly enough it was hard for me to find any relevant blog posts about the subjects. Ones I've found (this and http://www.pix-art.be/post/testing-drag-and-drop-with-behat-and-guzzle
) did not help me much. Especially the latter one.
Does anyone have any suggestions on how to approach the problem or has experience with actually testing that interaction?
you can find an working example in the ownCloud test code, it does move files into folders via drag-and-drop:
public function moveFileTo(
$name, $destination, Session $session, $maxRetries = STANDARD_RETRY_COUNT
) {
$toMoveFileRow = $this->findFileRowByName($name, $session);
$destinationFileRow = $this->findFileRowByName($destination, $session);
$this->initAjaxCounters($session);
$this->resetSumStartedAjaxRequests($session);
for ($retryCounter = 0; $retryCounter < $maxRetries; $retryCounter++) {
$toMoveFileRow->findFileLink()->dragTo(
$destinationFileRow->findFileLink()
);
$this->waitForAjaxCallsToStartAndFinish($session);
$countXHRRequests = $this->getSumStartedAjaxRequests($session);
if ($countXHRRequests === 0) {
\error_log("Error while moving file");
} else {
break;
}
}
if ($retryCounter > 0) {
$message
= "INFORMATION: retried to move file $retryCounter times";
echo $message;
\error_log($message);
}
}
from: https://github.com/owncloud/core/blob/47396de109965110276deb545a9bd09f375c9823/tests/acceptance/features/lib/FilesPageCRUD.php#L243
First it finds the NodeElement of the file that has to be moved, then then NodeElement of the destination and calls $fileToBeMovedElement->dragTo($destinationElement)
Because it proved to be flaky there is an retry loop around the dragTo function. To test if the drag-and-drop operation worked the code checks if any AJAX calls were set off or not (in this particular app this drag-and-drop operation sets off an WebDAV request)

MVC Grid Delete Record Action Performing Full Page Load, How to stop this?

I have an mvc grid.
Next to each record is a delete button.
This delete button corresponds to an action DeleteRecord. The action deletes this record and performs a RedirectToAction("MyGrid");.
This works pretty well but what is annoying is the fact that my redirectaction causes the entire page to reload. This is something I am trying to stay away from, but can't seem to figure out a way around this.
I start out like this.
/MyApp/MyGrid => click delete
/MyApp/DeleteRecord => redirect
/MyApp/MyGrid => full reload of page
Maybe this isn't possible but I tried doing this by using overriden actions and actions with different action names, but this didn't solve my full reload. I am new to MVC so maybe this isn't even possible. I was thinking maybe if I just did an ajax.post on the clientside that I could get away from this but the more I think about it the more likely that it just will end up in the same action performing the same redirect.
Any ideas on how to get around this situation?
I actually ended up just tying into the callback function of the grid and the inline delete of the grid function and it worked. Although, extremely hackish, it stops the full post back by hoping into the inline grid delete code. Although, you can't count on the callback javascript routines to run in order as that is just how javascript works right. ; o
s.SettingsEditing.DeleteRowRouteValues = new { Controller = "Home", Action = "DeleteRecordGrid" };
s.ClientSideEvents.EndCallback = "function(s,e) { OnEndCallback(s, e, 'delete') }";
<script type="text/javascript">
function deleteWithoutPostback(eVisibleIndex) {
var okToDelete = confirm("Do you really want to delete this record?");
if (okToDelete == true) {
myGrid.DeleteRow(eVisibleIndex);
myGrid.ClearFilter();
} else {
return;
}
}
function OnEndCallback(s, e, callType) {
if (typeof callType == 'undefined') {
if (callType == 'delete') {
myGrid.Refresh();
}
}
}
</script>

Yii renderpartial (proccessoutput = true) Avoid Duplicate js request

Im creating a site who works with ajaxRequest, when I click a link, it will load using ajaxRequest. When I load for example user/login UserController actionLogin, I renderPartial the view with processOUtput to true so the js needed inside that view will be generated, however if I have clientScriptRegister inside that view with events, how can I avoid to generate the scriptRegistered twice or multiple depending on the ajaxRequest? I have tried Yii::app()->clientScript->isSCriptRegistered('scriptId') to check if the script is already registered but it seems that if you used ajaxRequest, the result is always false because it will only be true after the render is finished.
Controller code
if (Yii::app()->request->isAjaxRequest)
{
$this->renderPartial('view',array('model'=>$model),false,true);
}
View Code
if (!Yii::app()->clientScript->isScriptregistered("view-script"))
Yii::app()->clientScript->registerScript("view-script","
$('.link').live('click',function(){
alert('test');
})
");
If I request for the controller for the first time, it works perfectly (alert 1 time) but if I request again for that same controller without refreshing my page and just using ajaxRequest, the alert will output twice if you click it (because it keeps on generating eventhough you already registered it once)
This is the same if you have CActiveForm inside the view with jquery functionality.. the corescript yiiactiveform will be called everytime you renderPartial.
To avoid including core scripts twice
If your scripts have already been included through an earlier request, use this to avoid including them again:
// For jQuery core, Yii switches between the human-readable and minified
// versions based on DEBUG status; so make sure to catch both of them
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery.min.js'] = false;
If you have views that are being rendered both independently and as HTML fragments to be included with AJAX, you can wrap this inside if (Yii::app()->request->isAjaxRequest) to cover all bases.
To avoid including jQuery scripts twice (JS solution)
There's also the possibility of preventing scripts from being included twice on the client side. This is not directly supported and slightly more cumbersome, but in practice it works fine and it does not require you to know on the server side what's going on at the client side (i.e. which scripts have been already included).
The idea is to get the HTML from the server and simply strip out the <script> tags with regular expression replace. The important point is you can detect if jQuery core scripts and plugins have already been loaded (because they create $ or properties on it) and do this conditionally:
function stripExistingScripts(html) {
var map = {
"jquery.js": "$",
"jquery.min.js": "$",
"jquery-ui.min.js": "$.ui",
"jquery.yiiactiveform.js": "$.fn.yiiactiveform",
"jquery.yiigridview.js": "$.fn.yiiGridView",
"jquery.ba-bbq.js": "$.bbq"
};
for (var scriptName in map) {
var target = map[scriptName];
if (isDefined(target)) {
var regexp = new RegExp('<script.*src=".*' +
scriptName.replace('.', '\\.') +
'".*</script>', 'i');
html = html.replace(regexp, '');
}
}
return html;
}
There's a map of filenames and objects that will have been defined if the corresponding script has already been included; pass your incoming HTML through this function and it will check for and remove <script> tags that correspond to previously loaded scripts.
The helper function isDefined is this:
function isDefined(path) {
var target = window;
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return false;
}
target = target[branch];
}
return true;
}
To avoid attaching event handlers twice
You can simply use a Javascript object to remember if you have already attached the handler; if yes, do not attach it again. For example (view code):
Yii::app()->clientScript->registerScript("view-script","
window.myCustomState = window.myCustomState || {}; // initialize if not exists
if (!window.myCustomState.liveClickHandlerAttached) {
window.myCustomState.liveClickHandlerAttached = true;
$('.link').live('click',function(){
alert('test');
})
}
");
The cleanest way is to override beforeAction(), to avoid any duplicated core script:
class Controller extends CController {
protected function beforeAction($action) {
if( Yii::app()->request->isAjaxRequest ) {
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery-2.0.0.js'] = false;
Yii::app()->clientScript->scriptMap['anything.js'] = false;
}
return parent::beforeAction($action);
}
}
Note that you have to put the exact js file name, without the path.
To avoid including script files twice, try this extension: http://www.yiiframework.com/extension/nlsclientscript/
To avoid attaching event handlers twice, see Jons answer: https://stackoverflow.com/a/10188538/729324