protractor 4 : how to locate a parent at 'n' level above by class - protractor

For instance with following html:
<div class='parent'>
<p>
<span class='child'></span>
</p>
</div>
To locate .parent from .child I'm trying something like :
let child = element(by.css('.child'));
let parent = child.element(by.xpath('../..')); // not working
let parent = child.element(by.xpath('ancestor::.parent')); // not working
What's the best way to achieve that?

You have mentioned incorrect locator combination. That is incuded css value '.parent' in xpath expression. The right way is:
let parent = child.element(by.xpath('ancestor::div'));
OR
let parent = child.element(by.xpath('ancestor::div[1]'));

Related

Protractor scrollleft but class have multiple instance

I have an element with class="objbox" but this attribute have multiple instances.
The current code that I use for scrolling is browser.executeScript('$(".objbox").scrollLeft(' + strPixels + ')'); but since there are multiple instances, it seems like it is getting the first instance and scroll was not successfully done to the target element.
I am wondering if it is possible to include the parent element on my code, or if there is a different work around.
<div class="dhxgrid2-wrapper">
<div class="dhtmlxgrid-container gridbox">
<div class="objbox">
...
</div>
</div>
</div>
It's possible.
What you need to do is the following
// Define the elementfinder of your parent, pick option A or B
const elementFinderWithParentA = $('.dhtmlxgrid-container .objbox');
// Or
const elementFinderWithParentB = $('.dhtmlxgrid-container').$('.objbox');
// The amount to scroll
const scrollLeft = 50;
browser.executeScript('arguments[0].scrollLeft = arguments[1];', elementFinderWithParentA, scrollLeft);
// Or making it more readable, make a function for the scrolling
// and pass it to the browser.executeScript
function scrollToLeft(element, scrollAmount) {
element.scrollLeft = scrollAmount;
}
browser.executeScript(scrollToLeft, elementFinderWithParentA, scrollLeft);
Hope it helps

protractor get element link inside nested div

nested div structure and inside it i have link
like this:
<div>
<div>
<a ng-click>
Now i reached the second div successfully, but I wasn't able to reach the second div and to click it(need to test if the click works)
My structure:
How i select the selected element in the picture and click it? thanks
Can you help? thanks
Just use CSS Attribute Selectors.
var el = element(by.css('a[ng-click="openService()"]'));
// or using the $() shorthand
// var el = $('a[ng-click="openService()"]');
el.click();
If that doesn't work, you can try cssContainingText()
var el = element(by.cssContainingText('a', 'gfd'));
Or it's close relative linkText.
var el = element(by.linkText('gfd'))

Search on descendants of an element

With protractor whats the best way to select child elements? Say we have the layout below...
<div id='parent_1'>
<div class='red'>Red</div>
<div class='blue'>Blue</div>
</div>
<div id='parent_2'>
<div class='red'>Red</div>
<div class='blue'>Blue</div>
</div>
With jQuery we'd do something like this.
var p1 = $('#parent_1');
var p1_red = $('.red', p1); //or p1.find('.red');
var p1_blue = $('.blue', p1); //or p1.find('.blue');
But with Protractor does it make sense to first get the parent element?
Since doing this var p1 = element('#parent_1'); doesn't actually retrieve/search for the object until getText() or something is called.
so doing this..
Scenario 1
expect(p1.element('.red')).toBe('red');
expect(p1.element('.blue')).toBe('blue');
OR
Scenario 2
expect(element('#parent_1').element('.red')).toBe('red');
expect(element('#parent_1').element('.blue')).toBe('blue');
OR
Scenario 3
expect(element('#parent_1 > .red')).toBe('red');
expect(element('#parent_1 > .blue')).toBe('blue');
Are there any benefits in one approach over the other?
This is what I'm doing but I don't know if there's any advantage of separating the parent from the cssSelector:
function getChild(cssSelector, parentElement){
return parentElement.$(cssSelector);
}
var parent = $('#parent_1');
var child_red = getChild('.red', parent);
var child_blue = getChild('.blue', parent);
Looking at Protractor's elementFinder I could be doing this:
function getChild(cssSelector, parentCss){
return $(parentCss).$(cssSelector);
}
var child_red = getChild('.red', '#parent_1');
var child_blue = getChild('.blue', '#parent_1');
The advantage of separating the child from the child css selector would only be if you'd like to use the parent for something else. Otherwise, it's slightly faster to do it in one call, like expect(element('#parent_1 > .red')).toBe('red'); since Protractor doesn't need to make two calls to the browser in this case.
Another reason to use the first approach would be if you were using a Locator strategy that cannot be expressed in CSS. For example:
var parent = element(by.css('.foo'));
var child = parent.element(by.binding('childBinding'));
expect(child.getText()).toEqual('whatever');

