I am using v2.52 of Selenium and the WebDriver with C#. What I am trying to achieve should be fairly simple, but I am unable to find the solution for: I'd like to find an element by multiple By-criteria.
Let's say, I have something like this:
Click me!
Click me!
<p class="foo">Click me!</p>
Ignore the fact, that I could use By.CssSelector, By.CssSelector, By.XPath and so on. I was assuming that it should be possible to do something like this:
driver.FindElement(By.TagName("a").ClassName("foo"))
// ...or something like this...
driver.FindElement(By.TagName("a"), By.ClassName("foo"))
OpenQA.Selenium.Support.PageObjects.ByChained does not do the trick, since it is searching hierarchical.
Is there a way to find element(s) which match multiple By-criteria?
Best regards,
Carsten
I imagine something like this may work for your case:
public IWebElement FindElementByMultipleCriteria(List<By> Criteria, IReadOnlyCollection<IWebElement> toFilter = null)
{
// If we've reached the end of the criteria list, return the first element:
if (Criteria.Count == 0 && toFilter != null) return toFilter.ElementAt(0);
// Take the head of the criteria list
By currentCriteria = Criteria[0];
Criteria.RemoveAt(0);
// If no list of elements found exists, we get all elements from the current criteria:
if (toFilter == null)
{
toFilter = Driver.FindElements(currentCriteria);
}
// If a list does exist, we must filter out the ones that aren't found by the current criteria:
else
{
List<IWebElement> newFilter = new List<IWebElement>();
foreach(IWebElement e in Driver.FindElements(currentCriteria))
{
if (toFilter.Contains(e)) newFilter.Add(e);
}
toFilter = newFilter.AsReadOnly();
}
// Pass in the refined criteria and list of elements found.
return FindElementByMultipleCriteria(Criteria, toFilter);
}
IWebElement example = FindElementByMultipleCriteria(new List<By>(){ By.TagName("a"), ClassName("foo") });
Essentially, you're taking the list of elements found by the first By passed in. You are then going through the remaining By criteria and removing elements from that initial list that are never found again.
This is horribly inefficient and I don't really understand why you would want to do this, but it exists.
Oh also, you'll need to add using System.Linq; in order to interact with the IReadOnlyCollection.
Well I think you can try something like this, and let me know if it works:
public IWebElement FindElementUsingNestedBy(By firstCriteria, By secondCriteria)
{
var allElements = Driver.FindElements(firstCriteria);
foreach (var webElement in allElements)
{
try
{
var desiredObject = webElement.FindElement(secondCriteria);
return desiredObject;
}
catch (NotFoundException ex)
{
}
}
return null;
}
Related
var links = new List<GeckoElement>();
foreach (var link in geckoWebBrowser1.Document.Links)
{
if (!String.IsNullOrEmpty(link.GetAttribute("href").ToString()))
links.Add(link);
}
}
I have this code to collect all links in the page, but I cant figure it out how can I filter to some specific links that starts with for example "ow.ly". Rest in the list should be ignored.
Tryed this but didnt seem to work
if (links.Count > 0)
{
if (links.Equals("ow.ly"))
{
}
}
When I debug if links equals ow.ly it shows 0 reults.
links is a a List<GeckoElement>. So it’s unlikely that the list equals to the string "ow.ly". Instead, you want to filter the list for items, which href property contains that text.
You could do that for example like this:
var owlyLinks = geckoWebBrowser1.Document.Links.Where(link =>
{
string hrefAttribute = link.GetAttribute("href").ToString();
return !string.IsNullOrEmpty(hrefAttribute) && hrefAttribute.Contains("ow.ly");
}).ToList();
You might want to adjust the check so that "ow.ly" needs to appear somewhere special instead of just somewhere inside the string. For example, you could parse the whole Url, and then check that the host name equals to ow.ly.
I'm looking for some advice on a good way to achieve my goal. I feel like my pseudo code below is redundant and that there must be a more efficient way of doing it. My question is there a better solutioj to this or more efficcient way? So here is the setup...
I have a class called Node which has two properties
class Node
{
bool favorite
string name
}
I have a list which contains around a thousand of these Nodes.
I want to give users three features..
A way to filter the list to just show favorites otherwise if favorites is false it displays the original list
Ability to search by string/name comparison
The ability for both the search and favorite to work in combination
below is my pseudo code - describes an approach, not ideal though. You can read the comments in the code to get the main gist.
// initial collection of nodes
list<Nodes> initialnodesList = [];
// list of nodes which are displayed in UI
list<Nodes> displayNodes = [];
public void FilterNodes()
{
list<Nodes> tempNodesList = [];
if (favoritesEnabled)
{
// collect favorites
foreach (n in initialnodesList)
if (n.favorite)
tempNodesList.add(n);
// search within favorites if needed and create new list
list<Nodes> searchedNodesList = [];
if (!isStringNullWhiteSpace(searchString))
{
foreach (n in tempNodesList)
if (n.name == searchString)
searchedNodesList.add(n);
displayNodes = searchedNodesList;
return;
}else{
return;
}
}
else
{
// search within initial node collection if needed and create new list
list<Nodes> searchedNodesList = [];
if (!isStringNullWhiteSpace(searchString))
{
foreach (n in initialnodesList)
if (n.name == searchString)
searchedNodesList.add(n);
displayNodes = searchedNodesList;
return;
}
// if search is not needed and favorites were not enabled then just return the original node collection
displayNodes = initialnodesList;
return;
}
}
You can optimize your code with linq statement to filter based on searchString and favorite option.
public List<Node> FilterNodes(bool seachFavorite, string searchString)
{
return initialnodesList.Where(l => (string.IsNullOrEmpty(searchString) || l.name.StartWith(searchString, StringComparison.OrdinalIgnoreCase)) && l.favorite == seachFavorite).ToList();
}
Also, optimize your code to look for search with StartWith, you can changed to Contains if you want search has to be done based on contains string search.
Have a look to the following example:
The first solution, using the foreach, works pretty well and easily. But I was trying to write it using Linq and I could not achieve this result. I made some attempts but no one succeeded.
I expect to find just one element.
The problem is not at runtime: I don't know very well the Linq sintax and so I don't know how to get the element called PlacedSelection (the foreach structure clarifies where I'm looking for it). Instead in my attempt I could get the PlacedCategory elements.. but I don't need this..
PlacedSelection ActualSelection = null;
foreach (var placedCategory in Model.Coupon.Categories)
{
foreach (PlacedSelection placedSelection in placedCategory.Value.Selections)
{
var pp = placedSelection.EventId;
if (pp == Model.EventId)
{
ActualSelection = placedSelection;
break;
}
}
}
//IEnumerable<KeyValuePair<string, PlacedCategory>> p = Model.Coupon.Categories(c => c.Value.Selections.Any(s=> s.EventId == Model.EventId));
It looks like you want:
PlacedSelection actualSelection = Model.Coupon.Categories
.SelectMany(cat => cat.Value.Selections)
.FirstOrDefault(selection => selection.EventId == Model.EventId);
Any would be used if you were trying to find the category, but you're trying to find the selection, by the looks of it.
Is it possible in dhtmlx to do a reverse lookup on an option list, i.e. if I know the ItemText can I use it to lookup the Id?
i.e. something like this if such a function existed
var t = this.getAllListOptionText("tbOptionList").indexOf(Name);
I want to get the id, so that I can preset the selection to a specific option.
I could potentially loop through all the options and look for the ItemText myself, however if something already exists that would be more elegant.
for (var i = 0; i < lsTags.length; i++) {
if (this.getListOptionText("tbOptionList", lsTags[i]) == Name) {
lOptionID = lsTags[i];
break;
}
}
There are dhtmlx methods forEachItem and forEachListOption, you can iterate items and use getItemText with a check.
I am trying to read a file and process using LINQ.
I have a exclude list where if i encounter certain words in the file, i should omit that line
my code is
string sCodeFile = #"C:\temp\allcode.lst";
List<string> sIgnoreList = new List<string>() { "foo.c", "foo1.c" };
var wordsPerLine = from line in File.ReadAllLines(sCodeFile)
let items = line.Split('\n')
where !line.Contains(sIgnoreList.ToString())
select line;
foreach (var item in wordsPerLine)
{
console.WriteLine(item);
}
My LST file looks like below
\voodoo\foo.c
\voodoo\voodoo.h
\voodoo\std.c
\voodoo\foo1.h
in the end i want only
\voodoo\voodoo.h
\voodoo\std.c
How can i process the ignored list in contains? with my above code i dont get the desired output for sure
can any one help?
regards,
Karthik
Revised my answer. The bug is that you're doing a ToString on the ignore list, which certainly will not work. You must check each item in the list, which can be done using something like this:
where !sIgnoreList.Any(ignore => line.Contains(ignore))
A curiosity: since the above lambda is just passing a value into a method that only take the value as a parameter, you can write this even more compact as a method group like this:
where !sIgnoreList.Any(line.Contains)
Try this.
string sCodeFile = #"C:\temp\allcode.lst";
List<string> sIgnoreList = new List<string>() { "foo.c", "foo1.c" };
var wordsPerLine = File.ReadAllLines(sCodeFile).Where(n =>
{
foreach (var ign in sIgnoreList)
{
if (n.IndexOf(ign) != -1)
return false;
}
return true;
});
It passes the current element (n) to a lambda function, which checks it against every element of the sIgnoreList. Returning false means the element is ignored, true means it's returned.
Change it to:
where !sIgnoreList.Contains(line)
You need to compare each single line and check that it doesn't exist in the ignore list.
That's why the Vladislav's answer did not work.
Here's the working solution:
var result = from line in File.ReadAllLines(codeFile)
where !ignoreList.Any(line.Contains)
select line;
The problem was you didn't want to check for the whole path and messed up words/lines part a bit.