To collect some test data for a neural net in C# I want to use Selenium to scrape some dynamically generated data WSJ. There is an example implementation on the Selenium site that seems to do exactly what I need Finding all the input elements to the every label on a page. The example searches on TagName, I search on ClassName, but other than that, I think it's identical.
Yet, when I run this code, creating an IList with IWebElements works, but the following IJavaScriptExecutor throws an Invalid Cast exception:
Unable to cast object of type
System.Collections.ObjectModel.ReadOnlyCollection 1[System.Object]
to type
System.Collections.Generic.IList 1[OpenQA.Selenium.IWebElement]
Here's a bit of the code, this is for "text", I do the same for "num":
// Find elements by class name
IList<IWebElement> labels = driver.FindElements(By.ClassName("text"));
// get all input elements for every class label
IList<IWebElement> labVals = (IList<IWebElement>)((IJavaScriptExecutor)driver).ExecuteScript(
"var labels = arguments[0], labVals = []; for (var i=0; i < labels.length; i++){" +
"labVals.push(document.getElementById(labels[i].getAttribute('for'))); } return labVals;", labels);
I have looked at this question Selenium Web Driver C# InvalidCastException which may point to the same problem, but I don't see how the answers provided can help me.
An option could be to break up the IJavaScriptExecutor statement into "discrete" code with a work around, but I would not know how to do that.
Once I have the text labels and data values both in a List structure, I should be able to find the numbers I need.
This is not using javascript, but it will work.
I would use a CssSelector method that receives throught parameters which column/row you need, and then you would be calling this method with loop to get all info from the page.
Checking the css of the page this is what I get from the first column/row
table.mdcTable > tbody > tr:nth-of-type(3) > td:nth-of-type(1)
So, the number "3" is related to the first row, and "1" is the first column. So we can make a method to return the exact element you want:
public IWebElement test(int line, int row)
{
return driver.FindElement(By.CssSelector(string.Format("table.mdcTable > tbody > tr:nth-of-type({}) > td:nth-of-type({})", line + 2, row)));
}
Calling this method will return the element that has the text, so all you need to do is use 'element.Text' to the the value of the 'cell', or make the method return the text directly.
public String test(int line, int row)
{
return driver.FindElement(By.CssSelector(string.Format("table.mdcTable > tbody > tr:nth-of-type({}) > td:nth-of-type({})", line + 2, row))).Text;
}
The only problem would be with the columns "Latest", because they do not only contain the numbers, but a bar. You would have to create a method to take care only of these column.
This would end up with something like this:
try
{
int line = 1;
int column = 1;
while(column <= 7)
valueOfTheCell = test(line, column);
getLatestGreen(line); //string.Format("tbody > tr:nth-of-type({0}) > td:nth-of-type(9) > span.text", line)
getLatestRed(line); //string.Format("tbody > tr:nth-of-type({0}) > td:nth-of-type(8) > span.text > b", line)
}
catch (NoSuchElementException)
{
//Exception will be thrown when the code reaches the end of the list
}
I won't say this is optimal, but it is an option.
If you want to do this way I can help you with any question or problem about how to use the selector.
The cast error occurs because the IJavascriptExecutor outputs the general System.Object class MSDN which I then try to cast to an IWebElement. This may work in some cases, but in this case it does not. Changing the receiving IList to IList<Object> solves the cast exception. With this the code runs, and then I found out with the debugger that all the data is captured with the first part of the code in the Labels list. The IJavaScriptExecutor returns null items only. So the second step is not required in my case.
Related
I want to validate that an expectedresult is on this page, but I keep getting an error message. Any help is appreciated.
Here is the code I am running:
string ActualResult;
string ExpectedResult = "nH-brand-logo";
IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl(" ");
driver.Manage().Window.Maximize();
driver.FindElement(By.Id("user_email")).SendKeys(" ");
driver.FindElement(By.Id("user_password")).SendKeys(" ");
driver.FindElement(By.Id("user_submit")).Click();
ActualResult = driver.FindElements(By.ClassName("nH-brand-logo"));
if (ActualResult.Contains(ExpectedResult))
{
Console.WriteLine("Test Case Passed");
}
else
{
Console.WriteLine("Test Case Failed");
}
driver.Close();
driver.Quit();
}
}
}
The code you’ve posted won’t compile, because you’ve declared ActualResult as a variable of type string, and FindElements returns a ReadOnlyCollection<IWebElement>, which is most decidedly not a string.
If you’re looking to find any element on the page where the class attribute contains "nH-brand-logo", then the following would be sufficient:
var elements = driver.FindElements(By.ClassName("nH-brand-logo"));
bool testPassed = elements.Count > 0;
By calling the FindElements method in this way, if there are any elements returned, they each must, by definition, contain that value in their class attribute. Now, if you’re looking to validate something more complex than this, the full solution may be proportionally more complex.
Things to remember when working with Selenium:
FindElement finds the element or throws an exception. If the element doesn’t exist, it does not return null or an empty value; it throws.
FindElements returns all matching elements, or an empty list when no elements meet the find criteria. However, you do have to access the elements in the returned list to get information about any single element found.
The line
ActualResult = driver.FindElements(By.ClassName("nH-brand-logo"));
will return a list of webElements, which you are then validating against a string. Hence the error.
string ActualResult;
string ExpectedResult = "nH-brand-logo";
if (ActualResult.Contains(ExpectedResult))
if you want to check if all the elements for ActualResults contains 'nH-brand-logo', then you can write
bool result = ActualResults.All(x=>x.GetAttribute("class").Contains('nH-brand-logo');
Hope this helps.
This line code returns number of elements.
document.getElementsByClassName("entry entryWriteable");-> returns 70 elements
I want to implement a loop so that, Below line of code will execute for all the element.
document.getElementsByClassName("entry entryWriteable")[i].value;
Can any one help me how to impliment in C# selenium ?
In order to execute JS in selenium on C# you should use the next code:
((IJavaScriptExecutor) Driver).ExecuteScript("your code"));
So you can execute any JS code you want.
The ExecuteScript returns object so probably you can typify it.
Like Denis said, but if you need a return value of all values in entire element array, then try:
using OpenQa.Selenium.Webdriver.Extensions
driver.ExecuteJavascript<string>(`
var els = document.getElementsByClassName("entry entryWriteable");
string returnAllElementTexts = "";
for(var i = 0; i < els.length; i++) {
returnAllElementTexts += els[i] + "|";
}
return returnAllElementText;`);
This will return a pipe-delimited string of all values. Split on pipe from C#. Is that what you wanted? All text values from array of elements?
I have an array of textboxes in which they change dyanmically depending on what the user types in. Those textboxes contain a number which represents a score of an assignment. Those score are linked to a module object. So if the user has 3 modules; 2 assignments on the first and second module and 3 assignments on the third module; then in total there would be 7 textboxes created for the user to input all their assignment marks.
What I am trying to do is to create a keyup event handler in which it gets the number in typed in by the user, and then dynamically calls a method to display the average of the the module. This is what I have so far. The following method gets called whenever the user types in a character:
public void calculateLevel4Modules(int counter) {
//iterate through modules
//iterate through assignts in that module
//whilst iterating, check tb and set userscore
//after iterating, update overall label with regards to modulecounter
//int assignmentCounter = 0;
//Console.WriteLine("in If statement.. " + counter);
for (int moduleCounter = 0; moduleCounter < requiredLevelList().Count; moduleCounter++)
{
int totalNumberOfAssignmentsInCurrentModule = requiredLevelList().ElementAt(moduleCounter).Assignments.Count;
Console.WriteLine("total number of assignmetns: " + totalNumberOfAssignmentsInCurrentModule);
assignmentCounter = assignmentCounter + totalNumberOfAssignmentsInCurrentModule;
Console.WriteLine("assignment counter: " + totalNumberOfAssignmentsInCurrentModule);
if (counter < assignmentCounter)
{
Console.WriteLine("in If statement.. " + userMarksTBLvl4[moduleCounter].Text);
try
{
int userMark = int.Parse(userMarksTBLvl4[counter].Text);
requiredLevelList().ElementAt(moduleCounter).Assignments.ElementAt(counter).UsersScore = userMark;
double modAvg = requiredLevelList().ElementAt(moduleCounter).getModuleScoreOverall();
moduleOverallLvl4[moduleCounter].Text = modAvg.ToString();
break;
}
catch (FormatException) { break; }
}
else { }
}
it works fine if the user has one module but if the user has two or more, then I get an error in the following line:
requiredLevelList().ElementAt(moduleCounter).Assignments.ElementAt(counter).UsersScore = userMark;
I am getting an out of bounds exception. I know why; its because counter is basically the # of the textbox that was typed into but by me using counter, I am accessing something not within the assignments list. This is an example of when the problem occus:
The user has 2 modules. In each module there are 2 assignments thus 4 textboxes are been created with their index ranging from 0 - 3. If the user wants to type in their score of the first assignment on the second module, its basically trying to write to the third index in that element then it crashes since that module only consist of 2 assignments.
There are some strange things in your code that make it hard to answer. First, the code you posted doesn't compile, so we have no way to test it.
Several times you use code like:
requiredLevelList().ElementAt(moduleCounter)
I assume requiredLevelList is a method that returns a list of things. There is no reason to assume requiredLevelList returns the same list, or even lists with the same number of elements, each time you call it. Maybe it does in your particular case, but this is a dangerous thing to rely on. You should use a construct like:
foreach (var module in requiredLevelList())
{
int totalNumberOfAssignmentsInCurrentModule = module.Assignments.Count;
...
module.Assignments.ElementAt(counter).UsersScore = userMark;
...
}
Code like this:
Console.WriteLine("total number of assignmetns: " + totalNumberOfAssignmentsInCurrentModule);
is symptomatic of trying to debug something after it has crashed. That is extremely inefficient. Learn how to use a debugger; you will not become an effective programmer until you know how to do this.
requiredLevelList().ElementAt(moduleCounter).Assignments.ElementAt(counter).UsersScore = userMark;
You're probably getting an out-of-bounds exception here because counter is outside the indexes of Assignments. Since you never initialize or change counter, I have no way to know what it is or should be. A debugger will tell you this, use one.
the # of the textbox that was typed into but by me using counter, I am accessing something not within the assignments list.
OK, if you're typing something “not within the assignments list” then you have to test for that and decide what to do. Perhaps something like:
if (counter >= 0 && counter < module.Assignments.Count)
module.Assignments.ElementAt(counter).UsersScore = userMark;
else
throw new Exception("I really have no idea what you want to do here.");
This also looks wrong:
moduleOverallLvl4[moduleCounter].Text = modAvg.ToString();
You never tell us what moduleOverallLvl4 is, but here you're assuming it has the same size as what is returned by requiredLevelList(). Maybe they are in this particular case, but that is a dangerous assumption. If these values are related, moduleOverallLvl4 should be contained in whatever class implements requiredLevelList, and you should have a method that assigns getModuleScoreOverall() to the correct element of moduleOverallLvl4.
I'm trying to implement a previous and next buttons.
I have a list of string called list1 which is filled with whatever is a user has inputted from a textbox. I can't access the previous (not the last) string in the list. IndexOf method isn't useful as I don't know what user will input.
private void previousBtn_click(object sender, EventArgs e)
{
getList();
int min = 0;
int max = list1.Count;
if(max==min)
{
previousBtn.Visible = false;
}
else
{
int temp =list.Count-1;
//how do I get my string if I know the element index from the previous line?
//textbox1.Text = thatPreviousString;
}
}
Sorry, it should be easy but I can't figure it out. How can I actully get my previous string in the list if the value is kind of unknown to me, so I can't just use find() and indexOf.
MSDN shows that there is a property called Item but there is no proper tutorial or code bit that shows how to use it.
UPDATE:
Let's say the user has typed "www.google.com", "www.facebook.com", "twitter.com" and then "www.yahoo.com". This urls are saved in list1. The last one was "www.yahoo.com", I can get it by calling Last(). The user can press the previous button anytime, so I can't specify the number of elements in list1, it's growing dynamically. I can only get the number of elements by calling
list1.Count and the last index by calling list1[list1.Count-1]
Now I know the number of indexes and elements, so how do I get the previous string, e.g. "www.twitter.com", if I can only say I can give you the index, give my string back?
By the way, ElementAs is only for arrays, doesn't work for lists.
how do I get my string if I know the element index from the previous line?
int prevIndex; // element index from the previous line that you know
string temp = list1[prevIndex - 1];
string temp =list[list.Count-1]; will give you the last element in the list.
string temp =list[list.Count-2]; will give you the previous element.
Remember, lists are 0-indexed, ie the first element is accessed via [0], so the last element will be [list size - 1].
So in your case textbox1.Text = list[list.Count-2]; will write the previous string into the textbox.
However, that won't give you a proper previous functionality. Pressing previous again won't give you list[list.Count-3]. You could though have a currentIndex variable that you decrement whenever previous is pressed for example and do textbox1.Text = list[currentIndex].
I love LINQ statements for the expressive syntax and other convenient features. However, I find it very troublesome to debug them sometimes. Specifically, when I run a LINQ statement on a collection and one of the elements in the collection causes an exception, how can I figure out what the problem input was and where the problem came from?
Imagine I have a text file with 1000 real numbers:
0.46578
12.314213
1.444876
...
I am reading this as a List<string> and loading it into a more specific data structure:
var file_contents = File.ReadAllLines("myfile.txt");
var data = file_contents.Select(s => double.Parse(s));
Now, for this particular input, I didn't bother to look at it carefully and it turns out the 876th line contains (line numbers shown):
875 5.56786450
876 Error: Could not calculate value.
878 0.0316213
For whatever reason (perhaps the file was generated by a script that malfunctioned). My LINQ method chain will of course throw an exception. The problem is, how do I figure which element of the list caused the exception, and what its value was?
To clarify, if instead I used a for-loop:
var data = new List<double>();
foreach(var row in file_contents)
{
var d = double.Parse(row);
data.Add(d);
}
Then the exception would highlight the string which calls double.Parse, and I would be able to mouse over row to easily see what the problem input was.
I can, of course, use Resharper to convert my LINQ statements into for-loops, and then debug them, but is there a better way?
Put a conditional breakpoint on the lambda function, where the condition is s.StartsWith("5.56"). You just need to have your cursor on the lambda and press F9. Assuming you're using visual studio.
var data = file_contents.Select(s => {
try
{
return double.Parse(s);
}
catch
{
throw; //breakpoint?
}
});
Disclaimer: I work for OzCode
LINQ debugging is hard borderline impossible using Visual Studio. I suggest you try using OzCode.
This is what your code looks when debugging (the exception in on the 6th item).
You can tell which item caused the exception by investigating the items that where passed to the Select clause - and since the last one triggered the exception - it's easy to find the offending value.
If you're interested you can try OzCode's LINQ debugging - we've just started an EAP
I would just use a tryparse personally.
var data = new List<string>
{
"0.46578",
"12.314213",
"Error: Could not calculate value.",
"1.444876",
};
double d;
var good = data.Where(s => Double.TryParse(s, out d)).Select(Double.Parse);
var bad = data.Where(s => !Double.TryParse(s, out d)).Select(x => new
{
key = data.IndexOf(x),
value = x
}).ToDictionary(x => x.key, x => x.value);
textBox1.AppendTextAddNewLine("Good Data:");
WriteDataToTextBox(good);
textBox1.AppendTextAddNewLine(String.Format("{0}{0}Bad Data:", Environment.NewLine));
WriteDataToTextBox(bad);
The AppendTextAddNewLine is simply an extension method I wrote for my little proof of concept test program
public static void AppendTextAddNewLine(this TextBox textBox, string textToAppend)
{
textBox.AppendText(textToAppend + Environment.NewLine);
}
Edit
The WriteDataToTextbox is a generic method that writes an IEnumerble<T> out to the text box.
void WriteDataToTextBox<T>(IEnumerable<T> data )
{
foreach (var row in data)
{
textBox1.AppendTextAddNewLine(row.ToString());
}
}
Forgot to put the output here so I figure I should do that. It shows the index of the bad data and the data itself that caused the problem.
Good Data:
0.46578
12.314213
1.444876
Bad Data:
[2, Error: Could not calculate value.]
I'm not sure why you don't like foreach loop here. LINQ uses it internally anyway, and as you've already realized there are some pros and cons of using LINQ and debugging is one of cons.
I would probably mix LINQ with foreach and end up with following:
// read all lines from file //
var file_contents = File.ReadAllLines("myfile.txt");
// set initial data list length to number of lines for better performance
var data = new List<double>(file_contents.Length);
// list for incorrect line numbers
var incorrectRows = new List<int>();
foreach (var x in file_contents.Select((s, i) => new {s, i}))
{
// x.s - line string
// x.i - line number
double value;
if (double.TryParse(x.s, out value))
data.Add(value); // add value, which was OK
else
incorrectRows.Add(x.i); // add index of incorrect value
}
That will prevent an exception at all and will give you line numbers for all incorrect values. It also iterate over file_contents just once and every value is being parsed only once.