Why might my SELECT control change selectedIndex not change sometimes? - c#

Background: I am running a process nightly that reads a database and then writes the results into another system. The target system does not have an interface other than HTML Web Service with a UI. I originally set up this system a few years ago and it was really reliable when running in IE 9. However, the host that I have to run this process on has changed from Windows 2008 R2 to 2012 R2 and the version of IE has changed to IE 11 and it mostly works, but in infuriatingly SOME cases, it does NOT work.
I am manipulating a web page with a dropdown SELECT with either "Off" (value=0) or "On" (value=1) and the name of the dropdown is variable, as there are multiple identical dropdowns, which I find and then set individually.
I have written some code to find the SELECT and to set the value, but in SOME cases, it works like a charm and in others, it just does NOT set the value, even though I have stepped through the code and watched IE with the page, which does not set the value. The code is here:
bool bDone = false;
mshtml.IHTMLElementCollection objEventCollection = (mshtml.IHTMLElementCollection)objDocument.getElementsByTagName("select");
for (int i = 0; i < objEventCollection.length; i++)
{
mshtml.IHTMLElement objElement = (mshtml.IHTMLElement)objEventCollection.item(i, 0);
if (objElement.getAttribute("name").EndsWith("NewEvent"))
{
objElement.click();
mshtml.HTMLSelectElement objEvent = (mshtml.HTMLSelectElement)objElement;
switch (sEventType)
{
case "Off":
objEvent.selectedIndex = 0; // This SOMETIMES works and SOMETIMES will error out!
bDone = true;
break;
case "On":
objEvent.selectedIndex = 1; // This SOMETIMES works and SOMETIMES will error out!
bDone = true;
break;
}
}
if (bDone)
{
break;
}
}
bOK = bDone;
The relevant page code looks like this:
<td valign="middle" align="center"><input class="data" type="hidden" name="No1EventString" value="0"><input class="data" type="hidden" name="No1OriginalEvent" value="0"><select class="data" name="No1NewEvent">
<option value="0" selected="">Off</option>
<option value="1">On</option>
</select></td>
I don't know why SOMETIMES, the value of the NewEvent dropdown is changing, but not every time.
When I run this in debug, it runs through quite happily, but then, sometimes, I get the following error:
Does anyone know of a more reliable method for setting this, if I can't fix it?
Alternatively, are there any workarounds, such as setting the focus to the SELECT and then pressing the down button (but I don't know how to do this)?

Well, I got to learn something during my lunch:
It seems that IE 11 SOMETIMES rejects the IHTMLElement.selectedIndex = x lines, but when I changed the lines to IHTMLElement.value = x instead, it was reliable!
When did that change?

Related

UIAutomation throws AccessViolationException on Windows 11

The issue:
We have an application written in C# that uses UIAutomation to get the current text (either selected or the word behind the carret) in other applications (Word, OpenOffice, Notepad, etc.).
All is working great on Windows 10, even up to 21H2, last update check done today.
But we had several clients informing us that the application is closing abruptly on Windows 11.
After some debugging I've seen some System.AccessViolationException thrown when trying to use the TextPatternRange.GetText() method:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
What we've tried so far:
Setting uiaccess=true in manifest and signing the app : as mentionned here https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/350ceab8-436b-4ef1-8512-3fee4b470c0a/problem-with-manifest-and-uiaccess-set-to-true?forum=windowsgeneraldevelopmentissues => no changes (app is in C:\Program Files\
In addition to the above, I did try to set the level to "requireAdministrator" in the manifest, no changes either
As I've seen that it may come from a bug in Windows 11 (https://forum.emclient.com/t/emclient-9-0-1317-0-up-to-9-0-1361-0-password-correction-crashes-the-app/79904), I tried to install the 22H2 Preview release, still no changes.
Reproductible example
In order to be able to isolate the issue (and check it was not something else in our app that was causing the exception) I quickly made the following test (based on : How to get selected text of currently focused window? validated answer)
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
var p = Process.GetProcessesByName("notepad").FirstOrDefault();
var root = AutomationElement.FromHandle(p.MainWindowHandle);
var documentControl = new
PropertyCondition(AutomationElement.ControlTypeProperty,
ControlType.Document);
var textPatternAvailable = new PropertyCondition(AutomationElement.IsTextPatternAvailableProperty, true);
var findControl = new AndCondition(documentControl, textPatternAvailable);
var targetDocument = root.FindFirst(TreeScope.Descendants, findControl);
var textPattern = targetDocument.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
string text = "";
foreach (var selection in textPattern.GetSelection())
{
text += selection.GetText(255);
Console.WriteLine($"Selection: \"{selection.GetText(255)}\"");
}
lblFocusedProcess.Content = p.ProcessName;
lblSelectedText.Content = text;
}
When pressing a button, this method is called and the results displayed in labels.
The method uses UIAutomation to get the notepad process and extract the selected text.
This works well in Windows 10 with latest update, crashes immediately on Windows 11 with the AccessViolationException.
On Windows 10 it works even without the uiaccess=true setting in the manifest.
Questions/Next steps
Do anyone know/has a clue about what can cause this?
Is Windows 11 way more regarding towards UIAutomation?
On my side I'll probably open an issue by Microsoft.
And one track we might follow is getting an EV and sign the app itself and the installer as it'll also enhance the installation process, removing the big red warnings. But as this is an app distributed for free we had not done it as it was working without it.
I'll also continue testing with the reproductible code and update this question should anything new appear.
I posted the same question on MSDN forums and got this answer:
https://learn.microsoft.com/en-us/answers/questions/915789/uiautomation-throws-accessviolationexception-on-wi.html
Using IUIautomation instead of System.Windows.Automation works on Windows 11.
So I'm marking this as solved but if anyone has another idea or knows what happens you're welcome to comment!

Selenium c# Google Chrome Drop Down menu

I'm going to explain this the best I can.
I run tests using the 3 major browsers, firefox, chrome and IE.
I have line where I select data from a drop down menu. Here is an example of what I use.
new SelectElement(CPC_Main.driver.FindElement(By.XPath("//select[#id='orgVdc']"))).SelectByText("Selenium_vDC");
This will select my element orgVdc and select the text value by Selenium_vDC. This will work perfectly for Firefox however it hangs and timesout for Chrome.
From what I can tell the issue is related to the default value of that element.
For example if default value of the element is "Test_vDC" selenium will successfully change it "Selenum_vDC" for all browsers.
However if the default value was "Selenium_vDC" already then Chrome will hang on trying to select that same value.
I hope that explains this enough, in a nut shell Chrome does not like matching default values.
I had encountered issue like this in the past. I used to get exceptions like "The element is no longer attached to the DOM". The issues(stale reference exceptions) like these needs a solid exception handling to make sure Automation execution is not affected.
You can try the below code. You can modify the below code as per your code set up.
public void SetDropDownValue(string Xpath, string value)
{
var element = FindElement(By.Xpath("Xpath"));
var selectElement = new SelectElement(element);
//check whether the option is selectable or not
var wait = new WebDriverWait(this.driver, this.testCaseConfiguration.WaitTime);
wait.Until(ExpectedConditions.TextToBePresentInElement(selectElement , option));
try
{
selectElement.SelectByText(value);
}
catch (StaleElementReferenceException)
{
element = FindElement(By.Xpath("Xpath"));
selectElement = new SelectElement(element);
selectElement.SelectByText(value);
}
}

Saving to session seems to erase value during Repeater's OnItemDataBound Event

This is weird. I'll try to explain my pain by the following example:
I've got an object in the Session: Session["reportQuestionGroupingTracker"]. It contains a List of strings. When a string is NOT found, a new h3 header is written in the repeater via a string literal.
The problem seems to be the line: Session["reportQuestionGroupingTracker"] = ary; This line seems to somehow (black)magically remove the string value in lit.Text. The value is there when I breakpoint the code and seems to persist until it goes out of scope in the function(so that part works like expected) - but the string value never seems to make it to the Literal control on the ASP.NET page - they are always blank.
Note that if I comment out the problem line or this line: if (!ary.Contains(headingText)), the headers show (but too many of them, since every iteration triggers a header write).
protected void rptQuestionsGroupedByCountry_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
RepeaterItem item = e.Item;
ArrayList ary = new ArrayList();
if (null != Session["reportQuestionGroupingTracker"]) ary = (ArrayList)Session["reportQuestionGroupingTracker"];
if ((item.ItemType == ListItemType.Item) ||
(item.ItemType == ListItemType.AlternatingItem))
{
DataRowView dr = (DataRowView)e.Item.DataItem;
string headingText = dr["Heading"].ToString();
Literal lit = (Literal)e.Item.FindControl("LiteralHeader");
if (!ary.Contains(headingText))
{
lit.Text = String.Format(#"<h3 class=""questionGroupingHeader"">{0}</h3>", headingText);
lit.Visible = true;
ary.Add(headingText);
Session["reportQuestionGroupingTracker"] = ary;
}
}
}
I've been on this for hours, banging my head - I've done similar things hundreds of times before, I just can't work out why it doesn't work this time! I've tried changing the Repeater to a DataList, Tried using the Context.Items object instead of the Session, a List instead of an ArrayList, but I'm stymied. Help!
I've also tried running it in IIS 6, just in case it was some Cassini weirdness, but the output is the same. It's an ASP.NET 4.0 Project.
Here's the code from the aspx page:
<asp:Repeater ID="rptQuestionsGroupedByCountry" runat="server" OnItemDataBound="rptQuestionsGroupedByCountry_OnItemDataBound">
<HeaderTemplate><table></HeaderTemplate>
<ItemTemplate>
<tr><td>
<asp:Literal ID="LiteralHeader" runat="server" Visible="false" />
<h3 class="report-country-tag"><%#DataBinder.Eval(Container, "DataItem.Numbers")%>.<%#DataBinder.Eval(Container, "DataItem.QusetionName")%></h3>
<div class="report-content">
<%#DataBinder.Eval(Container, "DataItem.Answer")%>
<p class="date">Date Updated: <%#DataBinder.Eval(Container, "DataItem.DocumentModifiedWhen")%></p>
</div>
</td></tr>
</ItemTemplate>
<FooterTemplate></table></FooterTemplate>
your problem with line
if (!ary.Contains(headingText))
You must populate literal text every time page loaded
if (!ary.Contains(headingText))
{
ary.Add(headingText);
Session["reportQuestionGroupingTracker"] = ary;
}
lit.Text = String.Format(#"<h3 class=""questionGroupingHeader"">{0}</h3>", headingText);
lit.Visible = true;
As Micheal suspected, the databind routine was being hit twice for every page load. The first time through the Session gets set properly, but the second time through, the Headers get skipped because the if (!ary.Contains(headingText)) correctly told it to. My bad for not noticing the two databinding hits. I feel very foolish. Coding while tired is no excuse for such an obvious issue. Sorry for wasting your time!
Also I was working with too large a dataset - I'd walk through the first 5 loops and see that the value was being set and then just F5. It wasn't until I was clearheaded this morning and just put breakpoints in every function so that I could see the execution order and understand what the heck was going on. Then it was a headslap moment... quickly followed by a single line change to fix the issue... and then a feeling of elation. Ain't programming fun?
Cheers and thanks for the help!
PS - Shout out to Kochobay for the line: '...every time page loaded...' which induced the headslapping epiphany!

Selenium 2 WebDriver - Chrome - Getting the value from a text box that is set via JavaScript

I am using Selenium 2 (latest release from Googlecode) and I have it firing up Chrome and going to a url.
When the page has loaded some javascript executes to set the value of a textbox.
I tell it to find a textbox by id which it does but it doesn't have the value within it (if I hardcode a value it finds it).
Looking at the PageSource e.g. Console.WriteLine(driver.PageSource); shows the html and the textbox is empty.
I've tried using :
driver.FindElement(By.Id("txtBoxId") to get the element and that too doesn't fetch the value.
I've also tried ChromeWebElement cwe = new ChromeWebElement(driver, "txtBoxId"); (which complains about Stale data).
Any thoughts?
John
Finally I found the answer! This is the code that works for me
WebDriverWait wait = new WebDriverWait(_driver, new TimeSpan(0,0,60));
wait.Until(driver1 => _driver.FindElement(By.Id("ctl00_Content_txtAdminFind")));
Assert.AreEqual("Home - My Housing Account", _driver.Title);
Here is my source!
http://code.google.com/p/selenium/issues/detail?id=1142
Selenium 2 does not have wait functions built in for elements in the DOM. This was the same thing as in Selenium 1.
If you have to wait for something you could do it as
public string TextInABox(By by)
{
string valueInBox = string.Empty;
for (int second = 0;; second++) {
if (second >= 60) Assert.Fail("timeout");
try
{
valueInBox = driver.FindElement(by).value;
if (string.IsNullOrEmpty(valueInBox) break;
}
catch (WebDriverException)
{}
Thread.Sleep(1000);
}
return valueInBox;
}
Or something along those lines
I use webdriver through ruby (cucumber watir-webdriver, actually), and I tend to do this:
def retry_loop(interval = 0.2, times_to_try = 4, &block)
begin
return yield
rescue
sleep(interval)
if (times_to_try -= 1) > 0
retry
end
end
yield
end
Then whenever I have content appearing due to javascript writes or whatever, i just wrap it in a retry_loop like so:
retry_loop do #account for that javascript might fill out values
assert_contain text, element
end
As you'll notice there is no performance penalty if it is already there. The reverse case (checking that something is NOT there) will always need to reach the timeout, obviously.
I like the way that keeps details packed away in the method and the test code clean.
Perhaps you could use something similar in C++?

Why doesn't this JavaScript display a confirm box?

I am trying to fix some bugs in a product I inherited, and I have this snippet of javascript that is supposed to hilight a couple of boxes and pop up a confirm box. What currently happens is I see the boxes change color and there is a 5 or so second delay, then it's as if the missing confirm just accepts itself. Does anyone smarter than I see anything amiss in this code?
function lastCheckInv() {
document.getElementById("ctl00$ContentPlaceHolderMain$INDet$txtInvNumber").style.background = "yellow";
document.getElementById("ctl00$ContentPlaceHolderMain$INDet$txtInvNumber").focus();
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_AddCharges").style.background = "yellow";
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight").style.background = "yellow";
bRetVal = confirm("Are you sure the information associated with this invoice is correct?");
return bRetVal;
}
The only thing I can think of is if one of the lines before the confirm is throwing an exception and you're never actually getting to the confirm.
If you're using IE, make sure script debugging is enabled. If you're using Firefox, install the Firebug add-on and enable it for your website.
Or, for very primitive debugging, just put alerts after each statement and figure out where it's bombing.
You should use the following method to reference your controls from JavaScript:
document.getElementById(<%= txtInvNumber.ClientID %>).style.background = "yellow"
If that doesn't help, try setting a breakpoint in your JavaScript and stepping through it to see where it's failing.
This test script ran fine for me in IE, Firefox, and Opera. So there doesn't seem to be anything wrong with your basic syntax. The problem is either in the ID's (which doesn't fit with the fact that it acts as if confirmed after 5 seconds) or in some other conflicting JavaScript on the page. It will be difficult to help you without seeing more of the page.
<script language="javascript">
function lastCheckInv() {
document.getElementById("test1").style.background = "yellow";
document.getElementById("test1").focus();
document.getElementById("test2").style.background = "yellow";
document.getElementById("test3").style.background = "yellow";
bRetVal = confirm("Are you sure?");
return bRetVal;
}
</script>
<form method="get" onsubmit="return lastCheckInv();">
<input id="test1" name="test1" value="Hello">
<input id="test2" name="test2" value="Hi">
<input id="test3" name="test3" value="Bye">
<input type="submit" name="Save" value="Save">
</form>
A few thoughts: Could be the .focus() call is hiding your confirm behind the page? Or could it be that one of your control id's is not correct causing, the .style.background references to fail?
You should set the focus after showing the confirm box, otherwise the confirm box will grab focus away, making that line meaningless.
Don't hard-code ASP.Net id's like that. While they typically are consistent, a future version of ASP.net won't guarantee the same scheme, meaning your setting yourself up for pain when it comes time to update this code at some point in the future. Use the server-side .ClientID property for the controls to write those values into the javascript somewhere as variables that you can reference.
Update:
Based on your comment to another response, this code will result in a postback if the function returns true. In that case, there's not point in running the .focus() line at all unless you are going to return false.
I do not like accesing objects directly by
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight").style.background = "yellow";
If the object is not returned JavaScript will error out. I prefer the method of
var obj = document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight");
if(obj)
{
obj.style.background = "yellow";
}
My guess is you are trying to access an object that is not on the DOM, so it never gets to the confirm call.
It might be worth checking that the elements actually exist. Also,try delaying the focus until after the confirm():
function lastCheckInv() {
var myObjs,bRetVal;
myObjs=[
"ctl00_ContentPlaceHolderMain_INDet_AddCharges",
"ctl00_ContentPlaceHolderMain_INDet_lblFreight",
"ctl00$ContentPlaceHolderMain$INDet$txtInvNumber"
];
bRetVal = confirm("Are you sure the information associated with this invoice is correct?");
for (var i=0, j=myObjs.length, myElement; i<j; i++){
myElement=document.getElementById(myObjs[i]);
if (!myElement){
// this element is missing
continue;
}
else {
// apply colour
myElement.style.background = "yellow";
// focus the last object in the array
if (i == (j-1) ){
myObj.focus();
}
}
}
return bRetVal;
}
function lastCheckInv()
{
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_txtInvNumber").style.background = "yellow";
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_txtInvNumber").focus();
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_AddCharges").style.background = "yellow";
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight").style.background = "yellow";
return confirm("Are you sure the information associated with this invoice is correct?");
}
The first two lines in your function are wrong, $ are in the UniqueID of an ASP.Net Control.
On Clientside you have to use the ClientID, replace $ with _ .
If you are sure that the Controls exists, the
code above might work.
Is bRetVal actually declared anywhere?
If not, "var bRetVal = confirm...." is a friendly way of telling jscript that it is a variable.

Categories