puppeteer using queryselector from elementhandle - xpath

I have element handle that are result from this xpath
var theXpath = await page.$x(
"//div[contains(#class, 'fontBodyMedium')]"
)
how would i run queryselector
"div > div > span > span:nth-of-type(2)"
inside that elementhandle and then get the innerHTML of the result?

apparently i can just use $eval
var theXpath = await page.$x(
"//div[contains(#class, 'fontBodyMedium')]"
)
for (let index = 0; index < theXpath.length; index++) {
const inner_html = await theXpath[index].$eval(
"div > div > span > span:nth-of-type(2)",
(element) => element.innerHTML
)
}

Related

Nightwatch js iterating multiple webelements

Can anyone help me with exact code to iterate multiple elements & clicking it having same ID or XPath or css whatever just like we do findelements in selenium
This format will click on each element found using elements()
async clickElements() {
const list = '//div[#id = "test"]//div[#class = "row"]';
this.api.useXpath();
const results = this.api.elements('xpath', list);
const listLength = results.value.length;
for (let i = 0; i < listLength; i++) {
const element = `//div[#id = "test"]")]//div[#class = "row"][${i + 1}]`;
this.api.waitForElementVisible(element);
this.api.click(element);
}
},

Get text from element & use it for next action

I'm beginner Cypress user, currently facing a problem with getting text from DOM element and then using it for next action.
Steps which were done by me:
1) Get element + text
Then(
"User change quantity of product: {string} to {int}",
(productName, qty) => {
var currentQty;
cy.get("[data-cy ='basketContainer']")
.contains(productName)
.parent()
.parent()
.find("div > span")
.then((variable) => {
var quantityText = variable.text();
cy.wrap(quantityText).as("quantityText");
});
In this case I'm completely not sure if entire: "then" is ok. The case which I was investigation is that in cypress there is no "normal" variable assignment. It may be done with combination of wrap and then alliasing it.
2) Get the alliased value
cy.get("#quantityText");
Not very needed step but I was wondering if we can reference to aliases by '#'
3) Use that alias inside the "method"
var increaseButton = cy.get("[data-cy='decreaseQtyProduct']");
var decreaseButton = cy.get("[data-cy='increaseQtyProduct']");
var timesToClick = cy.get("#quantityText") + qty;
cy.log(timesToClick)
if (currentQty == qty) {
console.log("Do nothing, values are equal!");
} else if (currentQty > qty) {
for (let i = 1; i < timesToClick; i++) decreaseButton.click();
} else if (currentQty < qty) {
timesToClick = Math.abs(timesToClick);
for (let i = 1; i < timesToClick; i++) increaseButton.click();
}
When I'm cy.logging that variable: "timesToClick" I'm receiving [object Object] instead of text inside an element. For sure what also needs to be done is to parse that string value to int value.
There's nothing wrong with the way you create the alias, but when using it don't save the result of cy.get(...) to variables, either getting the alias or selecting the elements.
It can lead to unpredictable results. For element selection, you can save the selector string instead. For using the alias, you must use .then().
Some other optimizations included too.
const increaseButton = "[data-cy='decreaseQtyProduct']"
const decreaseButton = "[data-cy='increaseQtyProduct']"
cy.get('#quantityText').then(quantityText => {
if (currentQty === qty) return
const timesToClick = Math.abs(parseInt(quantityText) + qty)
const selector = currentQty > qty ? increaseSelector : decreaseSelector;
Cypress._.times(timesToClick, () => cy.get(selector).click())
})
You can alias the inner text like this:
Then(
'User change quantity of product: {string} to {int}',
(productName, qty) => {
var currentQty
cy.get("[data-cy ='basketContainer']")
.contains(productName)
.parent()
.parent()
.find('div > span')
.invoke('text')
.as('quantityText')
}
)
Then you can fetch the value from the alias like this:
var increaseButton = cy.get("[data-cy='decreaseQtyProduct']")
var decreaseButton = cy.get("[data-cy='increaseQtyProduct']")
cy.get('#quantityText').then((quantityText) => {
var timesToClick = +quantityText + qty //+quantityText changes string to number
console.log(timesToClick) //prints the number
if (currentQty == qty) {
console.log('Do nothing, values are equal!')
} else if (currentQty > qty) {
for (let i = 1; i < timesToClick; i++) decreaseButton.click()
} else if (currentQty < qty) {
timesToClick = Math.abs(timesToClick)
for (let i = 1; i < timesToClick; i++) increaseButton.click()
}
})

