getting a handle on an element in the context of another - puppeteer

I have a typical page containing sections of label/field pairs but with the same label name in different sections. The sections have are named so I can identify them using the name. The HTML is structured so that the section name is a sibling of another element containing the label/fields
<div class="section">Business Address<\div>
<div>
<div class="field">
<div class="label">Country<\div>
<input type="text">
....
If I could identify the label element using a selector only I can do something like: -
const siblingHandle = page.evaluateHandle(() => {
const sectionLabelHandle = Array.from(document.querySelectorAll('.blah')).find(el=>el.textContent.includes('section label name'))
return sectionLabelHandle.nextElementSibling
})
const label = await siblingHandle.$('label selector')
But what I need is a handle on the label element so that I can get its sibling field so I can type a value in it.
I can't use siblingHandle.$eval() as it doesn't return a handle.
I've also considered using page.waitForFunction, passing in the handle so that can be used instead of 'document'
const labelHandle = page.waitForFunction(
handle => Array.from(handle.querySelectorAll('sel')).find(el=>el.textContent.includes('text'),
{},
siblingHandle
)
but I get a cycling JSON error if I do that.
So, a couple of questions,
1) Is there any way to get siblings in Puppeteer without having to use nextElementSibling in an evaluate function?
2) How can I search for an element containing specified text, but in the context of a parent handle rather than document?

Xpath selectors as opposed to CSS selectors can answer both of your questions.
Search for an element via specified text:
const xpathWithText = '//div[text()="Country"]';
Using it to get the next sibling:
const xPathTextToSibling = '//div[text()="Country"]/following-sibling::input';
In practice:
const myInput = await page.waitForXPath(xPathTextToSibling);
await myInput.type('Text to type');
You should not need to search for an element with specific text in the context of a parent handle because the second selector I used above will give you a handle of the element you want to type in directly.

Related

How to click on a button among elements with the same class names but different parent class names in puppeteer js?

<div class="parent1">
<div class="insider">
<button class="mybutton">No1</button>
</div>
</div>
<div class="parent2">
<div class="insider">
<button class="mybutton">No2</button>
</div>
</div>
As it can be seen in the code above, I have 2 buttons with exact same class names but different parent names.
I am able to select the right one but upon returning the class name to use in page.click() I obviously receive the class name which is not what I am looking for to click.
given the number of divs could be random and without paying attention to the order, how can I page.click on 2nd one lets say in this context or if there are more divs the one I am looking for?
My code so far :
const selectedElement = await page.evaluate(() => {
const arr = querySelector('.parent2').querySelector('button').className
return arr
})
await page.click(`.${selectedElement }`);
Which obviously selectedElement receives mybutton as the class name and clicks the first one on the list.
You can either use a descendant combinator between the ancestor and the descendant selectors. Note: it's not neccessary to give the intermediate selector like: .parent2 .insider .mybutton, it is enough to give the outsides: .parent2 .mybutton.
Then you can grab the element as an ElemntHandle and click it.
For Example:
const elementHandle = await page.$('.parent2 .mybutton')
await elementHandle.click()
Or you can use the stricter child combinator > by giving the path from ancestor to descendant:
const elementHandle = await page.$('.parent2 > .insider > .mybutton')
await elementHandle.click()
Did you know? If you right click on an element in Chrome DevTools "Elements" tab and you select "Copy": there you are able to copy the exact selector or xpath of an element. After that you can switch to the "Console" tab and with the Chrome api you are able to test the selector's content, so you can prepare it for your puppeteer script. E.g.: $('.parent2 > .insider > .mybutton').innerText should show the button text that you expected to click on, otherwise you need to change on the access, or you need to check if there are more elments with the same selector etc. This may helps to find more appropriate selectors.

element.value method is returning undefined

I have an ajax 'POST' method that sends the id input to a php file. For some reason whenever I write input.value method, it returns undefined:
input = document.getElementsByClassName("Input");
const id = input.value;
alert(id);
What am I doing wrong?
Edit: I tried making the element as a separate id instead of a class and the problem disappeared.
getElementsByClassName() returns an array-like collection of elements, not a single element.
You'll need to extract one of the elements from the collection, e.g.
input = document.getElementsByClassName("Input");
const id = input[0].value; //<--
alert(id);
Better would be to target the exact element in some way e.g.
document.querySelector('#theActualElement'); //<-- returns single element

Testcafe: How to grab the text not from html code (selector) but in field on UI

I need to extract parts of string from the text which was written in the field (input) on UI (This text is not in HTML code).
I am trying sth like this (but it does not work).
const textInput = await model.inputtTittle.textContent;
console.log(textInput)
Nothing return probably textContent take text from the selector, I was trying with .innerText but it also returned nothing.
And then I would like to write sth like this:
if (textInput.length > 32)
await t.typeText(model.inputTittle, textInput.substr(0, 30));
I hope that it will be work if I have the content of the field inputTittle.
Additional question:
This answer is hidden. This answer was deleted via review 16 hours ago by Jason Aller, Mark Rotteveel, Nico Haase, Botje.
This code works:
const textTittle = await model.inputTittle.value;
const textlength = textTittle.length
if (textlength>32)
{
console.log(textTittle.substr(0,30));
}
why i can not to writte shorter:
if (await model.inputTittle.value.length >32)
{ console.log(await model.inputTittle.value.substr(0,30));}
You can obtain the entire DOM Node Snapshot with all properties in one object to check what properties you need. It is likely you need the value property.

Get data-id from html element

I'm trying to extract the content of data-id.
For example :
<div data-id= "43434"></div>
How can I get the value of 43434? I want to get access to the content of data.
As I see you want to get this value inside a TestCafe test.
If so you can use the Selector.getAttribute() method.
const element = Selector('your-div-selector');
const attrValue = await element.getAttribute('data-id');
// or if you need to use it in an assertion
await t.expect(element.getAttribute('data-id')).eql('43434');
Get the element using has attribute selector and get the value from dataset property or get attribute value using Element#getAttribte method.
console.log(
document.querySelector('div[data-id]').dataset.id
)
<div data-id="43434"></div>

JQuery selectors - using html snippets as "context" in filter and find

A quick question about using context with Jquery selectors:
I'm trying to grab the text from a div element that has id="time". Can a HTML snippet be used as context in the following:
// An AJAX request here returns a HTML snippet "response":
var myTime = $("#time", response).text();
The reason I'm doing this is that I want the time variable from within the html held in response, but don't want the overhead of loading all of the html into the DOM first. (it's a large amount of html).
From the comments what I understand is the response is <span id="time">blah blah</span> which means the element time is the root variable itself, that is why the child lookup is not working.
var response = '<span id="time">blah blah</span>';
var myTime = $(response).text(); // Or $(response).filter("#time").text();
alert(myTime)
Demo: Fiddle
This method uses filter() rather than find(), the difference being:
filter() – search through the passed element set
find() – search through all the child elements only.
Did you try it?
$("#time", "<div><span id=time></span></div>")[0].id //returns 'time'
From the jQuery source code:
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
so valid selectors should work in the context parameter. Personally, I prefer using find to begin with because it keeps all the selectors in the same order instead of $("second > third", "first");