how to break out of a loop when a page is reloading - c#

I have this code below where it goes through all of the links in a panel and once it is able find the relevant link, it will click it. The problem I have though is that when I select the link, the page reloads before it shows a new page. So I thought of adding a break to break out of the loop because if I don't add this, then it will give me a element is not attached to document error. I believe this error occurs because it is still trying to loop through the links whilst the page is reloading.
However, when adding the break and running the test, nothing happens as in it doesn't select a link. How can I break out of the loop after clicking the link?
I don't need to go back to the loop after the page has reloaded, I simply want to break out of the loop because the loop has done its job in finding the link and clicking.
public void SelectHomepageSearchPanelLink(string linkText)
{
var searchPanelLinks = _driver.FindElements(HomepageResponsiveElements.HomepageSearchPanelLinks);
foreach (var searchPanelLink in searchPanelLinks)
{
if (searchPanelLink.Text == linkText)
{
searchPanelLink.Click();
break;
}
else
{
throw new Exception($"{linkText} link not found by the responsive homepage search panel");
}
}
}

Try this:
public void SelectHomepageSearchPanelLink(string linkText)
{
var searchPanelLinks = _driver.FindElements(HomepageResponsiveElements.HomepageSearchPanelLinks);
var linkToClick;
foreach (var searchPanelLink in searchPanelLinks)
{
if (searchPanelLink.Text == linkText)
{
linkToClick = searchPanelLink;
break;
}
else
{
throw new Exception($"{linkText} link not found by the responsive homepage search panel");
}
}
linkToClick.Click();
}

Related

Opening PDFs in WebView2 based on selection in CheckBoxColumn

