I am trying to fetch the Text of All table rows in a web page using Selenium C#.
Below is the code for retriving all tr's from the web page:
var trElements = driver.FindElements(By.TagName("tr"));
This shows the data correctly. For example, tr element number 35 has text 'canara bank' in it, that can be seen in below image.
Now I am trying to extract only text of all the tr elements. Either by using LINQ or by using for loop:
string[] strrr= trElements.Select(t => t.Text).ToArray();
Surprisingly, Text property of most of the element does not show up the data that was shown in web element. Randomly data of some elements keeps showing up or goes off.
I want to ensure that data of web elements is correctly converted to string array. How to achieve this?
I think there are 3 possibilities.
1. The rows are not visible. So element.Text can't give you the text. In this case, you need to use element.GetAttribute("innerText") instead of element.Text.
string[] strrr = trElements.Select(t => t.GetAttribute("innerText")).ToArray();
2. The script does not have enough wait time. In this case, you just need to add wait to check text length.
var trElements = driver.FindElements(By.TagName("tr"));
List<string> strrr = new List<string>();
foreach (var tr in trElements)
{
IWait<IWebElement> wait = new DefaultWait<IWebElement>(tr);
wait.Timeout = TimeSpan.FromSeconds(10);
try
{
wait.Until(element => element.Text.Trim().Length > 1);
strrr.Add(element.Text.Trim());
}
catch (WebDriverTimeoutException)
{
strrr.Add("");
}
}
3. The text will be displayed when you scroll down.
int SCROLL_PAUSE_TIME = 1;
int SCROLL_LENGTH = 500;
var jsExecutor = driver as IJavaScriptExecutor;
int pageHeight = Int32.Parse((string)jsExecutor.ExecuteScript("return document.body.scrollHeight"));
int scrollPosition = 0;
while (scrollPosition < pageHeight)
{
scrollPosition = scrollPosition + SCROLL_LENGTH;
jsExecutor.ExecuteScript("window.scrollTo(0, " + scrollPosition + ");");
System.Threading.Thread.Sleep(SCROLL_PAUSE_TIME);
}
var trElements = driver.FindElements(By.TagName("tr"));
Related
I am new to selenium coding, and I have the below code where I am fetching values from the table it has multiple pages,
for 1st time, it reads all values from the table and control move to the next page, I m getting the error stale element reference: element is not attached to the page document
but when i m debugging the code, i m not getting any error for the below code, when i run it throws an error and it shows an error at line where I have defined tdCollection
Please guide me on this.
var ReportCount = Convert.ToInt32(_driver.FindElement(By.Id("Reporter_TotalPages")).Text);
for (int i = 0; i < ReportCount; i++)
{
IList<IWebElement> _records = (IList<IWebElement>)_driver.FindElements(By.XPath("//*[contains(#id,'ReportViewerControl')]//div//table//tbody//tr[position()>2]"));
IList<IWebElement> tdCollection;
for (int j = 0; j < _records.Count; j++)
{
tdCollection = _records[j].FindElements(By.TagName("td"));
var Patientdemolist = new XPatientDemographicsList();
{
Patientdemolist.PatientID = tdCollection[0].Text;
Patientdemolist.LastName = tdCollection[1].Text;
Patientdemolist.FirstName = tdCollection[2].Text;
};
PatientDemographicsList.Add(Patientdemolist);
tdCollection = null;
}
if (ReportCount - 1 > i)
{
// For Next Page
_driver.FindElement(By.Id("Report_Next")).Click();
}
}
Try adjusting your conditional to this.
if (ReportCount - 1 > i)
{
// For Next Page
_driver.FindElement(By.Id("Report_Next")).Click();
Thread.Sleep(5000)
}
Its possible you are getting a reference before the page has completed loading from the .Click() method.
If that works you can refine the tests to wait implictly/ use fluent waits instead of waiting for 5 seconds.
https://www.selenium.dev/documentation/webdriver/waits/
I'm trying to pull all the values from another program's DataGridBox. For that I'm using FlaUi. I made a code that does what I want. However, it is very slow. Is there a faster way to pull up all the values from another program's DataGridView using FlaUi?
my code:
var desktop = automation.GetDesktop();
var window = desktop.FindFirstDescendant(cf => cf.ByName("History: NEWLIFE")).AsWindow();
var table = window.FindFirstDescendant(cf => cf.ByName("DataGridView")).AsDataGridView();
int rowscount = (table.FindAllChildren(cf => cf.ByProcessId(30572)).Length) - 2;
// Remove the last row if we have the "add" row
for (int i = 0; i < rowscount; i++)
{
string string1 = "Row " + i;
string string2 = "Symbol Row " + i;
var RowX = table.FindFirstDescendant(cf => cf.ByName(string1));
var SymbolRowX = RowX.FindFirstDescendant(cf => cf.ByName(string2));
SCAN.Add("" + SymbolRowX.Patterns.LegacyIAccessible.Pattern.Value);
}
var message = string.Join(Environment.NewLine, SCAN);
MessageBox.Show(message);
Thank you in-advance
Searching for descendants is pretty slow as it will go thru all objects in the tree until it finds the desired control (or there are no controls left). It might be much faster to use the grid pattern to find the desired cells or get all rows at once and loop thru them.
Alternatively you could try caching as UIA uses inter process calls which are generally slow. So each Find method or value property does such a call. If you have a large grid, that can sum up pretty badly. For that exact case, using UIA Caching could make sense.
For that, you would get everything you need (all descendants of the table and the LegacyIAccessible pattern) in one go inside a cache request and then loop thru those elements in the code with CachedChildren and such.
A simple example for this can be found at the FlaUI wiki at https://github.com/FlaUI/FlaUI/wiki/Caching:
var grid = <FindGrid>.AsGrid();
var cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Descendants;
cacheRequest.Add(Automation.PropertyLibrary.Element.Name);
using (cacheRequest.Activate())
{
var rows = _grid.Rows;
foreach (var row in rows)
{
foreach (var cell in row.CachedChildren)
{
Console.WriteLine(cell.Name);
}
}
}
I'm rendering a PDF document with MigraDoc.
Each section has one or more paragraph texts.
Currently this is how I create a document;
var document = new Document();
var pdfRenderer = new PdfDocumentRenderer(true);
pdfRenderer.Document = document;
for(int i=0;i<10;i++){
Section section = document.AddSection();
section.PageSetup.PageFormat = PageFormat.A4;
for(int j=0;j<5;j++) {
var paragraphText = GetParaText(i,j); // some large text can span multiple pages
section.AddParagraph(paragraphText);
//Want page count per section?
// Section 1 -> 5 , Section 2 ->3 etc.
// int count = CalculateCurrentPageCount(); //*EDIT*
}
}
// Create the PDF document
pdfRenderer.RenderDocument();
pdfRenderer.Save(filename);
Edit : Currently i use the following code to get the page count.
But it takes a lot of time ,possibly every page is rendered twice.
public int CalculateCurrentPageCount()
{
var tempDocument = document.Clone();
tempDocument.BindToRenderer(null);
var pdfRenderer = new PdfDocumentRenderer(true);
pdfRenderer.Document = tempDocument;
pdfRenderer.RenderDocument();
int count = pdfRenderer.PdfDocument.PageCount;
Console.WriteLine("-- Count :" + count);
return count;
}
Some of the sections can span multiple pages depending on content added.
Is it possible to get/find how many pages (in PDF) it took for a Section to render?
Edit 2 : Is it possible to tag a section and find on which page it starts on?
Thx for the help. I calculated it like this (i.e. To get the count in code...) :
First i tagged the section with a creation count of the section
newsection.Tag = num_sections_in_doc; //count changes every time i add a section
Then i used GetDocumentObjectsFromPage :
var x = new Dictionary<int, int>();
int numpages = pdfRenderer.PdfDocument.PageCount;
for (int idx = 0; idx < numpages; idx++)
{
DocumentObject[] docObjects = pdfRenderer.DocumentRenderer.GetDocumentObjectsFromPage(idx + 1);
if (docObjects != null && docObjects.Length > 0)
{
Section section = docObjects[0].Section;
int sectionTag = -1;
if (section != null)
sectionTag = (int)section.Tag;
if (sectionTag >= 0)
{
// count a section only once
if (!x.ContainsKey(sectionTag))
x.Add(sectionTag, idx + 1);
}
}
}
x.Keys are the sections.
and x.values are the start of each section.
If you want to display the page count in the PDF, use paragraph.AddSectionPagesField().
See also:
https://stackoverflow.com/a/19499231/162529
To get the count in code: you can add a tag to any document object (e.g. to any paragraph) and then use docRenderer.GetDocumentObjectsFromPage(...) to query the objects for a specific page. This allows you to find out which section the objects on this page belong to.
Or create each section in a separate document and then combine them to one PDF using docRenderer.RenderPage(...) as shown here:
http://www.pdfsharp.net/wiki/MixMigraDocAndPdfSharp-sample.ashx
The sample scales pages down to thumbnail size - you would draw them 1:1, each on a new page.
I have a table over a webpage having many values repeating like this:
Description App Name Information
Some Desc1 App1 Some Info
Some Desc2 App2 Some Info
Some Desc3 App2 Some Info
Some Desc4 App3 Some Info
Some Desc5 App4 Some Info
At the start of my app, it will ask the user to enter an appname of their choice. What I want is if I choose APP2 it should select "Some Desc2" first, that will lead to another page and there I will do something. Then again it should come back to previous page and this time it should select "Some Desc3", that will lead to another page. This should be repeated n number of times until selenium can't find an appname specified.
I have tried as shown below:
//Finding Table, its rows and coloumns
int rowcount = driver.FindElements(By.Id("someid")).Count;
for (int i = 0; i < rowcount; i++)
{
//Finding App name based on user entered text
var elems = driver.FindElements(By.PartialLinkText(text));
IList<IWebElement> list = elems;
for (int j = 0; j < list.Count; j++)
{
var table = driver.FindElement(By.Id("someid"));
IList<IWebElement> rows = table.FindElements(By.TagName("tr"));
IList<IWebElement> cells = rows[i].FindElements(By.TagName("td"));
//Again finding element based on user entered text
var elem = driver.FindElements(By.PartialLinkText(text));
list = elem;
if (list[1].Text.Equals(text))
{
list[0].Click();
string duration;
string price;
var elements = driver.FindElements(By.Id("SPFieldNumber"));
IList<IWebElement> lists = elements;
duration = lists.First().Text.ToString();
price = lists.ElementAt(1).Text.ToString();
MessageBox.Show(duration);
MessageBox.Show(price);
driver.Navigate().Back();
}
}
}
Running this code selects "Some Desc2" correctly and everything went fine. But after returning to the previous page c# throws an exception "element not found in the cache - perhaps the page has changed since it was looked up selenium".
For this particular issue, you find table and row elements before the loop, then by calling driver.Navigate().Back(); inside the loop, your table and row are no longer in the DOM (because your page changes, DOM changes, the table element is not the one you find outside the loop anymore)
Try put them inside the loop
int rowCount = driver.FindElements(By.CssSelector("#table_id tr")).Count; // replace table_id with the id of your table
for (int i = 0; i < rowCount ; i++)
{
var table = driver.FindElement(By.Id("some ID"));
rows = table.FindElements(By.TagName("tr"));
// the rest of the code
}
However, apart from solving your problems, I really suggest you read the Selenium documentation and learn some basic C# programming first, this will save you a lot time asking questions here.
Why are you doing this every time?
var elems = driver.FindElements(By.PartialLinkText(text));
IList<IWebElement> list = elems;
// IList<IWebElement> list = driver.FindElements(By.PartialLinkText(text));
element.Text is the string type you want, no need for calling ToString()
lists.First().Text.ToString();
// lists.First().Text;
You don't need this if there's no frames involved.
driver.SwitchTo().DefaultContent();
(from your earlier post) A list of IWebElement would never equal to a string, and the result can't be an element. Avoid using var if you don't know what type you want, as it may get you a totally different thing.
IList<IWebElement> list = elems;
var elem= list.Equals(text);
(from your earlier post) element.ToString() and element.Text are different
string targetele = elem.ToString(); // you want elem.Text;
I have a file that stores flight information and then I have a search form that allows a user to select a starting city and state, destination city and state, the departure date and number of seats they want to book.
I then have the results of the matched flights getting printed into a TableLayoutPanel. My issue is that when the program loops through to find the flights, it adds them, but if it finds multiple flights, the previous indexes are all replaced with the current one. Here is my code that searches through the flights (the lists are all label lists):
private void searchFlights()
{
StreamReader sr = File.OpenText("F:\\C#\\Airline\\Flight.txt");
string read = null;
Button button = new Button();
button.Text = "Book";
totalSeats = int.Parse(peopleSearchComboBox.Text);
while ((read = sr.ReadLine()) != null)
{
String[] flights = read.Split(' ');
testSeats = int.Parse(flights[6]);
if (cityStartSearchTextBox.Text == flights[2] & stateStartComboBox.Text == flights[3] & cityDestinationSearchTextBox.Text == flights[4] &
stateDestComboBox.Text == flights[5] & dateSearchTextBox.Text == flights[7] & totalSeats <= testSeats)
{
airlineSearchLabel.Text = flights[0];
priceSearchLabel.Text = flights[1];
seatSearchLabel.Text = flights[6];
startCityLabel.Text = flights[2];
startStateLabel.Text = flights[3];
endCityLabel.Text = flights[4];
endStateLabel.Text = flights[5];
price.Add(priceSearchLabel);
airline.Add(airlineSearchLabel);
seatsMatch.Add(seatSearchLabel);
buttons.Add(button);
cityStartMatch.Add(startCityLabel);
stateStartMatch.Add(startStateLabel);
cityDestMatch.Add(endCityLabel);
stateDestMatch.Add(endStateLabel);
flightsMatched++;
Console.WriteLine(airline[0].Text); //I have this to check the index and on each pass through its different
}
}
sr.Close();
}
And here is my code for printing it to the table:
private void fillTable()
{
blankTableLabel.Text = "";
priceTableLabel.Text = "Price";
seatsTableLabel.Text = "Open Seats";
airlineTableLabel.Text = "Airline";
noMatchedFlightsLabel.Text = "No Matches Found";
flightsSearchedTable.RowCount = flightsMatched + 1;
flightsSearchedTable.Controls.Add(blankTableLabel,0,0);
flightsSearchedTable.Controls.Add(priceTableLabel,1,0);
flightsSearchedTable.Controls.Add(airlineTableLabel,2,0);
flightsSearchedTable.Controls.Add(seatsTableLabel,3,0);
if (AppendTexts.totalFlights != 0 & flightsMatched != 0)
{
for (int x = 0; x < flightsMatched; x++)
{
if (WelcomeScreen.memberLoggedInCheck == true)
{
flightsSearchedTable.Controls.Add(buttons[x]);
flightsSearchedTable.Controls.Add(price[x]);
flightsSearchedTable.Controls.Add(airline[x]);
flightsSearchedTable.Controls.Add(seatsMatch[x]);
}
else
{
flightsSearchedTable.Controls.Add(price[x],1,x+1);
flightsSearchedTable.Controls.Add(airline[x],2,x+1);
flightsSearchedTable.Controls.Add(seatsMatch[x],3,x+1);
}
}
}
And this is what an example flight would look like that is stored in the file:
Southwest 80 Austin Texas Miami Florida 180 12/04/2011
Nevermind I was able to figure it out. I just needed to reset the labels each time when going through the loop.
It does not seem like you have your table layout panel setup to allow multiple flights to be added.
I's assuming you are using visual studio. Create a table layout panel using the ui editor and study the code it creates in the design file. Then add another row of data and study the design file again and take note of what was added.
Make sure that your code des everything VS does to make a new row/col.
Then practice populating the rows/columns in VS and make sure your code does the corectly also