Write query to parse HTML DOCUMENT with HtmlAgilityPack - c#

I want to get the A href of that element in span class="floatClear" whose rating is minimum in
span class="star-img stars_4"
How can I use HtmlAgilityPack to achieve this behaviour I have give the html source of my file
<div class="businessresult"> //will repeat
<div class="rightcol">
<div class="rating">
<span class="star-img stars_4">
<img height="325" width="84" src="http://media1.px" alt="4.0 star rating" **title**="4.0 star rating">
</span>
</div>
</div>
<span class="floatClear">
<a class="ybtn btn-y-s" href="/writeareview/biz/KaBw8UEm8u6war_loc%NY">
</span>
</div>
The query I have written
var lowestreview =
from main in htmlDoc.DocumentNode.SelectNodes("//div[#class='rightcol']")
from rating in htmlDoc.DocumentNode.SelectNodes("//div[#class='rating']")
from ratingspan in htmlDoc.DocumentNode.SelectNodes("//span[#class='star-img stars_4']")
from floatClear in htmlDoc.DocumentNode.SelectNodes("//span[#class='floatClear']")
select new { Rate = ratingspan.InnerText, AHref = floatClear.InnerHtml };
But I do not know how to apply condition here at last line of LINQ query!

Don't select "rating" from the entire htmlDoc, select it from the previously found "main".
I guess you need something like:
var lowestreview =
from main in htmlDoc.DocumentNode.SelectNodes("//div[#class='rightcol']")
from rating in main.SelectNodes("//div[#class='rating']")
from ratingspan in rating.SelectNodes("//span[#class='star-img stars_4']")
from floatClear in ratingspan.SelectNodes("//span[#class='floatClear']")
select new { Rate = ratingspan.InnerText, AHref = floatClear.InnerHtml };
I hope it will not crash if some of those divs ans spans are not present: a previous version of the HtmlAgilityPack returned null instead of an empty list when the SelectNodes didn't find anything.
EDIT
You probably also need to change the "xpath query" for the inner selects: change the "//" into ".//" (extra . at the beginning) to signal that you really want a subnode. If the AgilityPack works the same as regular XML-XPath (I'm not 100% sure) then a "//" at the beginning will search from the root of the document, even if you specify it from a subnode. A ".//" will always search from the node you are searching from.
A main.SelectNodes("//div[#class='rating']") will (probably) also find <div class="rating">s outside the <div class="rightcol"> you found in the previous line.
A main.SelectNodes(".//div[#class='rating']") should fix that.

Related

Select item in angular selectbox using selenium

I am trying to make a selection in a dropdown using selenium.
The selectbox is not a html type of 'select' but is using angular so it is a 'mat-select' html-tag.
I simplified my code to get this to work but could not get it to work, this is my code right now:
var q = driver.FindElement(By.TagName("mat-select"));
new SelectElement(q).SelectByText("My List Value");
When I run this code I get:
Element should have been select but was mat-select
How do I solve this? I tried using SendKeys but since "My List Value" has spaces, the space triggers the selectbox to open or close and it won't select the correct value. Then I read about SelectByText but that seems to require a normal '' but I only have a ''.
Here is the html of the select:
<div class="mat-form-field-infix">
<mat-select _ngcontent-qpv-c46="" class="mat-select ng-tns-c12-118 ng-pristine ng-valid mat-select-empty ng-star-inserted ng-touched" role="listbox" id="mat-select-5" tabindex="0" aria-labelledby="mat-form-field-label-41" aria-required="false" aria-disabled="false" aria-invalid="false" aria-multiselectable="false">
<div class="mat-select-trigger" aria-hidden="true" cdk-overlay-origin="">
<div class="mat-select-value">
<!---->
<span class="mat-select-placeholder ng-tns-c12-118 ng-star-inserted"> </span>
<!---->
</div>
<div class="mat-select-arrow-wrapper">
<div class="mat-select-arrow"></div>
</div>
</div>
<!---->
</mat-select>
<span class="mat-form-field-label-wrapper">
<!---->
<label class="mat-form-field-label ng-tns-c24-117 mat-empty mat-form-field-empty ng-star-inserted" id="mat-form-field-label-41" for="mat-select-5" aria-owns="mat-select-5">
<!----><!---->
<mat-label _ngcontent-qpv-c46="" class="ng-star-inserted">Properties</mat-label>
<!----><!---->
</label>
</span>
</div>
All the options are missing in the html? I know c# but not angular so this looks a bit funny to me. Have not tried to click because I don't know how to find them when I can't even see them myself.
When I click on the select all items appear in some magical angular way...
The reason you got the error is because SelectElement() can only be used with an HTML SELECT element. The mat-select (and some other elements) may be formatted to look like a dropdown but they are not SELECT dropdown elements. Because of this, we aren't going to be able to use SelectElement() but there are ways around this.
The simplest way I've found is to find the mat-select element (typically by ID) and click it to open the dropdown. Then click the desired option using an XPath that contains the expected string. The code below shows this but the second locator is a guess because the HTML you provided didn't show the dropdown options. If you update the HTML to show this, I can update and test the locator but even if you don't, this should point you in the right direction.
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(ExpectedConditions.ElementToBeClickable(By.Id("mat-select-5"))).Click();
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath("//span[contains(text(),'My List Value')]"))).Click();
I added waits just to be safe. They may not be needed.
driver.FindElement(By.Id("mat-select-5")).Click();
driver.FindElement(By.XPath("//span[contains(text(),'My List Value')]")).Click();
I'm assuming you are going to use this more than once. In that case, I would write a method that takes in the desired option and selects it.
public void SelectProperty(string propertyName)
{
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(ExpectedConditions.ElementToBeClickable(By.Id("mat-select-5"))).Click();
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath($"//span[contains(text(),'{propertyName}')]"))).Click();
}
and call it like...
SelectProperty("My List Value");
Selenium doesn't provide methods for interacting with mat-select. You need to write a custom program for Selenium to interact with a mat-select.
Here is an example of how to select a mat-select option with Selenium using C#:
IWebElement field = driver.FindElement(By.cssSelector('mat-select'));
// Click to open the dropdown.
field.Click();
// Query for options in the DOM. These exist outside of the mat-select component.
IReadOnlyList<WebElement> options = driver.FindElements(By.cssSelector("mat-option"));
// Find the option with the text that matches the one you are looking for.
options.First(element => element.GetText() == "My List Value")
// Click it to select it.
.Click();
You will probably want to wrap this up in a re-usable method of some sort.