Changing number of columns while using tablesorter pager with ajax

I am trying to add/remove columns depending on what is returned from the server in the ajaxProcessing function. I can see in the pager source that this isn't possible directly.
// only add new header text if the length matches
if ( th && th.length === hl ) {
Is there any workaround to this or should I start tinkering with the code? If I need to make changes, any advice on what to start with? Thanks.
Added this in the ajaxProcessing event and it did the trick.
var headerCount = $('#id thead tr:eq(0) th').length;
var hl = data.headers.length;
if (headerCount < hl) {
for (var i = headerCount; i < hl; i++) {
$('#id thead tr:eq(0)').append('<th>' + data.headers[i] + '</th>');
}
}else if (headerCount > hl) {
for (var i = headerCount - 1; i >= hl; i--) {
$('#id thead tr:eq(0) th:eq(' + i + ')').remove();
}
}
if(headerCount !== hl) $('#id').trigger('updateAll', [false, null]);

Set selection on tekst inside CKEditor

I'm having trouble to select text in CKEditor(3.6). As we use plain text i dont know how to use correctly the range selectors.
HTML code of the CKEditor:
<body spellcheck="false" class="rf-ed-b" contenteditable="true">
<br>
Cross those that apply:<br>
<br>
<br>
[«dummy»] If he/she is tall<br>
<br>
[«dummy»] If he/she is a male<br>
<br>
[«dummy»] If he/shi is a minor<br>
<br>
Specialties:<br>
<br>
[«dummy»] «Write here the specialties if known»<br>
<br>
<br>
«You are now done with filling in this form»<br>
</body>
With the keys 'CRTL+N' I want to go to the next filleble spot:
«[label]»
I tried stuff like:
var editor = CKEDITOR.instances['MyEditor'];
var findString = '«';
var element = editor.document.getBody();
var ranges = editor.getSelection().getRanges();
var startIndex = element.getHtml().indexOf(findString);
if (startIndex != -1) {
ranges[0].setStart(element.getFirst(), startIndex);
ranges[0].setEnd(element.getFirst(), startIndex + 5);
editor.getSelection().selectRanges([ranges[0]]);
}
Error:
Exception: Index or size is negative or greater than the allowed amount
While totally stripepd down it kinda works a bit:
var editor = CKEDITOR.instances['MyEditor'];
var ranges = editor.getSelection().getRanges();
var startIndex = 10;
if (startIndex != -1) {
ranges[0].setStart(element.getFirst(), startIndex);
ranges[0].setEnd(element.getFirst(), startIndex + 5);
editor.getSelection().selectRanges([ranges[0]]);
}
here it selects 5th till 10th char on first row.
I used the following sources:
example on Stackoverflow
Another stackoverflow example
CKEditor dom selection API
All solutions i can find work with html nodes.
How can set selection range on the '«' till next '»'
I've managed to solve this solution. Meanwhile i also upgraded CKeditor to 4.0.
This shouldnt have an impact on the solution.
It is a lot of code in JS.
On my keybinding i call the following JS function: getNextElement()
In this solution it also searches behind the cursor, this makes it possible to step through multiple find results.
Also the view gets scrolled to the next search result
var textNodes = [], scrollTo=0,ranges = [];
function getNextElement(){
var editor =null;
ranges = [];
// I dont know the ID of the editor, but i know there is only one the page
for(var i in CKEDITOR.instances){
editor = CKEDITOR.instances[i];
}
if(editor ==null){
return;
}
editor.focus();
var startRange = editor.getSelection().getRanges()[0];
var cursorData ="",cursorOffset=0,hasCursor = false;
if(startRange != null && startRange.endContainer.$.nodeType == CKEDITOR.NODE_TEXT){
cursorOffset = startRange.startOffset;
cursorData = startRange.endContainer.$.data;
hasCursor = true;
}
var element;
element = editor.document.getBody().getLast().getParent();
var selection = editor.getSelection();
// Recursively search for text nodes starting from root.
textNodes = [];
getTextNodes( element );
var foundElement = false;
foundElement = iterateEditor(editor,hasCursor,cursorData,cursorOffset);
if(!foundElement){
foundElement =iterateEditor(editor,false,"",0);
}
if(foundElement){
// Select the range with the first << >>.
selection.selectRanges( ranges );
jQuery(".cke_wysiwyg_frame").contents().scrollTop(scrollTo);
}
}
function iterateEditor(editor,hasCursor,cursorData,cursorOffset){
var foundElement = false;
var rowNr = 0;
var text, range;
var foundNode = false;
if(!hasCursor){
foundNode = true;
}
// Iterate over and inside the found text nodes. If some contains
// phrase "<< >>", create a range that selects this word.
for (var i = textNodes.length; i--; ) {
text = textNodes[ i ];
if ( text.type == CKEDITOR.NODE_ELEMENT && text.getName() == "br" ){
rowNr++;
} else if ( text.type == CKEDITOR.NODE_TEXT ) {
var sameNode = false;
if(text.$.data == cursorData){
foundNode = true;
sameNode = true;
}
if(foundNode){
var startIndex = -1;
var endIndex = 1;
if(sameNode){
// Check inside the already selected node if the text has multiple hits on the searchphrase
var indicesStart = getIndicesOf('\u00AB', text.getText());
var indicesEnd = getIndicesOf('\u00BB', text.getText());
for (var j = indicesStart.length; j--; ) {
if(indicesStart[j] > cursorOffset){
startIndex = indicesStart[j];
endIndex = indicesEnd[j];
}
}
} else{
startIndex = text.getText().indexOf( '\u00AB' );
endIndex = text.getText().indexOf( '\u00BB' );
}
if ( startIndex > -1 && (!sameNode || startIndex > cursorOffset)) {
range = editor.createRange();
range.setStart( text, startIndex );
foundElement = true;
// calculate the height the window should scroll to focus the selected element
scrollTo = (rowNr)*20;
}
if ( endIndex > -1 && foundElement ) {
range.setEnd( text, endIndex+1 );
ranges.push( range );
return true;
}
}
}
}
}
function getIndicesOf(searchStr, str) {
var startIndex = 0, searchStrLen = searchStr.length;
var index, indices = [];
while ((index = str.indexOf(searchStr, startIndex)) > -1) {
indices.push(index);
startIndex = index + searchStrLen;
}
return indices;
}
function getTextNodes( element ) {
var children = element.getChildren(), child;
for ( var i = children.count(); i--; ) {
child = children.getItem( i );
textNodes.push( child );
}
}

Need get all A tags in selection in editable iframe and add them attribute "class"

I have an editable <iframe> with the some HTML code in it. I need get all <a> tags in my range. I tried this code but it doesn't work:
var select = document.getElementById(iframe_id).contentWindow.getSelection();
var range = select.getRangeAt(0);
//HERE I WANT TO FIND ALL TAGS IN THIS RANGE AND IF IT "A" - ADD NEW ATTRIBUTE "CLASS". SOMETHING LIKE THIS
var parent = rng.commonAncestorContainer;
for(var i=0; i<parent.childNodes.length; i++)
{
if(parent.childNodes[i].tagName.toLowerCase() == "a")
parent.childNodes[i].setAttribute("class", "href_class");
}
You can use getElementsByTagName() to get all <a> tags of the range container and then check for each of them whether it actually belongs to the range using range.compareBoundaryPoints() (only parts of the container might be selected). Something like this:
var links = rng.commonAncestorContainer.getElementsByTagName("a");
for (var i = 0; i < links.length; i++)
{
var linkRange = document.createRange();
linkRange.selectNode(links[i]);
if (rng.compareBoundaryPoints(Range.START_TO_START, linkRange) <= 0 && rng.compareBoundaryPoints(Range.END_TO_END, linkRange) >= 0)
{
links[i].className = "href_class";
}
}
This should get you started in the right direction. This code does not do any null reference checks on the iframe, selection, range or list.
function addAnchorClass(targetFrameId) {
var targetIframe = document.getElementById(targetFrameId).contentWindow;
var selection = targetIframe.getSelection();
var range = selection.getRangeAt(0);
var alist = range.commonAncestorContainer.getElementsByTagName("a");
for (var i=0, item; item = alist[i]; i++) {
if (selection.containsNode(item, true) ) {
item.className += "PUT YOUR CSS CLASS NAME HERE";
}
}
}

Resources