fetching element from 2nd element of array protractor - selenium

I have 4 container boxes on a page which are identical. I have taken all of them in an array. Now each container box contains some elements which are identical as well. I want to fetch 2nd element from 1st container.
Below is what i used to fetch my container and it is working fine:
var pageContent = element(by.css('[class="main-content"]'));
expect(pageContent.all(by.css('.modularBoxContent')).count()).tobe(4);
Now if i want to fetch count of all elements with class as labelText insider modular box container, i am able to do that by using below:
expect(pageContent.all(by.css('.modularBoxContent')).all(by.css('[class="labelText"]')).count()).tobe(10);
But this is giving me labelText present in all 4 containers. I want to get count of labelText only in first container or get text of first labelText in first container. I tried below code but it is not working and getting error message
TypeError: this.pageContent.get is not a function
expect(pageContent.get(0).all(by.css('.modularBoxContent')).all(by.css('[class="labelText"]')).count()).tobe(3);
I also tried below but that is not working as well.
Getting same error as above for this as well.
expect(pageContent.all(by.css('.modularBoxContent')).get(1).all(by.css('[class="labelText"]')).get(0).getText()).tobe(3);
Can someone please suggest correct usage?

I think this should work for you, let's try this. Since all containers have the same class, create an array with .all() and use .get() to retrieve the desired index.
var pageContent = element(by.css('.main-content'));
var firstContainer = pageContent.all(by.css('.modularBoxContent')).first(); // or could have used .get(0);
Once your first container is successfully identified, now you just chain locator calls to find all the children under it. Since you said you wanted the second element under the first container, we'll use .get(1) (1 is the index, so it's the second item in the array).
var secondChild = firstContainer.all(by.css('[class="labelText"]')).get(1);
And, just an FYI, a lot of this could probably be refactored to be shorter if you wanted. For example, you can chain your CSS calls, no need to separate them:
The above code should be the same as this:
var firstContainer = element.all(by.css('.main-content .modularBoxContent')).first();
var secondChild = firstContainer.all(by.css('.labelText')).get(1);

Related

Click on first element contained in a div

I have a div that contains a set of dynamic elements. I want to click on the first search result.
I want to click on the first element contains in
I tried using creating a custom xPath like so but it didn't work. Any ideas here?
//div[1][contains(text(), 'listing')]
First of all It would've helped if you had provided more information.
best will be using pseudo-child like div.firstChild or if the elements are generated dynamically you can add a class and use document.querySelectorAll(".class") which will give you an array of elements that had the class.
You can use array[0] to use click on the first element.
For anyone coming across this thread here is the solution
const listings = await page.$x('//*[contains(#id,"listing_")]')

locate displayed element with webdriverio

When developing in the Ionic framework, the generated html sometimes will contain duplicate DOM elements, where all but one tree of elements is hidden with a class="ion-page-hidden" at the top level.
When using webdriverio to try and locate an element inside this tree, it find duplicated elements. Since the ion-page-hidden class is at the top of the tree, and not on the element itself, how can we use Xpath to locate only the displayed element.
I couldn't figure out any way to modify the XPath selector with a second condition since the elements are exactly the same!
So instead I have tried to use the webdriverio isDisplayed() function:
get openHamburgerMenu() { return Utils.visibleElement($$("//ion-button[#name='button-open-menu']")); }
where the Utils function is:
async visibleElement(elementArray) {
let returnElement;
elementArray.forEach(element => {
if (element.isDisplayed()) {
returnElement = element;
}
});
return returnElement;
}
but no elements are passed into the function. In the chrome browser, I can see two that match the xpath //ion-button[#name='button-open-menu']. I need the one not in the ion-page-hidden block!
tree
The tree looks like this:
app-for-homes[1]/ion-header/ion-toolbar/ion-buttons[1]/ion-button
app-for-homes[2]/ion-header/ion-toolbar/ion-buttons[1]/ion-button
where app-for-homes[2] happens to have the ion-page-hidden class.
I think it should be possible to use ancestors to identify which of the two elements, matching the xpath, does not have a 4th level ancestor with that class? But I'm way out of my depth on day one of working with xpaths...
Quick and Dirty
The desired outcome can be achieved using this Xpath:
//app-for-homes[1]/ion-header/ion-toolbar/ion-buttons/ion-button[#name='button-open-menu']
However, this only works where the order in which the elements appears is known.
Better Answer
When you have exactly 1 element that is not hidden, Xpaths allow you to look at an elements ancestors as far back as you want to identify the presence / or absence of the hidden class. In this case, we start by finding the ancestor app-for-homes which does not include the ion-page-hidden class:
//app-for-homes[not(contains(#class,'ion-page-hidden'))]
and then simply append the remainder of the path to the desired element. Full answer =
//app-for-homes[not(contains(#class,'ion-page-hidden'))]/ion-header/ion-toolbar/ion-buttons/ion-button[#name='button-open-menu']