Are global variables required for some types of DOM manipulation with javascript?

In the example code below, I want to know why the variable called child must be global ( no var) in order for the code to work. I also want to know if the code below is considered bad practice due to having a global variable and how a better practiced rendition of the code below might look. Thanks.
<!DOCTYPE html>
<meta charset="UTF-8">
<title>dom</title>
<div class="product">
<h2> Product Name </h2>
<img src="pic.jpg" />
<p> Description </p> </div>
<script>
var products = document.getElementsByClassName("product"),
child; // how come var breaks the code ?
for ( i = 0; i < products.length; i++) {
child = products[i].firstChild;
while (child.nodeType !== 1) {
child = child.nextSibling;
}
console.log(child);
}
</script>
You already have a var, as there is a comma before child. Hence adding var would give you
var product, var child
which is illegal.
child is not global, because the var in
var product, child
applies to the whole list of variables following var. (Well, child is global anyway since it is not nested in a function. But that does not have to do with var or not var.)
If you insist on having var twice, write
var product = ... ;
var child;

XHTML DOM - How to split a tag on IE?

Let's assume I have a part of an html document containing the following code (basic structure) :
<p>
<span class="1">This is my first content</span>
<span class="2">This is my second content</span>
</p>
I'd like to allow the user to select a part of the text and apply a new class to it.
Let's say the user selects "is my first" in the first span, and applies class "3".
I'd like to have the following result :
<p>
<span class="1">This </span>
<span class="3">is my first</span>
<span class="1"> content</span>
<span class="2">This is my second content</span>
</p>
I've managed to do this on Firefox by using the execCommand "InsertHTML", but I can't find a way to do this in IE (before IE9)
The only result I have is a nested span element, like below :
<p>
<span class="1">This <span class="3">is my first</span> content</span>
<span class="2">This is my second content</span>
</p>
Do you have any idea of how I could achieve this ?
Any help would be much appreciated !
By the way, if this looks too simple to you, how would you handle the case of a user selecting a portion of text that spans over 2 or more spans ? over 2 or more ps ?
you can get the selected segment using selection range. I would recommend using rangy, which is a cross browser range module.
Here's some "untested" code using jQuery and Rangy to hopefully point you in the right direction, for your first case:
var splitTag=function(class){
var sel = rangy.getSelection();
// this is your selection, in your example "is my first"
var r0 = sel.getRangeAt(0);
// create a new range
var r1 = rangy.createRange();
// this would be your <p>
var p = r0.endContainer.parentNode;
// set the new range to start at the end of your phrase and to end at <p>
r1.setStart(r0.endContainer, r0.endOffset);
r1.setEnd(p, p.length-1);
// extract the content of your first selection "is my first"
var r0Txt=r0.toHtml();
// make it into an span, with class set to "class argument" which would be 3
var newContent=$("<span/>").html(r0Txt).attr("class", class);
r0.deleteContents();
// insert the new node before r1
r1.insertNode(newContent[0]);
sel.removeAllRanges();
}
this should get you the result for your first situation. for selections across multiple paragraphs, here's a modification of the code:
var splitTag=function(class){
var sel = rangy.getSelection();
var r0 = sel.getRangeAt(0);
var r1 = rangy.createRange();
var p = r0.endContainer.parentNode;
r1.setStart(r0.endContainer, r0.endOffset);
r1.setEnd(p, p.length-1);
var r0Txt=r0.toHtml();
if(!r0.startContainer===r0.endContainer){
// the selection spans multiple dom's
// set the class of all spans in the highlight to 3
var newContent=$(r0Txt).find("span").attr("class",class);
}else{
var newContent=$("<span/>").html(r0Txt).attr("class", class);
}
r0.deleteContents();
r1.insertNode(newContent[0]);
sel.removeAllRanges();
}