Selenium drag and drop not working in angular js - angularjs

i have a test to automate drag and drop item in angular Js application page. When i click on source element and drag it my destination element xpath is getting changed.
I tried to use selenium action class with below code , but it didn't work for me.
Code Example 1
//CustomListItemElement = source element
// MainPanelElement = destination element
var action = new Actions(_webDriver);
action.DragAndDrop(CustomListItemElement, MainPanelElement);
action.Build().Perform();
Code Example 2
var action = new Actions(_webDriver);
action.MoveToElement(CustomListItemElement);
action.ClickAndHold(CustomListItemElement);
action.MoveByOffset(240, 52);
action.MoveToElement(MainPanelElement);
action.MoveByOffset(799, 53);
action.Release(MainPanelElement).Perform();
there is no error and test pass. it drags the element to destination , but didn't release it. (i think because destination xpath changeafter dragging it)
can someone help me with this please?
Updated:
Source Element:
<li class="bar-list-item ng-scope drag-list-tool-item" ng-repeat="toolItem in toolboxItems | filter:filterFunction" drag-list-tool-item="toolItem" draggable="true">
Destination Element:
<ul drag-list="day.Items" drag-list-id="day.AgendaID" create-from-tool="createAgendaItem(itemType)" on-item-added="onItemAdded(index, item, $index)" on-item-moved="onItemMoved(fromIndex, toIndex, item, $index)" class="bar-list drag-list ng-scope">
<!-- ngRepeat: item in day.Items -->
</ul>
After source element drag in to destination element it's changed to below.
<ul drag-list="day.Items" drag-list-id="day.AgendaID" create-from-tool="createAgendaItem(itemType)" on-item-added="onItemAdded(index, item, $index)" on-item-moved="onItemMoved(fromIndex, toIndex, item, $index)" **class="bar-list drag-list ng-scope list-drag-over"**>
<!-- ngRepeat: item in day.Items -->
</ul>

Related

Angular JS- testing with Protractor: how to get an element when element doesn't have a tag-name?