So,
In my WPF application, I want my users to be able to open previews of invoices, so that they may either verify or discard them. I am letting them check rows (each row representing a invoice) in a DataGridCheckBoxColumn in my DataGrid, then clicking a button (which runs my CreateInvoicePreview() method, see bottom of post), having all of the invoice previews be opened in new windows (one window for each invoice).
Well.. What happens now, is: User checks InvoiceA and InvoiceB. Two invoices are opened, but they are the same: InvoiceC. The correct amount of invoices are always opened, but not the correct instance. If I open the temp folder specified in my file path, I see that all invoices in the datagrid has been saved: InvoiceA through InvoiceJ.
Let me take you through the code.
This is the method that creates that builds and saves the actual PDF's, which the WebView2 control uses as source, so that it can display them in-app. It is heavily abbreviated.
I have kept the structure with the nested foreach loops in case that is relevant.
public void CreatePreviewInvoice() {
/* SQL SERVER CODE
* SQL SERVER CODE
* SQL SERVER CODE */
List<PaidTrip> paidTrips = PaidTrips.ToList();
tripsGroupedByCompany = paidTrips.GroupBy(pt => pt.LicenseHolderID);
foreach (IGrouping<string, PaidTrip> companyGroup in tripsGroupedByCompany) {
/* SQL SERVER CODE
* SQL SERVER CODE
* SQL SERVER CODE */
List<LicenseHolder> licenseHolders = LicenseHolders.ToList();
IEnumerable<IGrouping<string, PaidTrip>> groupedByVehicle = companyGroup.GroupBy(n => n.VehicleID);
foreach (IGrouping<string, PaidTrip> vehicleGroup in groupedByVehicle) {
// Iterating over all data pertaining to each vehicle
foreach (PaidTrip trip in vehicleGroup) {
}
try {
string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Split('\\')[1];
string fileName = $"FORHÅNDSVISNING - MÅ IKKE SENDES! {LicenseHolderID + "_" + "Faktura_" + InvoiceID}.pdf";
string filePath = $#"C:\Users\{userName}\AppData\Local\Temp\";
PdfFilePath = $"{filePath}{fileName}";
//if (LicenseHolderID == PreviewInvoiceViewModel.SelectedRow.LicenseHolderID) {
document.Save($"{PdfFilePath}");
//} else {
// return;
//}
} catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
}
As you see, towards the end of the method I have commented out a bit of code, which was me trying to implement a way to filter based on the checked rows only. It did not work.
This is the XAML for the WebView2:
<Wpf:WebView2
x:Name="wv_preview_invoice" Loaded="{s:Action CreatePreviewInvoice}"
Height="997" Width="702" Canvas.Left="20" Canvas.Top="71"
Source="{Binding PdfFilePath}"></Wpf:WebView2>
PdfFilePath is a property, which is referenced within the method above.
It's given a value within the method, and when Source (for the WebView2) is called, it is able to get the value from PdfFilePath, and thus display the PDF.
But as I said initially, it just creates X amount of instances/windows of the same invoice. Always the same one, because of in what order they are queried from the database.
And finally, here is the method that run when they click whichever invoices they want to preview, it's to open the new window with the WebView2 control:
public void PreviewInvoices() {
bool success = false;
foreach (PaidTrip item in PaidTrips) {
if (item.IsChecked == true) {
ShowPreviewInvoiceDetailed(item);
success = true;
}
}
if (!success) {
MessageBox.Show("You must chose an invoice to preview first.");
}
}
The method that opens the next window where the WebView2 is, looks like this:
public void ShowPreviewInvoiceDetailed() {
PreviewInvoiceDetailedViewModel viewModel = new(windowManager);
windowManager.ShowWindow(viewModel);
}
What part (or several parts) of the picture am I missing?
I managed to solve this by doing the following:
I made a property; public static string PreviewedInvoice { get; set; } in the ViewModel of the parent window. In my method that opens the child window (where the preview invoices are to be displayed) I bind it to LicenseHolderID of the rows that have a checked CheckBox, via foreach loop, like such:
public void PreviewInvoices() {
bool success = false;
foreach (PaidTrip item in PaidTrips) {
if (item.IsChecked == true) {
PreviewedInvoice = item.LicenseHolderID;
ShowPreviewInvoiceDetailed();
success = true;
}
}
if (!success) {
MessageBox.Show("You must chose an invoice to preview first.");
}
}
So now I have a way to filter only the checked rows, and make sure only the LicenseHolderID which match those in the row with a checked CheckBox, are actually saved. I updated my main method:
if (LicenseHolderID == PreviewInvoiceViewModel.PreviewedInvoice) {
document.Save($"{fullPath}");
SourcePath = fullPath;
}
And I bound SourcePath to the the source of the WebView2 in the XAML.
I feel like this is a clunky way of doing it, and I am going back and forth between layers, as a comment (since removed) mentioned.
If anyone can show me a better way, I'm all ears..

WinForms Control Not working

I am trying to read in the first description in the first row. However, for some reason my if statement is never becoming true. Here is the html code that has the class im trying to read from. I gave a picture of it. Below that is the actual code im running. There is a first page that I login to and then input the part number. This then takes me to this page where you see the html code in the picture. I then need to grab the description of the part number. I included a picture of the website I am grabbing the description from. In order to give you a better Idea of what's happening. Also, I have a web browser component in my code that I am using. Can you point out why its not working? Thanks.
var secondPage = webBrowser1.Document;
foreach (HtmlElement element in secondPage.All)
{
if (element.GetAttribute("className") == "SE-Content-PartSearch-Grid-Row-Description")
{
messagebox.Show("Found it");
}
}
ActualWebsite
HTMLCODE
I suggest that you put a breakpoint on the "if" statement. It will perhaps be easier to see what is going on if you modify the code slightly:
var secondPage = webBrowser1.Document;
foreach (HtmlElement element in secondPage.All)
{
String className = element.GetAttribute("className");
if (className == "SE-Content-PartSearch-Grid-Row-Description")
{
messagebox.Show("Found it");
}
}

Selenium C#: How to Find Element Again

First time here. So I was wanting some help on a selenium testing that's got me stuck now for several hours.
So I stored a IList<IWebElement> on a page, composed of <a>. I was able to click the 1st element of that IList through a foreach, get what I need from the new page and go back to the page of the list using driver.manage.navigate.back().
However, this time, I can't click the 2nd element of the list.
Is there a way to find that 2nd element, 3rd element, 4th element, etc?
static void Main(string[] args)
{
IWebDriver driver = new FirefoxDriver();
string url = "http://DummyPageOne.Com";
driver.Navigate().GoToUrl(url);
driver.Manage().Timeouts().ImplicitlyWait(Timespan.FromSeconds(10));
PageOne page = new PageOne();
foreach (IWebElement item in page.Items)
{
item.Click();
ItemDetails details = new ItemDetails();
details.SaveImage(#"D:\Images\");
driver.Navigate().Back();
}
}
public class PageOne()
{
public PageOne()
{
PageFactory.InitElements(driver, this);
}
public IList<IWebElement> Items;
public void StoreItems()
{
string locator = "DummyLocator";
Items = driver.FindElements(By.Xpath(locator));
}
}
public class ItemDetails()
{
public ItemDetails()
{
PageFactory.InitElements(driver, this);
}
public void SaveImage()
{
}
}
On the basis of what I understood from your question, here is the way to achieve that. I checked the following C# Code on Chrome and it's working perfectly. I hope this helps.
[Test]
public void ClickAllLinks()
{
//Navigate to URL
driver.Navigate().GoToUrl(#"https://www.google.co.in/#q=Selenium");
//Store Links in IList
IList<IWebElement> resultLinks = driver.FindElements(By.CssSelector("div#ires>ol>div:not(:first-child)>div>div>h3>a"));
//Print Count
//Console.WriteLine(googleLinks.Count);
for (int i = 0; i < resultLinks.Count; i++)
{
int j = i + 1;
//Click on Link
driver.FindElement(By.CssSelector("div#ires>ol>div:not(:first-child)>div:nth-child(" + j + ")>div>h3>a")).Click();
//Print Element Locator
//Console.WriteLine("div#ires>ol>div:not(:first-child)>div:nth-child(" + j + ")>div>h3>a");
Thread.Sleep(2000); //Static wait is not recommended, add conditional wait
//Get what you need from new page
//Navigate back parent page
driver.Navigate().Back();
Thread.Sleep(2000); //Static wait is not recommended, add conditional waits
}
}
If this is not what you want please let me know.
The simple way is to initialize that list again in the end of loop.
Ok, the thing to remember about .Net and Selenium, is that the C# variables/types are references to things that exist in memory (on screen) in the browser.
When the browser window navigates away, the reference held by those variables will be invalid. You must 're-find' them.
I would be very surprised if you could click the first item in the list either.
The only way to click on these objects, is to reacquire the object after the page has reloaded.
Eg: Driver.FindElement(By.XPath("\\div[#class='somelocator']"));
If you can, I would recommend constructing a collection of 'By' classes.
Then loop through the By classes using them in the Find method.
That way, each iteration of the loop, you will be acquiring the object fresh.

Find element after page reload

Ok this one got me hitting my head on the wall for a while now.
I look at a list of web elements. I access that list like so
foreach (IWebElement link in driver.FindElementsByCssSelector("span.cn.mailbox > a"))
{
// Click at a lot of page and the page will reload eventually
}
The problem is, inside the loop, I need to change pages and stuff but at the end I get back to that page that has the link list.
As soon as I hit the second iteration, I get the following error :
Probably because I changed page and even thought the links in the collection I loop through are the same, the compiler doesn't seem to think it's the exact same collection.
Is there a way around this or a workaround I could use ?
The reason you're getting the exception is because the page the driver.FindElementsByCssSelector is referencing has been reloaded.
Something like this should work:
Create an array of link text. Iterate through the link text array, clicking each link.
string [] links = new string[driver.FindElementsByCssSelector("span.cn.mailbox > a").Count);
int i = 0;
foreach (IWebElement link in driver.FindElementsByCssSelector("span.cn.mailbox > a"))
{
links[i++] = link.Text;
}
for (int i = 0; i < driver.FindElementsByCssSelector("span.cn.mailbox > a").Count; i++)
{
driver.FindElementByLinkText(links[i]).Click();
}

How can I disallow WebView to open links on the browser in WinRT( target=_blank links )?

I have a WebView on my app and I can't change the html file("target=_blank" link types). But some links on the page makes my app open them on the system browser. How can I disallow this action?
Thanks.
In the NavigationCompleted event handler run this script:
webView.InvokeScriptAsync("eval", new[]
{
#"(function()
{
var hyperlinks = document.getElementsByTagName('a');
for(var i = 0; i < hyperlinks.length; i++)
{
if(hyperlinks[i].getAttribute('target') != null)
{
hyperlinks[i].setAttribute('target', '_self');
}
}
})()"
});
On Windows 10, you can use WebView.NewWindowRequested:
private void WebView1_NewWindowRequested(
WebView sender,
WebViewNewWindowRequestedEventArgs args)
{
Debug.WriteLine(args.Uri);
args.Handled = true; // Prevent the browser from being launched.
}
There is a navigation starting event. It have a cancel property that can be used to cancel the navigation. Maybe this will work for you?
http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.webview.navigationstarting
Stumbled on this myself recently, and I want to add that even though user2269867's answer is a viable solution, it might not work in certain situations.
For example, system browser will not only open if user click a link with target="_blank" attribute, but also if window.open() function called in javascript. Moreover, even removing all 'target' attributes won't work if a page loading some content dynamically and changing DOM after your script is already finished executing.
To solve all problems above, you need to override window.open function and also check for 'target' attribute not once, but every time user click something. Here is script that covers those cases:
function selfOrParentHasAttribute(e, attributeName) {
var el = e.srcElement || e.target;
if (el.hasAttribute(attributeName)) {
return el;
}
else {
while (el = el.parentNode) {
if (el.hasAttribute(attributeName)) {
return el;
}
}
}
return false;
}
var targetAttributeName = "target";
document.addEventListener("click", function (e) {
var el = selfOrParentHasAttribute(e, targetAttributeName);
if (el) {
if ((el.getAttribute(targetAttributeName) == "_blank") ||
(el.getAttribute(targetAttributeName) == "_new"))
{
el.removeAttribute(targetAttributeName);
}
}
});
window.open = function () {
return function (url) {
window.location.href = url;
};
}(window.open);
My js skills aren't ideal, so feel free to modify.
Also don't forget that, as kiewic mentioned, for Windows 10 there is WebView.NewWindowRequested event which solves this issue more natural.
If you just want to show the page and not allow any action to be done on that page I would look into WebViewBrush. The WebViewBrush will basically screenshot the website and the users will not be able to use any links or anything else on that page, it will turn into a read-only page. I believe this is what you are asking for.
More info on WebViewBrush can be found here: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.webviewbrush
If you can edit HTML of the page and NavigateToString(), then add <base target='_blank'/> in the <head>

Categories

Resources