How to get xpath of different html values with the same properties

I'm working on Selenium and trying to get the values inside tags. The site that I'm working on is https://www.qnbfinansbank.enpara.com/doviz-kur-bilgileri/doviz-altin-kurlari.aspx. But the properties of the objects are the same. Therefore, the xpath scripts are the same. The values that I'm trying to get are like 5,615505 TL, 4,827450 TL, 187,389825 TL from
<div class="dlCont">
<span>5,615505 TL </span>
</div>
<div class="dlCont">
<span>4,827450 TL </span>
</div>
<div class="dlCont">
<span>187,389825 TL </span>
</div>
and so on. Is there any way to get the xpath of these values?
You can store all the values in a List. Then one by one you can retrieve it.
Something like :
IList<IWebElement> allValues= driver.FindElements(By.CssSelector("div.dlCont span"));
foreach (IWebElement values in allValues)
{
Console.WriteLine(values.Text);
}
Hope this will help.
You can use like this,
//span[contains(text(),'5,615505 TL')]
You can manually write the xpath for the below DOM Structure
<div class="dlCont">
<span>5,615505 TL </span>
</div>
Manually written xpath for above DOM structure is "//div[#class='dlCont']/span".
if the page is having many elements with same DOM struture then written Xpath will match with all the nodes.
There are 8 nodes are matched with XPATH="//div[#class='dlCont']/span" in the below URL https://www.qnbfinansbank.enpara.com/doviz-kur-bilgileri/doviz-altin-kurlari.aspx
if you want to fetch particular webelements then you need to specify the index value as "(//div[#class='dlCont']/span)[2]".
you need to add open bracket in the starting of the manually written xpath and close bracket in the ending of the Xpath.after that you need to mention the index value
1.//div[#class='dlCont']/span
2.(//div[#class='dlCont']/span
3.(//div[#class='dlCont']/span)
4.(//div[#class='dlCont']/span)[1]
Hope it will be helpful

How to sendkeys to a <p> tag through C# and Selenium

i want to sendkeys "description" within a textarea. I have tried all the possible ways but does not work.
HTML of the element :
<div class="ta-scroll-window ng-scope ta-text ta-editor form-control" ng-hide="showHtml">
<div class="popover fade bottom" style="max-width: none; width: 305px;">
<div class="arrow"></div>
<div class="popover-content"></div>
</div>
<div class="ta-resizer-handle-overlay">
<div class="ta-resizer-handle-background"></div>
<div class="ta-resizer-handle-corner ta-resizer-handle-corner-tl"></div>
<div class="ta-resizer-handle-corner ta-resizer-handle-corner-tr"></div>
<div class="ta-resizer-handle-corner ta-resizer-handle-corner-bl"></div>
<div class="ta-resizer-handle-corner ta-resizer-handle-corner-br"></div>
<div class="ta-resizer-handle-info"></div>
</div>
<div id="taTextElement737852736512107" contenteditable="true" ta-bind="ta-bind" ng-model="html" ta-keep-styles="true" class="ng-pristine ng-valid ta-bind ng-empty ng-touched" an-form-object-name="Açıklama" name="Açıklama">
<p>
<br>
</p>
</div>
</div>
Code trial :
Dim action2 = New Actions(driver)
Dim cekbul2 = driver.FindElement(By.XPath("//*#id=""taHtmlElement737852736512107""]"))
cekbul2.SendKeys("Açıklama")
Console.Write("textarea send description")
or
Dim cekbul2 = driver.FindElement(By.XPath("//textarea[#class='ng-pristine ng-untouched ng-valid ng-scope ta-bind ta-html ta-editor form-control ng-empty ng-hide' and #id='taHtmlElement737852736512107']"))
The error is :
"no such element: Unable to locate element does not work" give error
Your html does not have a text area input field inside it.
When you use an xPath that says
'//textarea' this means that you are looking for an element that has tags of <textarea> </textarea>
It looks like your html is actually div's that are styled up to look like text areas.
That is why your second attempt will never work - because you are looking for a textarea where none exists.
Typically, in the situation where a div is styled up to work like a text area or textbox, you will find that the div has a backing input behind it.
These must be located between the
<form> and </form> tags in the html - otherwise the server would never be able to receive the data. (Html 5 provides new ways of working with this - but that is another story)
Can you examine your full html, and see if you can find the actual text area objects or the input type objects that end up containing the text content.
Type some dummy text, and use an html inspector tool within chrome or firefox to look for your dummy text.
If however, the post is completed by javascript - you may find that the javascript does not use inputs or text areas for containing the text and instead posts it external to any form elements. This is common with richtext emulators such as forum post pages.
If that is the case- you may need to experiment and find the appropriate html element that you need to send keys to in order for the content to work.
Also - could you try
Dim cekbul2 = driver.FindElement(By.XPath("//div[#id='taHtmlElement737852736512107']"))
I couldnt help but notice it had an xPath syntax error - you had no starting [ square bracket ] - also, in programming it is sometimes considered lazy a bad practice to wildcard / work with dynamics. I recommend always using the tag type for your xpaths, as opposed to '//*'
Worse case scenario, I would say that you could probably get around this by using Javascript execution. Eg: Directly setting the text, instead of 'sending the key strokes'.
However, this does not emulate human behavior - but it may be a necessary evil depending on your situation.
To send text to the <p> tag you have to use the ExecuteScript() method from IJavaScriptExecutor Interface and you can use the following code block :
((IJavaScriptExecutor)driver).ExecuteScript("document.getElementsByTagName("p")[0].innerHTML="Hasan Sarıkaya";");
I want to highlight some points here
Most probably your locator which you are using is not correct.
There are three way which I know to enter text using selenium
1)Use driver.findElement(yourLoator).sendKeys("Stringvalue");
2)You can use action class to send keys
3)You can use javascript executor to change innerHtml code
Personally ill not prefer the third solution, because we are testers I believe changing dom attribute is a good practice
Hope this will give you some help. please Let me know in case any query.