I am developing a test suite for my AngularJS app using Protractor as my testing framework.
The app's menu is displayed at a fixed location across all pages of the site- when the user hovers the cursor over any of the buttons on the menu, the cursor changes to a pointer, to indicate that that button can be clicked. Each button in the menu represents a particular page of the app- if there are sub-pages belonging to a page when the user hovers the cursor over the menu button for that page, a sub-menu is displayed, with text-buttons for each of the sub-pages.
I am currently working on a test that will test the functionality of the menu- hovering the cursor over one of the buttons on the menu, checking that the sub-menu is displayed, and then hidden again when the cursor is no longer hovering over that menu button, checking that clicking any of the menu buttons/ sub-menu buttons take you to the right page, etc.
The HTML for the menu is written as follows:
<ul id="nav"
class="nav"
data-slim-scroll
data-collapse-nav
data-highlight-active>
<li data-ng-mouseenter="setMenuTop($event)" ng-show="menu.pages.length > 0 || menu.upages.length > 0 || canCreatePage">
<i class="ti-layers"></i><span data-i18n="Pages"></span>
<ul class="text-capitalize">
<li ng-repeat="page in menu.pages"><i class="ti-angle-right"></i><span data-i18n="{{page.title}}"></span></li>
<li ng-repeat="upage in menu.upages">
<div class="nav-menu-divider" data-ng-if="$index === 0"></div>
<a href="#/{{upage.location}}" target="_self">
<i class="ti-angle-right"></i><span data-i18n="{{upage.title}}"></span>
</a>
</li>
<li data-ng-show="canCreatePage">
<div class="nav-menu-divider"></div>
<a href="#/pages/create" target="_self">
<i class="ti-angle-right"></i><span data-i18n="Create Page"></span>
</a>
</li>
</ul>
</li>
<li>...</li>
<li>...</li>
<li>...</li>
<li>...</li>
<li data-ng-mouseenter="setMenuTop($event)">
<i class="ti-settings"></i><span data-i18n="Config"></span>
<ul>
<li ng-repeat="page in menu.config"><i class="ti-angle-right"></i><span data-i18n="{{page.title}}"></span></li>
<li ng-show="platform._id"><a ng-href="#/config/platform/{{platform._id}}" target="_self"><i class="ti-angle-right"></i><span data-i18n="Platform"></span></a></li>
<li ng-show="system._id"><a ng-href="#/config/system/{{system._id}}" target="_self"><i class="ti-angle-right"></i><span data-i18n="System"></span></a></li>
<li><i class="ti-angle-right"></i><span data-i18n="Users"></span></li>
<li><i class="ti-angle-right"></i><span data-i18n="Backup / Restore"></span></li>
<li ng-show="systemActions.dateTime"><i class="ti-angle-right"></i><span data-i18n="Date / Time"></span></li>
</ul>
</li>
<li class="nav-footer">
<span>{{currentTime.localTDZFormat}}</span>
</li>
</ul>
As shown, there are two menu items which have 'sub-menus' associated with them.
I want to test that when the user hovers their cursor over one of the menu-items that have an associated sub-menu, the sub-menu is displayed correctly, and when the cursor leaves the menu-item, the sub-menu is no longer displayed: I have tried to do this with the following test:
it('should display the Pages menu', function() {
browser.waitForAngularEnabled(false);
browser.actions().mouseMove(pagesMenuBtn).perform();
expect(pageVarBrowserBtn.isDisplayed()).toBeTruthy();
browser.actions().mouseMove(userCircle).perform();
expect(pageVarBrowserBtn.isDisplayed()).toBeFalsy();
browser.waitForAngularEnabled(true);
});
The variables used in this test are defined with:
var pagesMenuBtn = element(by.id('pagesMenuBtn'));
var pageVarBrowserBtn = element.all(by.id('menu.pages')).get(0);
var userCircle = element(by.id('icon-user-circle'));
The pageVarBrowserBtn is the first menu-item in the sub-menu displayed when the user hovers their cursor over the pagesMenuBtn.
Currently, when I run the test, the browser opens, navigates to the root page of my app, and hovers the cursor over the pagesMenuBtn element, so that the sub-menu is displayed (I can see it displayed in the browser window that was opened up by the test).
But, I then get an error in the console that says:
Failed: Index out of bound. Trying to access element at index: 0, but there are only 0 elements that match locator By(CSS selector, *[id="menu.pages"])
I don't understand why I'm getting this error...? How can I get a single element from the sub-menu that's being displayed, in order to check that it is the correct item and that clicking it takes the user to the correct location? I can't specify the exact element's ID because it is being created by the ng-repeat, so that's why I'm trying to get it by its position/ array index - 0.
First of all, There may be two reasons why you are getting the above error.
1. Either the locator you are using is wrong.
2. Your script is not waiting until the element is displayed in DOM.
In your case, you have mentioned a wrong locator as element.all(by.id('menu.pages')) because there is no element with id menu.pages is present in your HTML.
Instead of using element.all(by.id('menu.pages')), change it to element.all(by.repeater('page in menu.pages')).get(0) to get the 1st element from the menu list. Also kindly refer Element locators in Protractor to find the list of all available locating strategies that is supported by protractor.
Hope this might help solve your problem!
Did you try using element.all(locator).first() to get the first element form the selector

Limiting the drag area in angular drag drop list

I'm using the angular drag drop list library for drag/drop of items in a list. Currently I see the item is draggable throughout the body of the page. How can I limit the drag area to only to a particular zone and not everywhere. I checked their API but I don't see any option for that.
In the plunker example you cannot drag and drop every where, only on html element with dnd-draggable
<ul dnd-list="list">
<!-- The dnd-draggable directive makes an element draggable and will
transfer the object that was assigned to it. If an element was
dragged away, you have to remove it from the original list
yourself using the dnd-moved attribute -->
<li ng-repeat="item in list"
dnd-draggable="item"
dnd-moved="list.splice($index, 1)"
dnd-effect-allowed="move"
dnd-selected="models.selected = item"
ng-class="{'selected': models.selected === item}"
>
{{item.label}}
</li>
</ul>

Angular dropped item stay on dropped position

