Testing drag and drop with behat and mink - behat

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)

Related

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

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!

JS not loading php into div, website specific behaviour

I have two joomla applications se up with exactly the same versions, the same global configuration settings, and I set up a test application with the following function to load a php into a div on a specific page..
function getDiv(str) {
var id = document.getElementById("appselector").value;
if (id == "") {
document.getElementById("scoffitcategoryedit").innerHTML = "";
return;
}
else {
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp3 = new XMLHttpRequest();
} else {
// code for IE6, IE5
xmlhttp3 = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp3.onreadystatechange = function() {
if (xmlhttp3.readyState == 4 && xmlhttp3.status == 200) {
document.getElementById("scoffitcategoryedit").innerHTML = xmlhttp3.responseText;
alert("WTF");
}
};
xmlhttp3.open("GET","index.php?option=com_jumi&fileid=23& format=raw&" + str + id, true);
xmlhttp3.send();
}
}
I know its a little long winded, but the problem is that the script works fine in one website, but not in the other. Both of them are loading the same versions of jquery (which as you can see I don't like using), and both have the same templates. The only clue I seem to have found is that when I have text links elsewhere on the site attached with query statements pointing to self (index.php?blastr=bla&drivelstr=drivel), the browser shows index.php/ prepended to the link mentioned above.
I debugged the script and in the non functioning website it hangs on the xmlhttp3.send line.
I know people espouse the beauty of jquery's load() function as a replacement for this, but I cant get it to work (probably because the templates are using jquery versions higher than 1.8.1 when it was deprecated). So i'd rather stick with base js.
Any ideas about this inconsistent behaviour ?
This should be a comment but can't yet :(
I would check your configuration file for this line:
public $live_site = '';
and see if there is something in the failing site besides ''.
I found out what it was, I must have actually installed two different versions of the Jumi application in my websites. one website had a file under components/com_jumi/views/application named view.raw.php while the other did not. This meant that the format=raw in the index.php string could not be interpreted and caused a null response.
I simply copied the file into the other website where it was missing and everything then worked fine. But thanks for the response.

How do you poll for a condition in Intern / Leadfoot (not browser / client side)?

I'm trying to verify that an account was created successfully, but after clicking the submit button, I need to wait until the next page has loaded and verify that the user ended up at the correct URL.
I'm using pollUntil to check the URL client side, but that results in Detected a page unload event; script execution does not work across page loads. in Safari at least. I can add a sleep, but I was wondering if there is a better way.
Questions:
How can you poll on something like this.remote.getCurrentUrl()? Basically I want to do something like this.remote.waitForCurrentUrlToEqual(...), but I'm also curious how to poll on anything from Selenium commands vs using pollUntil which executes code in the remote browser.
I'm checking to see if the user ended up at a protected URL after logging in here. Is there a better way to check this besides polling?
Best practices: do I need to make an assertion with Chai or is it even possible when I'm polling and waiting for stuff as my test? For example, in this case, I'm just trying to poll to make sure we ended up at the right URL within 30 seconds and I don't have an explicit assertion. I'm just assuming the test will fail, but it won't say why. If the best practice is to make an assertion here, how would I do it here or any time I'm using wait?
Here's an example of my code:
'create new account': function() {
return this.remote
// Hidden: populate all account details
.findByClassName('nextButton')
.click()
.end()
.then(pollUntil('return location.pathname === "/protected-page" ? true : null', [], 30000));
}
The pollUntil helper works by running an asynchronous script in the browser to check a condition, so it's not going to work across page loads (because the script disappears when a page loads). One way to poll the current remote URL would be to write a poller that would run as part of your functional test, something like (untested):
function pollUrl(remote, targetUrl, timeout) {
return function () {
var dfd = new Deferred();
var endTime = Number(new Date()) + timeout;
(function poll() {
remote.getCurrentUrl().then(function (url) {
if (url === targetUrl) {
dfd.resolve();
}
else if (Number(new Date()) < endTime) {
setTimeout(poll, 500);
}
else {
var error = new Error('timed out; final url is ' + url);
dfd.reject(error);
}
});
})();
return dfd.promise;
}
}
You could call it as:
.then(pollUrl(this.remote, '/protected-page', 30000))
When you're using something like pollUntil, there's no need (or place) to make an assertion. However, with your own polling function you could have it reject its promise with an informative error.

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

Why do I need to turn off security validation?

I'm working on building a webpart that creates a site, adds some lists based on user input, and sets the theme for the site. I can do this whole operation from a console app running on the server just fine, but when I do this from the webpart I get a secrutiy validation error when I try to set the theme. I can get around this by turning off security validation for the entire web app through central admin, but I'd rather not go down that route. This is currently what I'm running -
SPSecurity.RunWithElevatedPrivileges(delegate()
{
newWeb = web.Webs.Add(siteName, siteName, description, 1033, "STS#1", true, false);
newWeb.AllowUnsafeUpdates = true;
ReadOnlyCollection<ThmxTheme> managedThemes = null;
managedThemes = ThmxTheme.GetManagedThemes(newWeb.Site);
foreach (ThmxTheme theme2 in managedThemes)
{
if (theme2.Name == "oked")
{
theme2.ApplyTo(newWeb, true);
break;
}
}
});
I've tried several different flavors of this, but all with the same result. Thanks!
This can happen if you are doing update operation on GET request.
Did you check out this
http://blogs.technet.com/b/speschka/archive/2011/09/14/a-new-twist-on-an-old-friend-quot-the-security-validation-for-this-page-is-invalid-quot-in-sharepoint-2010.aspx