How to get values of same class/properties in Selenium C#?

I am currently automating tests to compare expected and actual endorsements on a summary page.
How can I read all endorsements values on the page shown on the summary page.These can change, meaning there can be 2-5 depending on different input. I have tried Xpath and CSS selector but have had no luck. Here are the elements properties for two endorsements the rest of the endorsements will have same properties (elements wise) just different values.
I want to be able to get all the endorsements listed on the page so I can input to my excel sheet for comparison against expected endorsement.
ENDORSEMENT 1:
<div class="guidance smaller ng-scope" ng-repeat="end in
prop.Endorsements">
<a ng-href="#c03770af-3724-4c3a-a240-e341c0d2c3ef" ng-bind-
html="end.Name" class="ng-binding" href="#c03770af-3724-4c3a-
a240-e341c0d2c3ef">Restricted Theft</a>
</div>
<a ng-href="#c03770af-3724-4c3a-a240-e341c0d2c3ef" ng-bind-
html="end.Name" class="ng-binding" href="#c03770af-3724-4c3a-a240-
e341c0d2c3ef">Restricted Theft</a>
ENDORSEMENT 2:
<div class="guidance smaller ng-scope" ng-repeat="end in
prop.Endorsements">
<a ng-href="#93ff9067-f64c-4879-933d-8b0a1d077e74" ng-bind-
html="end.Name" class="ng-binding" href="#93ff9067-f64c-4879-933d-
8b0a1d077e74">Malicious Damage Exclusion</a>
</div>
<a ng-href="#93ff9067-f64c-4879-933d-8b0a1d077e74" ng-bind-
html="end.Name" class="ng-binding" href="#93ff9067-f64c-4879-933d-
8b0a1d077e74">Malicious Damage Exclusion</a>
You need a XPath expression to catch all the a elements at once and store them in a list.
When there are no other anchor tags then the Endorsements:
IList<IWebElement> listOfEndorsements= Driver.FindElements(By.XPath("//a"));
When there are other kind of anchor tags you can try:
IList<IWebElement> listOfEndorsements= Driver.FindElements(By.XPath("//div[contains(#ng-repeat,'prop.Endorsements')]/a"));
Then you can use a ForEach loop to extract from the list of IWebElements the information you need.Like:
foreach (var endorsement in listOfEndorsements)
{
var text = endorsement.Text;
}

how to ignore double white-spaces during text matching using Xpath

I have a html code like this:
<div class="main">
<div class ="first">
<p>just text</p>
</div>
<div class= "second">
<p>some text</p>
</div>
<div class= "third">
<p>some text having double white-space</p>
</div>
</div>
and use Xpath like this: //div/p[contains(text(),'some text')]
But unfortunately in any place of the "some text" inside element p can be double white-spaces. So I need to ignore them during this matching. I know I can use Xpath expression like this translate(normalize-space(//div/p), ' ', '') but it will find all elements p and just replace double white-spaces without matching "some text"!
Is it possible to match "some text" but ignore double white-spaces in the same time?
When selecting a set of nodes using XPath 1.0, the XPath can't change the nodes that are returned in the result. You can only select nodes as they already are. You can use the following to ignore the double spaces when doing the selection:
//div/p[contains(normalize-space(), 'some text')]
This will return the set of ps that you are looking for, but their text content will be kept as it originally was. If you then want to obtain the text values without the duplicate spaces, you can iterate through this node set and strip out the spaces from the values one by one. You haven't told us anything about the code you're using to carry out these queries, so it's hard to tell you precisely how you would modify your code to do this. If you can show us your code, I can show you how to get it to do what you need.

Categories

Resources