trying to expand out nodes to null parent

I'm using cytoscape.js and cytoscape.js-expand-collapse to create dynamic hierarchies in my graph structure. I would like to be able to dynamically create a collapsed(merged) node that could potentially be expanded out, removed or possibly re-merged with additional nodes. I am having trouble whenever I call nodes.move({parent:null}). They become detached from the graph and I cannot re-attach them to a new parentNode. If I call restore on them I will see errors saying they already exist in the graph.
merge and unmerge by itself works fine in a simple case of no existing collapsed nodes. However calling merge on something that already contains a compound collapsed node breaks things. I would get the resulting merged node without the children of the previously collapsed merge candidate
Update #1 I've realized my problem was returning the wrong moved nodes. Calling .move on a collection returns a new set of nodes. so the unmerge should return those instead.
Update #2 cytoscape-expand-collapse utility does some internal book-keeping during the parent node collapse/expand by tucking away node data in 'originalEnds' data prop on the meta edges. so, if I'm now altering the graph dynamically by moving nodes in/out of parents these originalEnds pointers get out of sync causing infinite loops. I am doing some additional node tracking of my own as a workaround.
function merge(nodes){
//if one of the nodes is a compound itself, first uncollapse, then merge
var collapsed = nodes.filter(function(c){return typeof c.data('collapsedChildren')!=='undefined';});
if (collapsed.length>0) {
// for now just assume collapsed is a length of 1
var unmerged = unmerge(collapsed); //unmerged should be now the former collapsed children
nodes = nodes.subtract(collapsed).union(unmerged.nodes());
}
var parentNode = cy.add({group:'nodes', data: {id: parentID}});
nodes.move({parent: parentID});
collapseApi.collapse(parentNode);
return parentNode;
}
function unmerge(parentNode){
collapseApi.expand(parentNode);
var children = parentNode.children();
var moved = children.move({parent:null});
//at this point children become "detached" from the graph
// .removed() returns true, however, calling restore() logs an error and they are still technically in the graph
parentNode.remove();
return moved;
}
ele.move() has a more convenient implementation in cytoscape>=3.4: The elements are modified in-place instead of creating replacement elements.
Old code that uses the returned collection from ele.move() will still work. New code can be simplified by not having to use the returned collections at all.

What's the proper way to access a previous page's elements using Protractor?