I'm using angular-dragdrop to move images from on div to another div. As each div has its own $scope list, when I drop an element from one to another list, the list which receives the item is updated.
However when I drop an item into the droppable area, it goes to top-left corner. It does not stay where I dropped
this is my html
Draggable
<div class="sand-image" ng-repeat="item in filtered = (products | filter: {cat : config.category.id}:true) |
itemsPerPage: pageSize"
current-page="currentPage"
data-drag="true"
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
ng-model="products"
jqyoui-draggable="{index:$index,applyFilter:'{{getIndex(item)}}',
placeholder: 'keep',deepCopy :true}"
></div>
Droppable
<div class="wrap" id="sand-ground"
data-drop="true"
ng-model='box'
jqyoui-droppable="{multiple:true, deepCopy:true ,onOver :'stop',containment :'position'}" >
<!-- item html -->
<div class="draggable" ng-repeat="item in box track by $index" resizable ><img src="{{item.url}}" ></div>
The item in the second list ( dropable ) has a directive ( resizable )to set some info to the item.
How can I make the dropped item stay on dropped position?
$scope.dropAction = function(event, ui) {
console.log('left:', ui.position.left, 'top:', ui.position.top, ui);
// update model
var newItem = {
left:, ui.position.left,
top:, ui.position.top
};
$scope.someList.push(newItem);
};
Since you can capture the position in your drop function, I decided to just update a model that manages the positions of each draggable item. In your drop function:
jqyoui-droppable="{onDrop: 'dropaction($event)'}"
Then in your markup you can repeat through the items and use the stored property to position them with inline ng-style.
ng-repeat="item in someList" ng-style="{'left': item.left, 'top': item.top}"
These are the basics of what I did and you will most likely have to expand it to fit your usage. In my case, this captures pixels and I am converting to percentages in order to get responsive positions. I also launch a $uibModal that takes more information about my item before I place it.

Use CSS3 animations with a list in Angular

I have a list of items with a checkbox for each item. I'm using CSS3 animations (this particular CSS http://daneden.github.io/animate.css/) and Angular to manipulate this list. I've managed to animate adding a new element to the list, but I'm having trouble animating removal of an item from the list (e.g. deleting or checking the checkbox).
This is what a list item looks like:
<li ng-class="{'animated fadeInDown' : task.isNew, 'animated fadeOutRight' : delete}" class="list-group-item" ng-repeat="task in tasks | filter:{is_done: false}" ng-mouseover="hovering = true" ng-mouseout="hovering = false">
<input type="checkbox" ng-model="task.is_done" ng-click="toggleDone(task)">
<span class="done-{{task.is_done}}" ng-hide="editing" ng-dblclick="editing = true">{{task.name}} </span><a href ng-show="hovering" ng-click="deleteTask(task); delete = true">x</a>
</li>
When I add a new task, the property task.isNew is set to true which sets 'animated fadeInDown' class to the list element. However, when I click on delete link, I would expect the class to be 'animated fadeOutRight' and that the removal is actually animated. This doesn't happen though, the item is removed but no animation is displayed.

ng-repeat Circular Reference Screen Refresh

I'm a noob to AngularJS. As a learning exercise I am creating a typeahead control.
The typeahead is comprised of a text box for filtering the options, an unordered list for displaying the menu of short-listed options, and a Show/Hide button for manually toggling the list.
The text box filters the li elements using ng-repeat. When selecting an li item from the list, I populate the text box with the selected value, and then hide the list.
<div class="xtab-typeahead" ng-controller="xTabTypeAheadCtrl">
<input type="text" class="xtab-typeahead-search" placeholder="{{type}}" ng-model="query.id" ng-focus="showMenu()" ng-init="query.id = undefined" />
<button ng-click="toggleMenu()">Show/Hide</button>
<ul class="xtab-typeahead-menu" ng-class="{true:'active',false:''}[isActive]">
<li ng-repeat="item in menuItems | filter:query"
ng-click="hideMenu(); query.id = item.id">
{{item.id}}
</li>
</ul>
</div>
My issue is that when the value is assigned to the text box, the list is momentarily filtered to the one option selected (because the text box is also the source of the ng-repeat filter), before hiding the menu. What I want id for the menu to be hidden without being refreshed with the filter first.
It should be noted that this only occurs when using CSS transitions which I am using to fade the menu out.
Here is a plnkr to illustrate.
Here's a working Plnkr. I basically gave your menu time to finish its animation before setting the value. Relevant code is as follows:
$scope.selectItem = function(id){
$scope.isActive = false;
$timeout(function(){
$scope.query.id = id;
}, 250);
}
...and the HTML using the newly created selectItem method:
<div class="xtab-typeahead" ng-controller="xTabTypeAheadCtrl">
<input type="text" class="xtab-typeahead-search" placeholder="{{type}}" ng-model="query.id" ng-focus="showMenu()" ng-init="query.id = undefined" />
<button ng-click="toggleMenu()">Show/Hide</button>
<ul class="xtab-typeahead-menu" ng-class="{true:'active',false:''}[isActive]">
<li ng-repeat="item in menuItems | filter:query"
ng-click="selectItem(item.id)">
{{item.id}}
</li>
</ul>
</div>

Resources