I have the following code in C# using selenium:
private void SelectElementFromList(string label)
{
var xpathcount = selenium.GetXpathCount("//select");
for (int i = 1; i <= xpathcount; ++i)
{
string[] options;
try
{
options = selenium.GetSelectOptions("//select["+i+"]");
}
catch
{
continue;
}
foreach (string option in options)
{
if (option == label)
{
selenium.Select("//select[" + i + "]", "label=" + label);
return;
}
}
}
}
The problem is the line:
options = selenium.GetSelectOptions("//select["+i+"]");
When i == 1 this works, but when i > 1 the method return null ("ERROR: Element //select[2] not found"). It works only when i == 1.
I have also tried this code in JS:
var element = document.evaluate("//select[1]/option[1]/#value", document, null, XPathResult.ANY_TYPE, null);
alert(element.iterateNext());
var element = document.evaluate("//select[2]/option[1]/#value", document, null, XPathResult.ANY_TYPE, null);
alert(element.iterateNext());
Which print on the screen "[object Attr]" and then "null".
What am I doing wrong?
My goal is to iterate all "select" elements on the page and find the one with the specified label and select it.
This is the second most FAQ in XPath (the first being unprefixed names and default namespace.
In your code:
options = selenium.GetSelectOptions("//select["+i+"]");
An expression of the type is evaluated:
//select[position() =$someIndex]
which is a synonym for:
//select[$someIndex]
when it is known that $someIndex has an integer value.
However, by definition of the // XPath pseudo-operator,
//select[$k]
when $k is integer, means:
"Select all select elements in the document that are the $k-th select child of their parent."
When i == 1 this works, but when i > 1 the method return null ("ERROR:
Element //select[2] not found"). It works only when i == 1.
This simply means that in the XML document there is no element that has more than one select child.
This is a rule to remember: The [] XPath operator has higher precedence (priority) than the // pseudo-operator.
The solution: As always when we need to override the default precedence of operators, we must use brackets.
Change:
options = selenium.GetSelectOptions("//select["+i+"]");
to:
options = selenium.GetSelectOptions("(//select)["+i+"]");
Finally I've found a solution.
I've just replaced these lines
options = selenium.GetSelectOptions("//select["+i+"]");
selenium.Select("//select["+i+"]", "label="+label);
with these
options = selenium.GetSelectOptions("//descendant::select[" + i + "]");
selenium.Select("//descendant::select[" + i + "]", "label=" + label);
The above solution options = selenium.GetSelectOptions("(//select)["+i+"]"); doesn't worked for me but i tried to use css selectors.
I want to get username and password text box. I tried with css=input this gave me Username text box and when used css=input+input this gave me Password textbox.
along with this selectors you can use many things in combination.
here is the link from where i read.
I think this will help u to achieve your target.
Regards.
Related
I am trying to identify text nodes from an HTML text having a format like as below
sample text 1 : <strong>[Hot Water][Steam][Electric]</strong> Preheating Coil
sample text 2 : <b><span>[Steam] [Natural Gas Fired] [Electric] [Steam to steam]</span></b><span> Humidifier</span><br>
using the below code
public static string IdentifyHTMLTagsAndRemove(string htmlText)
{
_ = htmlText ?? throw new ArgumentNullException(nameof(htmlText));
var document = new HtmlDocument();
document.LoadHtml(htmlText);
var rootNode = document.DocumentNode;
// get first and last text nodes
var nonEmptyTextNodes = rootNode.SelectNodes("//text()[not(self::text())]") ?? new HtmlNodeCollection(null);
//if (nonEmptyTextNodes.Count == 0)
//{
// return rootNode.OuterHtml;
//}
if (nonEmptyTextNodes.Count > 0)
{
var firstTextNode = nonEmptyTextNodes[0];
var lastTextNode = nonEmptyTextNodes[^1];
// get all br nodes in html string,
var breakNodes = rootNode.SelectNodes("//br") ?? new HtmlNodeCollection(null);
var lastTextNodeLengthIndex = lastTextNode.OuterStartIndex + lastTextNode.OuterLength;
foreach (var breakNode in breakNodes)
{
if (breakNode == null)
continue;
// check index of br nodes against first and last text nodes
// and remove br nodes that sit outside text nodes
if (breakNode.OuterStartIndex <= firstTextNode.OuterStartIndex
|| breakNode.OuterStartIndex >= lastTextNodeLengthIndex)
{
breakNode.Remove();
}
}
}
return rootNode.OuterHtml;
}
But it is constantly failing here
var nonEmptyTextNodes =
rootNode.SelectNodes("//text()[not(self::text())]") ?? new
HtmlNodeCollection(null);
and nonEmptyTextNodes giving count as zero, I am unsure where I am doing wrong with the above code.
Could anyone please point me in the right direction? Many thanks in advance.
In addition to Siebe's answer, I'd also like to point out an inefficiency in the code that trims start/end BR tags. If you look at the HtmlAgilityPack code for HtmlNode operations, you'll see that whenever nodes are removed, the SetChanged() method is called on the parent (and its parent, all the way up). The next time you check the start/end indexes of anything in the tree, they need to be recalculated. So this code could be made to run much faster if you instead just create a temporary list of all the nodes to be removed, then remove them after they've all been identified.
var lastTextNodeLengthIndex = lastTextNode.OuterStartIndex + lastTextNode.OuterLength;
var breakNodesToRemove = rootNode.SelectNodes("//br")?.Where(node => node.OuterStartIndex <= firstTextNode.OuterStartIndex || node.OuterStartIndex >= lastTextNodeLengthIndex).ToList();
breakNodesToRemove?.ForEach(a => a.Remove());
reference: https://github.com/zzzprojects/html-agility-pack/blob/master/src/HtmlAgilityPack.Shared/HtmlNode.cs
Not sure what you are trying to achieve with
//text()[not(self::text())]
It tries to select text()-nodes that are not text()-nodes. So nothing will be found. If you just use
//text()
Will select all text()-nodes
I have a web page that contains numerous blocks with a text field, drop-down menus, etc.
I need a method, that evaluates the type of the web selector and does exact actions if the selector name matches a pattern.
I have a SpecFlow feature file
Scenario: Scenario name
When I do this
And I do that
And I do that
And I do that
And I filled in the form
| Field | Field | Field |
| value | value | value |
And the C# code
[When(#"I filled in the form")]
public void WhenFillTheForm(Table table)
{
for (int i = 0; i < table.Header.Count(); i++)
{
var elementName = table.Header.ElementAt(i);
var elementValue = table.Rows[0].Values.ElementAt(i);
var textField =
driver.FindElement(
By.XPath($"//label[contains(text(),'{elementName}')]//following::textarea[1]"));
textField.SendKeys(elementValue);
driver.WaitFor(0.1);
var dropDown =
driver.FindElement(By.XPath($"//label[contains(text(),'{elementName}')]/following::input[1]"));
dropDown.SendKeys(elementValue);
driver.WaitFor(2);
dropDown.SendKeys(Keys.Down);
dropDown.SendKeys(Keys.Enter);
}
HTML code of the textarea type element
<textarea data-v-29bdb854="" rows="2" name="combatExperience" autocomplete="off" class="el-textarea__inner form-control"></textarea>
HTML code of the input type element
<input type="text" readonly="readonly" autocomplete="off" placeholder="Choose" class="el-input__inner" wtx-context="A120D40C-D388-40CD-88DE-9CD2C5B270E0">
Both have the exact same locator body //label[contains(text(),'{elementName}')]//following::textarea[1] The only difference is the final part of a selector textarea and input.
This code repeats and fills unnecessary fields. I want to perform specific actions regardless of the name of the selector.
Instead of detecting differences in the XPath expression, you can use element.TagName to differentiate between <textarea> and <input> elements.
var xpath = "(//label[contains(text(),'{elementName}')]//following::textarea[1])"
+ "|"
+ "(//label[contains(text(),'{elementName}')]//following::input[1])";
var element = driver.FindElement(By.XPath(xpath));
if (element.TagName == "input")
{
// do something specific with an <input> element
}
else if (element.TagName == "textarea")
{
// do something specific with a <textarea> element
}
else
{
throw new Exception("Fix the XPath");
}
The | character is an "OR" operator. The XPath expression is basically (A)|(B) so it will match either a <textarea> or <input>. Without more guidance on what the HTML looks like or what you would like to do in each case, this is all I can provide.
Specflow step. Here add an XPath to a collection. Then we evaluate what action to perform with a specific locator. Fill or fill with additional steps. This test now reduces the manual testing job from 1h30m to 20 minutes.
[When(#"I filled in the form")]
public void WhenFillTheForm(Table table)
{
for (int i = 0; i < table.Header.Count(); i++)
{
var elementName = table.Header.ElementAt(i);
var elementValue = table.Rows[0].Values.ElementAt(i);
var xpathList = new List<string>();
var textAreaLocator = $"//label[contains(text(),'{elementName}')]//parent::div//following-sibling::textarea[1]";
var inputLocator = $"//label[contains(text(),'{elementName}')]//parent::div//following-sibling::input";
var textAreaLocatorWithoutParentNode = $"//label[contains(text(),'{elementName}')]//following::textarea[1]";
xpathList.Add(textAreaLocator);
xpathList.Add(inputLocator);
xpathList.Add(textAreaLocatorWithoutParentNode);
var element = driver.FindElementByAnyXpath(xpathList);
if (element.TagName == "input")
{
element.SendKeys(elementValue);
element.SendKeys(Keys.Down);
element.SendKeys(Keys.Enter);
}
else if (element.TagName == "textarea")
{
element.SendKeys(elementValue);
}
driver.WaitFor(0.1);
}
}
FindElementByAnyXpath method to find an XPath
public static IWebElement FindElementByAnyXpath(this IWebDriver driver, IEnumerable<string> xPathCollection)
{
foreach (var selector in xPathCollection)
{
try
{
var element = driver.FindElement(By.XPath(selector));
return element;
}
catch (NoSuchElementException e)
{
continue;
}
}
throw new NoSuchElementException();
}
First time doing this so I might be way off!
I've done something similar using a switch statement - hopefully this looks right.
switch (elementName)
{
case: "do this":
Do something
break;
case: "do that":
Do the other thing
break;
}
I'am new using C# and i have problem when using if inside "public object" method, this my code :
public object Login([FromBody] MailParameters data)
{
UmbracoDatabase db = ApplicationContext.DatabaseContext.Database;
var select = new Sql("SELECT UserID FROM Users where Email='" + data.Email + "';");
var ids = db.Fetch<listUsersChecks>(select);
if (ids)
{
var getByEncrypt = new Sql("SELECT * FROM Users where Email='" + data.Email + "' AND password='" + data.Password + "';");
var listue = db.Fetch<listUsers>(getByEncrypt);
}else{
var listue = "";
}
return listue;
}
the output is :
error CS0029: Cannot implicitly convert type 'System.Collections.Generic.List<LoginController.listUsersChecks>' to 'bool'
the error is in if(ids){ , how to solved this?
thanks
Look at the error message, if statement required a Boolean, but you feed in with a List. In this case, your ids is a list List<LoginController.listUsersChecks>
Since it's a list, you can check by counting number of item in this list:
if(ids.Count >0){} else{}
var ids = db.Fetch<listUsersChecks>(select);
This will give you a List<listUsersChecks> and an if condition needs a bool to evaluate if it should be executed or not.
If you want to execute the if statement when you have entries in your list you should use
if(ids.Count > 0)
{
//logic
}
Count is a property of List and gives the number of items in the list.
To make it even more clearer you could write this as well.
bool hasItems = ids.Count > 0;
if(hasItems)
{
//logic
}
You could use the Any method of LINQ as well.
Determines whether any element of a sequence exists or satisfies a
condition.
That would look like
if(ids.Any())
{
//logic
}
For more information have a look at 101 LINQ-Samples
not all your code paths return a value. you have to define your return object prior to the if statement and inside each branch just set it.and as for ids, it's not a valid boolean expression by itself, you may try ids.Any()
It looks like you are expecting C# to support "truthy" and "falsey" values like Javascript. In C#, there is no concept of "truthy" or "falsey". The expression in the if must evaluate to a boolean true or false. What you really want to do is use something like this Linq expression:
if(ids.Any())
{
...
}
From the above image the new data that I add gets added to the last page but there can be similar name and I need to verify the data using the ID as shown. So I am trying to figure out the way to store text values from the id and when the new data is added it should verify the last newly added ID. Any ideas?
int i = 1;
bool found = false;
string ID;
try
{
IWebElement LastPage = Driver.driver.FindElement(By.XPath("html/body/div[4]/div/div/div[2]/table/tbody/tr[8]/td[1]"));
LastPage.Click();
for (i = 1; i <= 10; i++) ;
{
ID= Driver.driver.FindElement(By.XPath("html/body/div[4]/div/div/div[2]/table/tbody/tr[" + i + "]/td[1]")).Text;
if (??)
}
As #viet-pham said, using last is a good idea, you can also use a relative xpath like:
//table/tbody/tr[last()-1]/td
and you don't need to use [1] since you are using FindElement and not FindElements and is returning the first element found.
You don't need to use for loop to get the last item, just put last() to select the final tr element
In your case:
IWebElement lastElement = Driver.driver.FindElement(By.XPath("(html/body/div[4]/div/div/div[2]/table/tbody/tr)[last()]/td[1]"));
ID = lastElement.Text;
In additional: because the last row is the total, so you must change to the row before it => [last()-1]
My question is very similar to this one XmlNode.SelectSingleNode syntax to search within a node in C#
I'm trying to use HTML Agility Pack to pull price/condition/ship price... Here's the URL I am scraping: http://www.amazon.com/gp/offer-listing/0470108541/ref=dp_olp_used?ie=UTF8&condition=all
Here's a snippet of my code:
string results = "";
var w = new HtmlWeb();
var doc = w.Load(url);
var nodes = doc.DocumentNode.SelectNodes("//div[#class='a-row a-spacing-medium olpOffer']");
if (nodes != null)
{
foreach (HtmlNode item in nodes)
{
var price = item.SelectSingleNode(".//span[#class='a-size-large a-color-price olpOfferPrice a-text-bold']").InnerText;
var condition = item.SelectSingleNode(".//h3[#class='a-spacing-small olpCondition']").InnerText;
var price_shipping = item.SelectSingleNode("//span[#class='olpShippingPrice']").InnerText;
results += "price " + price + " condition " + condition + " ship " + price_shipping + "\r\n";
}
}
return results;
No matter what combination I try of .// and . and ./ and / etc... I cannot get what I want (just now trying to learn xpaths), also currently it is returning just the 1st item over and over and over, just like the original question I referenced earlier. I think I'm missing a fundamental understanding of how selecting nodes work and/or what is considered a node.
UPDATE
Ok, I've changed the URL to point to a different book and the first two items are working as expected... When I try to change the third item (price_shipping) to a ".//" Absolutely no information is being pulled from anything. This must be due to sometime there is not even a shipping price and that span is omitted. How do I handle this? I tried if price_shipping !=null.
UPDATE
Solved. I removed the ".InnerText" from the price_shipping that causing issues when it was null... then I did the null check and Then it was safe to use .InnerText.
Solved. I removed the ".InnerText" from the price_shipping that causing issues when it was null... then I did the null check and Then it was safe to use .InnerText.