I have a test where I click through list of links on a page. If I open the links in new pages, I can iterate through the list using browser.switchTo().window and the original window's handle, clicking on each one.
But if I open a link in the same page (_self) and I navigate back to the original list of links using browser.navigate().back(), I get the following error when it iterates to click the next link:
StaleElementReferenceError: stale element reference: element is not attached to the page document
What's the proper way to access elements on a prior page once you've navigated away?
When you request a object from Selenium, Selenium IDs that object with a internal Unique ID. See the below command
>>> driver.find_element_by_tag_name("a")
<selenium.webdriver.firefox.webelement.FirefoxWebElement
(session="93fc2bec-c9f8-0c46-aec3-1939af00c917",
element="5173f7fb-63ca-e447-b176-4a226d956834")>
As you can see the element has a unique uuid. Selenium maintains these list internally, so when you take an action like click, it fetches the element from its cached and takes action on it.
Once the page is refreshed or a new page is loaded, this cache is no longer valid. But the object you created in your language binding still is. If I try and execute some action on its
>>> elem.is_displayed()
selenium.common.exceptions.StaleElementReferenceException:
Message: The element reference of [object Null] null
stale: either the element is no longer attached to the DOM or the page has been refreshed
So in short there is no way to use the same object. Which means you need to alter your approach. Consider the below code
for elem in driver.find_elements_by_tag_name("a"):
elem.click()
driver.back()
Above code will fail on the second attempt of elem.click(). So the fix is to make sure not to re-use a collection object. Instead use a number based loop. I can write the above code in many different ways. Consider few approaches below
Approach 1
elems = driver.find_elements_by_tag_name("a")
count = len(elems)
for i in range(0, count):
elems[i].click()
driver.back()
elems = driver.find_elements_by_tag_name("a")
This is not a very great approach as I am getting a collection of objects and only using one of them. A page which would have 500+ odd links will make this code quite slow
Approach 2
elems = driver.find_elements_by_tag_name("a")
count = len(elems)
for i in range(1, count + 1):
elem = driver.find_element_by_xpath("(//a)[{}]".format(i))
driver.back()
This is better than approach 1 as I am getting all objects just one. Latter I am getting one and using one
Approach 3
elems = driver.find_elements_by_tag_name("a")
links = []
for elem in elems:
links.append(elem.get_attribute("href"))
for link in links:
driver.get(link)
# do some action
This approach will only work when links are href based. So it is that based on the situation I would choose or alter my approach

Dojo dnd (drag and drop) 1.7.2 - How to maintain a separate (non-dojo-dnd) list?

I'm using Dojo dnd version 1.7.2 and it's generally working really well. I'm happy.
My app maintains many arrays of items, and as the user drags and drops items around, I need to ensure that my arrays are updated to reflect the contents the user is seeing.
In order to accomplish this, I think I need to run some code around the time of Source.onDndDrop
If I use dojo.connect to set up a handler on my Source for onDndDrop or onDrop, my code seems to get called too late. That is, the source that's passed to the handler doesn't actually have the item in it any more.
This is a problem because I want to call source.getItem(nodes[0].id) to get at the actual data that's being dragged around so I can find it in my arrays and update those arrays to reflect the change the user is making.
Perhaps I'm going about this wrong; and there's a better way?
Ok, I found a good way to do this. A hint was found in this answer to a different question:
https://stackoverflow.com/a/1635554/573110
My successful sequence of calls is basically:
var source = new dojo.dnd.Source( element, creationParams );
var dropHandler = function(source,nodes,copy){
var o = source.getItem(nodes[0].id); // 0 is cool here because singular:true.
// party on o.data ...
this.oldDrop(source,nodes,copy);
}
source.oldDrop = source.onDrop;
source.onDrop = dropHandler;
This ensures that the new implementation of onDrop (dropHandler) is called right before the previously installed one.
Kind'a shooting a blank i guess, there are a few different implementations of the dndSource. But there are a some things one needs to know about the events / checkfunctions that are called during the mouseover / dnddrop.
One approach would be to setup checkAcceptance(source, nodes) for any target you may have. Then keep a reference of the nodes currently dragged. Gets tricky though, with multiple containers that has dynamic contents.
Setup your Source, whilst overriding the checkAcceptance and use a known, (perhaps global) variable to keep track.
var lastReference = null;
var target = dojo.dnd.Source(node, {
checkAcceptance(source, nodes) : function() {
// this is called when 'nodes' are attempted dropped - on mouseover
lastReference = source.getItem(nodes[0].id)
// returning boolean here will either green-light or deny your drop
// use fallback (default) behavior like so:
return this.inhertied(arguments);
}
});
Best approach might just be like this - you get both target and source plus nodes at hand, however you need to find out which is the right stack to look for the node in. I believe it is published at same time as the event (onDrop) youre allready using:
dojo.subscribe("/dnd/drop", function(source, nodes, copy, target) {
// figure out your source container id and target dropzone id
// do stuff with nodes
var itemId = nodes[0].id
}
Available mechanics/topics through dojo.subscribe and events are listed here
http://dojotoolkit.org/reference-guide/1.7/dojo/dnd.html#manager