From c607a49467f1730b7d0a5d0d1ec46212207aba30 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 01:15:22 +0200 Subject: [PATCH 01/12] Fixed incorrect merge from other branch --- src/TestStack.White/Application.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TestStack.White/Application.cs b/src/TestStack.White/Application.cs index 4002a685..210207f8 100644 --- a/src/TestStack.White/Application.cs +++ b/src/TestStack.White/Application.cs @@ -10,6 +10,7 @@ using TestStack.White.Sessions; using TestStack.White.UIItems.Finders; using TestStack.White.UIItems.WindowItems; +using TestStack.White.WindowsAPI; namespace TestStack.White { From 707feb2d7b9b66990fefa2b3f6243576cab9fa2b Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Fri, 3 Jan 2014 15:32:05 +0200 Subject: [PATCH 02/12] Replaced usage of UIItem.Pattern(...) with extension method GetPattern() --- src/TestStack.White/Debug.cs | 3 +- src/TestStack.White/UIA/AutomationElementX.cs | 40 ++++++++++++++++++- .../ListBoxItems/SilverlightComboBox.cs | 2 +- .../UIItems/ListBoxItems/WPFComboBox.cs | 2 +- .../PropertyGridItems/PropertyGridProperty.cs | 2 +- .../UIItems/Scrolling/ScrollBar.cs | 2 +- src/TestStack.White/UIItems/SelectionItem.cs | 2 +- src/TestStack.White/UIItems/Slider.cs | 2 +- .../UIItems/TableItems/TableCell.cs | 4 +- src/TestStack.White/UIItems/TextBox.cs | 8 ++-- src/TestStack.White/UIItems/ToggleableItem.cs | 2 +- .../UIItems/TreeItems/TreeNode.cs | 2 +- src/TestStack.White/UIItems/UIItem.cs | 33 +++------------ .../UIItems/WindowItems/Window.cs | 6 +-- 14 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/TestStack.White/Debug.cs b/src/TestStack.White/Debug.cs index d39a53d3..9720c45d 100644 --- a/src/TestStack.White/Debug.cs +++ b/src/TestStack.White/Debug.cs @@ -3,6 +3,7 @@ using System.Windows.Automation; using Castle.Core.Logging; using TestStack.White.Configuration; +using TestStack.White.UIA; using TestStack.White.UIItems; namespace TestStack.White @@ -95,7 +96,7 @@ private static void GetAllWindows(StringBuilder stringBuilder, int level, Automa AutomationElementCollection allWindows = baseElement.FindAll(TreeScope.Children, windowCondition); foreach (AutomationElement windowElement in allWindows) { - var pattern = (WindowPattern) UIItem.Pattern(windowElement, WindowPattern.Pattern); + var pattern = windowElement.GetPattern(); string modalText = pattern == null ? "(null)" : pattern.Current.IsModal.ToString(); stringBuilder.AppendFormat("{0}Name: {1}, Bounds: {2} ProcessId: {3}, Modal: {4}", padding, windowElement.Current.Name, windowElement.Current.BoundingRectangle, windowElement.Current.ProcessId, modalText); diff --git a/src/TestStack.White/UIA/AutomationElementX.cs b/src/TestStack.White/UIA/AutomationElementX.cs index a309679c..48b72e98 100644 --- a/src/TestStack.White/UIA/AutomationElementX.cs +++ b/src/TestStack.White/UIA/AutomationElementX.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Automation; using Castle.Core.Logging; @@ -9,7 +12,8 @@ namespace TestStack.White.UIA { public static class AutomationElementX { - private static readonly ILogger Logger = CoreAppXmlConfiguration.Instance.LoggerFactory.Create(typeof(AutomationElementX)); + private static readonly ILogger Logger = CoreAppXmlConfiguration.Instance.LoggerFactory.Create(typeof (AutomationElementX)); + private static readonly Dictionary AutomationPatterns = new Dictionary(); public static string Display(this AutomationElement automationElement) { @@ -32,6 +36,40 @@ public static bool IsPrimaryControl(this AutomationElement automationElement) return ControlDictionary.Instance.IsPrimaryControl(elementInformation.ControlType, elementInformation.ClassName, elementInformation.Name); } + public static T GetPattern(this AutomationElement automationElement) + where T : class + { + T result; + try + { + object patternObj; + var gotIt = automationElement.TryGetCurrentPattern(GetAutomationPattern(), out patternObj); + result = gotIt ? (T) patternObj : null; + } + catch (COMException) + { + return null; + } + catch (ElementNotAvailableException) + { + return null; + } + return result; + } + + private static AutomationPattern GetAutomationPattern() + { + AutomationPattern automationPattern; + var patternType = typeof (T); + if (!AutomationPatterns.TryGetValue(patternType, out automationPattern)) + { + var f = patternType.GetField("Pattern", BindingFlags.Static | BindingFlags.Public); + automationPattern = (AutomationPattern) f.GetValue(null); + AutomationPatterns[patternType] = automationPattern; + } + return automationPattern; + } + public static AutomationElement GetAutomationElementFromPoint(Point location) { try diff --git a/src/TestStack.White/UIItems/ListBoxItems/SilverlightComboBox.cs b/src/TestStack.White/UIItems/ListBoxItems/SilverlightComboBox.cs index 373b6a81..86799090 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/SilverlightComboBox.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/SilverlightComboBox.cs @@ -21,7 +21,7 @@ public override VerticalSpan VerticalSpan { get { - var scrollPattern = (ScrollPattern) Pattern(ScrollPattern.Pattern); + var scrollPattern = GetPattern(); var bounds = Bounds; var firstVisibleItem = Items.First(i=>!i.IsOffScreen).Bounds; var lastItem = Items.Last(i=>i.Bounds != Rect.Empty).Bounds; diff --git a/src/TestStack.White/UIItems/ListBoxItems/WPFComboBox.cs b/src/TestStack.White/UIItems/ListBoxItems/WPFComboBox.cs index df6427da..85e54e71 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/WPFComboBox.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/WPFComboBox.cs @@ -20,7 +20,7 @@ public override VerticalSpan VerticalSpan { get { - var scrollPattern = (ScrollPattern) Pattern(ScrollPattern.Pattern); + var scrollPattern = GetPattern(); var bounds = Bounds; var firstVisibleItem = (Items.FirstOrDefault(i=>!i.IsOffScreen) ?? Items[0]).Bounds; var lastItem = Items[Items.Count - 1].Bounds; diff --git a/src/TestStack.White/UIItems/PropertyGridItems/PropertyGridProperty.cs b/src/TestStack.White/UIItems/PropertyGridItems/PropertyGridProperty.cs index 6441d6a5..68d3cbea 100644 --- a/src/TestStack.White/UIItems/PropertyGridItems/PropertyGridProperty.cs +++ b/src/TestStack.White/UIItems/PropertyGridItems/PropertyGridProperty.cs @@ -26,7 +26,7 @@ private ValuePattern.ValuePatternInformation ValuePatternId() private ValuePattern ValuePattern() { - return ((ValuePattern) Pattern(System.Windows.Automation.ValuePattern.Pattern)); + return GetPattern(); } public virtual bool IsReadOnly diff --git a/src/TestStack.White/UIItems/Scrolling/ScrollBar.cs b/src/TestStack.White/UIItems/Scrolling/ScrollBar.cs index 96389e7e..3c1ac11c 100644 --- a/src/TestStack.White/UIItems/Scrolling/ScrollBar.cs +++ b/src/TestStack.White/UIItems/Scrolling/ScrollBar.cs @@ -61,7 +61,7 @@ public virtual void SetToMinimum() } public virtual void SetToMaximum() { - ((RangeValuePattern) Pattern(RangeValuePattern.Pattern)).SetValue(MaximumValue); + GetPattern().SetValue(MaximumValue); ForwardSmallChangeButton.Click(); } diff --git a/src/TestStack.White/UIItems/SelectionItem.cs b/src/TestStack.White/UIItems/SelectionItem.cs index 5c32e71d..1adcda30 100644 --- a/src/TestStack.White/UIItems/SelectionItem.cs +++ b/src/TestStack.White/UIItems/SelectionItem.cs @@ -24,7 +24,7 @@ public virtual void Select() { if (!Bounds.IsEmpty) Click(); - var selectionItemPattern = (SelectionItemPattern) Pattern(SelectionItemPattern.Pattern); + var selectionItemPattern = GetPattern(); if (selectionItemPattern == null) { throw new UIActionException(string.Format("{0} cannot be selected as its position is unknown and doesn't support SelectionItemPattern", ToString())); diff --git a/src/TestStack.White/UIItems/Slider.cs b/src/TestStack.White/UIItems/Slider.cs index 53826d87..f3d4fa5d 100644 --- a/src/TestStack.White/UIItems/Slider.cs +++ b/src/TestStack.White/UIItems/Slider.cs @@ -36,7 +36,7 @@ public virtual double Minimum private RangeValuePattern RangePattern() { - return ((RangeValuePattern) Pattern(RangeValuePattern.Pattern)); + return GetPattern(); } public virtual Button LargeIncrementButton diff --git a/src/TestStack.White/UIItems/TableItems/TableCell.cs b/src/TestStack.White/UIItems/TableItems/TableCell.cs index de6a33e8..67607cd6 100644 --- a/src/TestStack.White/UIItems/TableItems/TableCell.cs +++ b/src/TestStack.White/UIItems/TableItems/TableCell.cs @@ -23,7 +23,7 @@ public virtual object Value set { actionListener.ActionPerforming(this); - var valuePattern = (ValuePattern) Pattern(ValuePattern.Pattern); + var valuePattern = GetPattern(); if (value is string) { Click(); @@ -44,7 +44,7 @@ public virtual object Value public override void Click() { - ((InvokePattern) Pattern(InvokePattern.Pattern)).Invoke(); + GetPattern().Invoke(); } public override void RightClick() diff --git a/src/TestStack.White/UIItems/TextBox.cs b/src/TestStack.White/UIItems/TextBox.cs index ffcc12d4..0a301225 100644 --- a/src/TestStack.White/UIItems/TextBox.cs +++ b/src/TestStack.White/UIItems/TextBox.cs @@ -23,9 +23,9 @@ public virtual string Text { if (automationElement.Current.IsPassword) throw new WhiteException("Text cannot be retrieved from textbox which has secret text (e.g. password) stored in it"); - var pattern = Pattern(ValuePattern.Pattern) as ValuePattern; + var pattern = GetPattern(); if (pattern != null) return pattern.Current.Value; - var textPattern = Pattern(TextPattern.Pattern) as TextPattern; + var textPattern = GetPattern(); if (textPattern != null) return textPattern.DocumentRange.GetText(int.MaxValue); throw new WhiteException(string.Format("AutomationElement for {0} supports neither ValuePattern or TextPattern", ToString())); @@ -43,7 +43,7 @@ public virtual string BulkText { try { - var pattern = Pattern(ValuePattern.Pattern) as ValuePattern; + var pattern = GetPattern(); if (pattern != null) pattern.SetValue(value); else { @@ -64,7 +64,7 @@ public virtual string BulkText public virtual bool IsReadOnly { - get { return ((ValuePattern) Pattern(ValuePattern.Pattern)).Current.IsReadOnly; } + get { return GetPattern().Current.IsReadOnly; } } public virtual void ClickAtRightEdge() diff --git a/src/TestStack.White/UIItems/ToggleableItem.cs b/src/TestStack.White/UIItems/ToggleableItem.cs index 3373bda4..c3968201 100644 --- a/src/TestStack.White/UIItems/ToggleableItem.cs +++ b/src/TestStack.White/UIItems/ToggleableItem.cs @@ -25,7 +25,7 @@ public virtual ToggleState State public virtual void Toggle() { - var pattern = (TogglePattern) Pattern(TogglePattern.Pattern); + var pattern = GetPattern(); pattern.Toggle(); } } diff --git a/src/TestStack.White/UIItems/TreeItems/TreeNode.cs b/src/TestStack.White/UIItems/TreeItems/TreeNode.cs index 8caf95ec..47db8555 100644 --- a/src/TestStack.White/UIItems/TreeItems/TreeNode.cs +++ b/src/TestStack.White/UIItems/TreeItems/TreeNode.cs @@ -62,7 +62,7 @@ public virtual void Select() /// public virtual bool UnSelect() { - var pattern = (SelectionItemPattern) Pattern(SelectionItemPattern.Pattern); + var pattern = GetPattern(); if (pattern != null) { pattern.RemoveFromSelection(); diff --git a/src/TestStack.White/UIItems/UIItem.cs b/src/TestStack.White/UIItems/UIItem.cs index 51d678f6..5f38c99c 100644 --- a/src/TestStack.White/UIItems/UIItem.cs +++ b/src/TestStack.White/UIItems/UIItem.cs @@ -117,33 +117,12 @@ public virtual bool ValueOfEquals(AutomationProperty property, object compareTo) return Property(property).Equals(compareTo); } - protected virtual T Pattern() + public T GetPattern() + where T : class { - var fieldInfo = typeof(T).GetField("Pattern", BindingFlags.Static | BindingFlags.Public); - var pattern = (AutomationPattern)fieldInfo.GetValue(null); - object patternObject; - if (automationElement.TryGetCurrentPattern(pattern, out patternObject)) - { - return (T)patternObject; - } - return (T)(object)null; - } - - protected virtual BasePattern Pattern(AutomationPattern pattern) - { - return Pattern(AutomationElement, pattern); + return AutomationElement.GetPattern(); } - - internal static BasePattern Pattern(AutomationElement automationElement, AutomationPattern pattern) - { - object patternObject; - if (automationElement.TryGetCurrentPattern(pattern, out patternObject)) - { - return (BasePattern) patternObject; - } - return null; - } - + public virtual void RightClickAt(Point point) { actionListener.ActionPerforming(this); @@ -419,7 +398,7 @@ public virtual AutomationElement GetElement(SearchCriteria searchCriteria) public virtual void Enter(string value) { - var pattern = Pattern(ValuePattern.Pattern) as ValuePattern; + var pattern = GetPattern(); if (pattern != null) pattern.SetValue(string.Empty); if (string.IsNullOrEmpty(value)) return; @@ -445,7 +424,7 @@ internal virtual UIItemContainer AsContainer() public virtual void RaiseClickEvent() { - var invokePattern = (InvokePattern) Pattern(InvokePattern.Pattern); + var invokePattern = GetPattern(); if (invokePattern != null) invokePattern.Invoke(); } } diff --git a/src/TestStack.White/UIItems/WindowItems/Window.cs b/src/TestStack.White/UIItems/WindowItems/Window.cs index e66b9c72..f48bb9a6 100644 --- a/src/TestStack.White/UIItems/WindowItems/Window.cs +++ b/src/TestStack.White/UIItems/WindowItems/Window.cs @@ -91,7 +91,7 @@ public virtual string Title private WindowPattern WinPattern { - get { return (WindowPattern)Pattern(WindowPattern.Pattern); } + get { return GetPattern(); } } /// @@ -124,7 +124,7 @@ public virtual bool IsClosed public virtual void Close() { minOpenTime.Wait(); - var windowPattern = (WindowPattern)Pattern(WindowPattern.Pattern); + var windowPattern = GetPattern(); try { Logger.DebugFormat("Closing window {0}", Title); @@ -220,7 +220,7 @@ protected static void HourGlassWait() /// when window is not responding private void WaitForWindow() { - var windowPattern = (WindowPattern)Pattern(WindowPattern.Pattern); + var windowPattern = GetPattern(); if (windowPattern == null) return; var finalState = Retry.For( From 65adad016e02fa071e38ab7267120184bee07580 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Fri, 3 Jan 2014 15:36:11 +0200 Subject: [PATCH 03/12] Fixed issues with WPF ComboBox. Made ComboBox collapse itself after selection if item requested to select is already active and thus Select just returns. Absence of this code leads to problems in case when client code tries to select already selected item. Then base.Select(itemText) or base.Select(index) do nothing and combo box stays open. Next user actions may not expect this which could lead to confusing results. For example, after selection in combo box client code wants to click on check box below - White emulates click on the place where check box is located, but combo box drop down is at the place, so some another item is selected. So, we have to close combo box regardless of the fact that selection did something. For same reasons added ComboBox collapsing after getting SelectedItem and SelectedItemText. --- .../ListControls/ComboBoxTests.cs | 15 +-- .../ListControls/DataBoundComboBoxTests.cs | 19 ++-- .../Configuration/CoreAppXmlConfiguration.cs | 2 +- .../UIItems/ListBoxItems/ComboBox.cs | 106 +++++++++++++++++- .../UIItems/ListBoxItems/ListControl.cs | 20 +--- 5 files changed, 124 insertions(+), 38 deletions(-) diff --git a/src/TestStack.White.UITests/ControlTests/ListControls/ComboBoxTests.cs b/src/TestStack.White.UITests/ControlTests/ListControls/ComboBoxTests.cs index 1ed9fc01..fc609173 100644 --- a/src/TestStack.White.UITests/ControlTests/ListControls/ComboBoxTests.cs +++ b/src/TestStack.White.UITests/ControlTests/ListControls/ComboBoxTests.cs @@ -29,28 +29,29 @@ protected override void ExecuteTestRun(WindowsFramework framework) void ListItemInComboBoxWithoutTextAvailableInitially() { + var config = CoreAppXmlConfiguration.Instance; + var originalVal = config.ComboBoxItemsPopulatedWithoutDropDownOpen; try { - CoreAppXmlConfiguration.Instance.ComboBoxItemsPopulatedWithoutDropDownOpen = false; + config.ComboBoxItemsPopulatedWithoutDropDownOpen = true; Assert.Equal(0, ComboBoxUnderTest.Items.Count); } finally { - CoreAppXmlConfiguration.Instance.ComboBoxItemsPopulatedWithoutDropDownOpen = true; + config.ComboBoxItemsPopulatedWithoutDropDownOpen = originalVal; } } void ComboBoxOnlyCollapsesWhenExpansionWasForItemRetrieval() { // Arrange - var expandCollapsePattern = (ExpandCollapsePattern)ComboBoxUnderTest.AutomationElement.GetCurrentPattern(ExpandCollapsePattern.Pattern); var config = CoreAppXmlConfiguration.Instance; var originalVal = config.ComboBoxItemsPopulatedWithoutDropDownOpen; config.ComboBoxItemsPopulatedWithoutDropDownOpen = false; try { - expandCollapsePattern.Expand(); + ComboBoxUnderTest.Expand(); // Act #pragma warning disable 168 @@ -59,13 +60,13 @@ void ComboBoxOnlyCollapsesWhenExpansionWasForItemRetrieval() #pragma warning restore 168 // Assert - var expansionState = expandCollapsePattern.Current.ExpandCollapseState; + var expansionState = ComboBoxUnderTest.ExpandCollapseState; Assert.Equal(ExpandCollapseState.Expanded, expansionState); } finally { config.ComboBoxItemsPopulatedWithoutDropDownOpen = originalVal; - expandCollapsePattern.Collapse(); + ComboBoxUnderTest.Collapse(); } } @@ -85,7 +86,7 @@ void ComboBoxWithAutoExpandCollapsedOnceItemsAreRetrieved() #pragma warning restore 168 // Assert - var expansionState = (ExpandCollapseState)ComboBoxUnderTest.AutomationElement.GetCurrentPropertyValue(ExpandCollapsePattern.ExpandCollapseStateProperty); + var expansionState = ComboBoxUnderTest.ExpandCollapseState; // The combobox should have been collapsed after the items were retrieved Assert.Equal(ExpandCollapseState.Collapsed, expansionState); } diff --git a/src/TestStack.White.UITests/ControlTests/ListControls/DataBoundComboBoxTests.cs b/src/TestStack.White.UITests/ControlTests/ListControls/DataBoundComboBoxTests.cs index 256759fb..d73d57c8 100644 --- a/src/TestStack.White.UITests/ControlTests/ListControls/DataBoundComboBoxTests.cs +++ b/src/TestStack.White.UITests/ControlTests/ListControls/DataBoundComboBoxTests.cs @@ -18,21 +18,24 @@ protected override void ExecuteTestRun(WindowsFramework framework) private void CanSelectDataboundItems() { - ListItems items; - CoreAppXmlConfiguration.Instance.ComboBoxItemsPopulatedWithoutDropDownOpen = false; + var config = CoreAppXmlConfiguration.Instance; + bool oldValue = config.ComboBoxItemsPopulatedWithoutDropDownOpen; + config.ComboBoxItemsPopulatedWithoutDropDownOpen = true; try { - items = ComboBoxUnderTest.Items; + ListItems items = ComboBoxUnderTest.Items; Assert.Equal(0, items.Count); + + config.ComboBoxItemsPopulatedWithoutDropDownOpen = false; + + items = ComboBoxUnderTest.Items; + Assert.Equal(5, items.Count); + Assert.Equal("Test", items[0].Text); } finally { - CoreAppXmlConfiguration.Instance.ComboBoxItemsPopulatedWithoutDropDownOpen = true; + config.ComboBoxItemsPopulatedWithoutDropDownOpen = oldValue; } - - items = ComboBoxUnderTest.Items; - Assert.Equal(5, items.Count); - Assert.Equal("Test", items[0].Text); } protected override IEnumerable SupportedFrameworks() diff --git a/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs b/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs index d69d45d5..1cc46279 100644 --- a/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs +++ b/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs @@ -29,7 +29,7 @@ static CoreAppXmlConfiguration() DefaultValues.Add("DefaultDateFormat", DateFormat.CultureDefault.ToString()); DefaultValues.Add("DragStepCount", 1); DefaultValues.Add("InProc", false); - DefaultValues.Add("ComboBoxItemsPopulatedWithoutDropDownOpen", true); + DefaultValues.Add("ComboBoxItemsPopulatedWithoutDropDownOpen", false); DefaultValues.Add("RawElementBasedSearch", false); DefaultValues.Add("MaxElementSearchDepth", 10); DefaultValues.Add("DoubleClickInterval", 0); diff --git a/src/TestStack.White/UIItems/ListBoxItems/ComboBox.cs b/src/TestStack.White/UIItems/ListBoxItems/ComboBox.cs index 63aaed11..0ef4d3ad 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ComboBox.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ComboBox.cs @@ -1,6 +1,10 @@ +using System; +using System.Threading; using System.Windows.Automation; using TestStack.White.AutomationElementSearch; +using TestStack.White.Configuration; using TestStack.White.Recording; +using TestStack.White.UIA; using TestStack.White.UIItemEvents; using TestStack.White.UIItems.Actions; using TestStack.White.UIItems.Scrolling; @@ -9,6 +13,31 @@ namespace TestStack.White.UIItems.ListBoxItems { public class ComboBox : ListControl { + private class TempExpand : IDisposable + { + private readonly ComboBox parent; + private readonly bool needsCollapse; + + private TempExpand(ComboBox parent) + { + this.parent = parent; + needsCollapse = parent.Expand(); + parent.Logger.DebugFormat("TempExpand called. Expansion made: {0}", needsCollapse); + } + + public void Dispose() + { + parent.Logger.DebugFormat("TempExpand finished. Will be collapsed: {0}", needsCollapse); + if (needsCollapse) + parent.Collapse(); + } + + public static TempExpand IfNeeded(ComboBox parent) + { + return new TempExpand(parent); + } + } + private AutomationPropertyChangedEventHandler handler; private ListItem lastSelectedItem; @@ -55,6 +84,55 @@ protected virtual TextBox EditableItem } } + public override ListItems Items + { + get + { + using (TempExpand.IfNeeded(this)) + return base.Items; + } + } + + public virtual bool Expand() + { + if (CoreAppXmlConfiguration.Instance.ComboBoxItemsPopulatedWithoutDropDownOpen) return false; + if (!Enabled) return false; + + var ecp = AutomationElement.GetPattern(); + if (ecp != null && ecp.Current.ExpandCollapseState == ExpandCollapseState.Collapsed) + { + ecp.Expand(); + Thread.Sleep(50); + return true; + } + return false; + } + + public virtual bool Collapse() + { + var ecp = AutomationElement.GetPattern(); + if (ecp != null && ecp.Current.ExpandCollapseState == ExpandCollapseState.Expanded) + { + ecp.Collapse(); + return true; + } + return false; + } + + public virtual ExpandCollapseState ExpandCollapseState + { + get + { + var ecp = AutomationElement.GetPattern(); + if (ecp != null) + { + var state = ecp.Current.ExpandCollapseState; + return state; + } + return ExpandCollapseState.LeafNode; + } + } + public virtual bool IsEditable { get { return EditableElement() != null;} @@ -72,8 +150,11 @@ public override void Select(string itemText) Logger.WarnFormat("Could not select {0}in {1} since it is disabled", itemText, Name); return; } - if (Equals(itemText, SelectedItemText)) return; - base.Select(itemText); + using (TempExpand.IfNeeded(this)) + { + if (Equals(itemText, SelectedItemText)) return; + base.Select(itemText); + } } public override void Select(int index) @@ -83,7 +164,26 @@ public override void Select(int index) Logger.Warn("Could not select " + index + "in " + Name + " since it is disabled"); return; } - base.Select(index); + using (TempExpand.IfNeeded(this)) + base.Select(index); + } + + public override string SelectedItemText + { + get + { + using (TempExpand.IfNeeded(this)) + return base.SelectedItemText; + } + } + + public override ListItem SelectedItem + { + get + { + using (TempExpand.IfNeeded(this)) + return base.SelectedItem; + } } public override void HookEvents(UIItemEventListener eventListener) diff --git a/src/TestStack.White/UIItems/ListBoxItems/ListControl.cs b/src/TestStack.White/UIItems/ListBoxItems/ListControl.cs index 166ecc0c..02493a95 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ListControl.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ListControl.cs @@ -2,6 +2,7 @@ using System.Windows.Automation; using TestStack.White.AutomationElementSearch; using TestStack.White.Configuration; +using TestStack.White.UIA; using TestStack.White.UIItems.Actions; using TestStack.White.UIItems.Scrolling; @@ -28,29 +29,10 @@ public virtual ListItems Items { get { - ExpandListIfNeeded(); return new ListItems(Finder.Descendants(AutomationSearchCondition.ByControlType(ControlType.ListItem)), this); } } - internal virtual void ExpandListIfNeeded() - { - if (!CoreAppXmlConfiguration.Instance.ComboBoxItemsPopulatedWithoutDropDownOpen) return; - if (!Enabled) return; - - object expandCollapse; - - if (!AutomationElement.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out expandCollapse)) return; - - var state = (ExpandCollapseState) automationElement - .GetCurrentPropertyValue(ExpandCollapsePattern.ExpandCollapseStateProperty); - if (state == ExpandCollapseState.Collapsed) - { - ((ExpandCollapsePattern)expandCollapse).Expand(); - Thread.Sleep(50); - } - } - public virtual ListItem Item(string itemText) { return Items.Item(itemText); From 04f890a1f6beca159244432c175135950aa25498 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Fri, 3 Jan 2014 18:28:04 +0200 Subject: [PATCH 04/12] Without this change ComboBox expansion lags behind SelectionItemPattern.Select() which throws InvalidOperationException for some reason. --- src/TestStack.White/UIItems/ListBoxItems/ListItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs index d57e6d51..d89895b5 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs @@ -70,6 +70,7 @@ private static void WaitForBoundsToStabilise(IUIItem item) Thread.Sleep(10); return oldBounds.Equals(item.Bounds); }, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(10)); + Thread.Sleep(100); } public abstract void Check(); From 019d7ec9e93118c68ceb7b0ef33a986fe97f1d1a Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 6 Jan 2014 19:39:29 +0200 Subject: [PATCH 05/12] Fixes possible COMException during Window.WaitForProcess() execution. If process exited already, then automationElement.Current.ProcessId throws COMException with E_FAIL. Hence, instead of getting processId every time, we'll take everything we need at ctor time and just use it afterwards. --- .../UIItems/WindowItems/Window.cs | 23 +++++++++++++++++-- .../WindowsAPI/NativeWindow.cs | 3 +++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/TestStack.White/UIItems/WindowItems/Window.cs b/src/TestStack.White/UIItems/WindowItems/Window.cs index f48bb9a6..8df6392d 100644 --- a/src/TestStack.White/UIItems/WindowItems/Window.cs +++ b/src/TestStack.White/UIItems/WindowItems/Window.cs @@ -20,6 +20,7 @@ using TestStack.White.UIItems.MenuItems; using TestStack.White.UIItems.WindowStripControls; using TestStack.White.Utility; +using TestStack.White.WindowsAPI; using Action = TestStack.White.UIItems.Actions.Action; namespace TestStack.White.UIItems.WindowItems @@ -35,6 +36,9 @@ public abstract class Window : UIItemContainer, IDisposable new Dictionary(); private AutomationEventHandler handler; + private Process ownerProcess; + private uint ownerThreadId; + /// /// If a window is opened then you try and close it straight away, the window can fail to close /// @@ -77,6 +81,11 @@ private void InitializeWindow() Title, Bounds, bounds); } WindowSession.Register(this); + + var hwnd = new IntPtr(automationElement.Current.NativeWindowHandle); + int ownerProcessId; + ownerThreadId = NativeWindow.GetWindowThreadProcessId(hwnd, out ownerProcessId); + ownerProcess = Process.GetProcessById(ownerProcessId); } protected override ActionListener ChildrenActionListener @@ -124,6 +133,8 @@ public virtual bool IsClosed public virtual void Close() { minOpenTime.Wait(); + ownerProcess.Dispose(); + ownerProcess = null; var windowPattern = GetPattern(); try { @@ -235,10 +246,18 @@ private void WaitForWindow() } } - /// when current process is not available any more (id expired) protected virtual void WaitForProcess() { - Process.GetProcessById(automationElement.Current.ProcessId).WaitForInputIdle(); + if (ownerProcess == null || ownerProcess.HasExited) return; + try + { + ownerProcess.WaitForInputIdle(); + } + catch (InvalidOperationException) + { + // process might be exited after the check, but before WaitForInputIdle() call + // in this case we will do nothing + } } public override void ActionPerformed(Action action) diff --git a/src/TestStack.White/WindowsAPI/NativeWindow.cs b/src/TestStack.White/WindowsAPI/NativeWindow.cs index f8ce0006..33ba3d7d 100644 --- a/src/TestStack.White/WindowsAPI/NativeWindow.cs +++ b/src/TestStack.White/WindowsAPI/NativeWindow.cs @@ -20,6 +20,9 @@ public class NativeWindow [DllImport("gdi32.dll")] private static extern COLORREF GetTextColor(IntPtr hdc); + [DllImport("user32.dll", SetLastError = true)] + internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + public NativeWindow(Point point) { handle = WindowFromPoint(new POINT((int) point.X, (int) point.Y)); From 801d2126b1ddb5c940e8273bfd3386e0ef69784e Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 01:40:22 +0200 Subject: [PATCH 06/12] Fixes #186: TextBox.Text property assignment types only part of text. --- src/TestStack.White/InputDevices/Keyboard.cs | 40 ++---------- src/TestStack.White/InputDevices/Mouse.cs | 64 ++++--------------- src/TestStack.White/InputDevices/Native.cs | 52 +++++++++++++++ src/TestStack.White/TestStack.White.csproj | 1 + src/TestStack.White/UIItems/TextBox.cs | 9 ++- src/TestStack.White/UIItems/UIItem.cs | 4 ++ .../WindowsAPI/NativeWindow.cs | 19 ++++++ 7 files changed, 103 insertions(+), 86 deletions(-) create mode 100644 src/TestStack.White/InputDevices/Native.cs diff --git a/src/TestStack.White/InputDevices/Keyboard.cs b/src/TestStack.White/InputDevices/Keyboard.cs index e959d01c..586bf7a2 100644 --- a/src/TestStack.White/InputDevices/Keyboard.cs +++ b/src/TestStack.White/InputDevices/Keyboard.cs @@ -1,6 +1,5 @@ -using System; using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Threading; using TestStack.White.UIItems.Actions; using TestStack.White.WindowsAPI; using Action = TestStack.White.UIItems.Actions.Action; @@ -30,21 +29,6 @@ public class Keyboard : IKeyboard KeyboardInput.SpecialKeys.RWIN }; - [DllImport("user32", EntryPoint = "SendInput")] - private static extern int SendInput(uint numberOfInputs, ref Input input, int structSize); - - [DllImport("user32", EntryPoint = "SendInput")] - private static extern int SendInput64(int numberOfInputs, ref Input64 input, int structSize); - - [DllImport("user32.dll")] - private static extern IntPtr GetMessageExtraInfo(); - - [DllImport("user32.dll")] - private static extern short VkKeyScan(char ch); - - [DllImport("user32.dll")] - private static extern ushort GetKeyState(uint virtKey); - private readonly List heldKeys = new List(); /// @@ -71,7 +55,7 @@ public virtual void Send(string keysToType, ActionListener actionListener) CapsLockOn = false; foreach (char c in keysToType) { - short key = VkKeyScan(c); + short key = Native.VkKeyScan(c); if (c.Equals('\r')) continue; if (ShiftKeyIsNeeded(key)) SendKeyDown((short) KeyboardInput.SpecialKeys.SHIFT, false); @@ -152,7 +136,7 @@ private void SendKeyUp(short b, bool specialKey) if (!keysHeld.Contains(b)) throw new InputDeviceException(string.Format("Cannot unpress the key {0}, it has not been pressed", b)); keysHeld.Remove(b); KeyboardInput.KeyUpDown keyUpDown = GetSpecialKeyCode(specialKey, KeyboardInput.KeyUpDown.KEYEVENTF_KEYUP); - SendInput(GetInputFor(b, keyUpDown)); + Native.SendInput(GetInputFor(b, keyUpDown)); } private static KeyboardInput.KeyUpDown GetSpecialKeyCode(bool specialKey, KeyboardInput.KeyUpDown key) @@ -166,31 +150,19 @@ private void SendKeyDown(short b, bool specialKey) if (keysHeld.Contains(b)) throw new InputDeviceException(string.Format("Cannot press the key {0} as its already pressed", b)); keysHeld.Add(b); KeyboardInput.KeyUpDown keyUpDown = GetSpecialKeyCode(specialKey, KeyboardInput.KeyUpDown.KEYEVENTF_KEYDOWN); - SendInput(GetInputFor(b, keyUpDown)); - } - - private static void SendInput(Input input) - { - // Added check for 32/64 bit - if (IntPtr.Size == 4) - SendInput(1, ref input, Marshal.SizeOf(typeof(Input))); - else - { - var input64 = new Input64(input); - SendInput64(1, ref input64, Marshal.SizeOf(typeof(Input))); - } + Native.SendInput(GetInputFor(b, keyUpDown)); } private static Input GetInputFor(short character, KeyboardInput.KeyUpDown keyUpOrDown) { - return InputFactory.Keyboard(new KeyboardInput(character, keyUpOrDown, GetMessageExtraInfo())); + return InputFactory.Keyboard(new KeyboardInput(character, keyUpOrDown, Native.GetMessageExtraInfo())); } public virtual bool CapsLockOn { get { - ushort state = GetKeyState((uint) KeyboardInput.SpecialKeys.CAPS); + ushort state = Native.GetKeyState((uint)KeyboardInput.SpecialKeys.CAPS); return state != 0; } set { if (CapsLockOn != value) Send(KeyboardInput.SpecialKeys.CAPS, true); } diff --git a/src/TestStack.White/InputDevices/Mouse.cs b/src/TestStack.White/InputDevices/Mouse.cs index 23e650a4..9b5b3a62 100644 --- a/src/TestStack.White/InputDevices/Mouse.cs +++ b/src/TestStack.White/InputDevices/Mouse.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using System.Threading; using System.Windows; using TestStack.White.Configuration; @@ -14,34 +13,9 @@ namespace TestStack.White.InputDevices { public class Mouse : IMouse { - [DllImport("user32", EntryPoint = "SendInput")] - static extern int SendInput(uint numberOfInputs, ref Input input, int structSize); - - [DllImport("user32", EntryPoint = "SendInput")] - static extern int SendInput64(int numberOfInputs, ref Input64 input, int structSize); - - [DllImport("user32.dll")] - static extern IntPtr GetMessageExtraInfo(); - - [DllImport("user32.dll")] - - static extern bool GetCursorPos(ref System.Drawing.Point cursorInfo); - - [DllImport("user32.dll")] - static extern bool SetCursorPos(int x, int y); - - [DllImport("user32.dll")] - static extern bool GetCursorInfo(ref CursorInfo cursorInfo); - - [DllImport("user32.dll")] - static extern short GetDoubleClickTime(); - - [DllImport("user32.dll")] - static extern int GetSystemMetrics(SystemMetric smIndex); - public static Mouse Instance = new Mouse(); DateTime lastClickTime = DateTime.Now; - readonly short doubleClickTime = GetDoubleClickTime(); + readonly short doubleClickTime = Native.GetDoubleClickTime(); Point lastClickLocation; const int ExtraMillisecondsBecauseOfBugInWindows = 13; @@ -52,7 +26,7 @@ public virtual Point Location get { var point = new System.Drawing.Point(); - GetCursorPos(ref point); + Native.GetCursorPos(ref point); return point.ConvertToWindowsPoint(); } set @@ -61,7 +35,7 @@ public virtual Point Location { throw new WhiteException(string.Format("Trying to set location outside the screen. {0}", value)); } - SetCursorPos((int)value.X, (int)value.Y); + Native.SetCursorPos((int)value.X, (int)value.Y); } } @@ -70,7 +44,7 @@ public virtual MouseCursor Cursor get { CursorInfo cursorInfo = CursorInfo.New(); - GetCursorInfo(ref cursorInfo); + Native.GetCursorInfo(ref cursorInfo); int i = cursorInfo.handle.ToInt32(); return new MouseCursor(i); } @@ -78,28 +52,28 @@ public virtual MouseCursor Cursor private static int RightMouseButtonDown { - get { return GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_RIGHTDOWN : WindowsConstants.MOUSEEVENTF_LEFTDOWN; } + get { return Native.GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_RIGHTDOWN : WindowsConstants.MOUSEEVENTF_LEFTDOWN; } } private static int RightMouseButtonUp { - get { return GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_RIGHTUP : WindowsConstants.MOUSEEVENTF_LEFTUP; } + get { return Native.GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_RIGHTUP : WindowsConstants.MOUSEEVENTF_LEFTUP; } } private static int LeftMouseButtonDown { - get { return GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_LEFTDOWN : WindowsConstants.MOUSEEVENTF_RIGHTDOWN; } + get { return Native.GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_LEFTDOWN : WindowsConstants.MOUSEEVENTF_RIGHTDOWN; } } private static int LeftMouseButtonUp { - get { return GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_LEFTUP : WindowsConstants.MOUSEEVENTF_RIGHTUP; } + get { return Native.GetSystemMetrics(SystemMetric.SM_SWAPBUTTON) == 0 ? WindowsConstants.MOUSEEVENTF_LEFTUP : WindowsConstants.MOUSEEVENTF_RIGHTUP; } } public virtual void RightClick() { - SendInput(InputFactory.Mouse(MouseInput(RightMouseButtonDown))); - SendInput(InputFactory.Mouse(MouseInput(RightMouseButtonUp))); + Native.SendInput(InputFactory.Mouse(MouseInput(RightMouseButtonDown))); + Native.SendInput(InputFactory.Mouse(MouseInput(RightMouseButtonUp))); } public virtual void Click() @@ -117,12 +91,12 @@ public virtual void Click() public static void LeftUp() { - SendInput(InputFactory.Mouse(MouseInput(LeftMouseButtonUp))); + Native.SendInput(InputFactory.Mouse(MouseInput(LeftMouseButtonUp))); } public static void LeftDown() { - SendInput(InputFactory.Mouse(MouseInput(LeftMouseButtonDown))); + Native.SendInput(InputFactory.Mouse(MouseInput(LeftMouseButtonDown))); } public virtual void DoubleClick(Point point) @@ -139,21 +113,9 @@ public virtual void DoubleClick(Point point, ActionListener actionListener) ActionPerformed(actionListener); } - private static void SendInput(Input input) - { - // Added check for 32/64 bit - if (IntPtr.Size == 4) - SendInput(1, ref input, Marshal.SizeOf(typeof(Input))); - else - { - var input64 = new Input64(input); - SendInput64(1, ref input64, Marshal.SizeOf(typeof(Input))); - } - } - private static MouseInput MouseInput(int command) { - return new MouseInput(command, GetMessageExtraInfo()); + return new MouseInput(command, Native.GetMessageExtraInfo()); } public virtual void RightClick(Point point, ActionListener actionListener) diff --git a/src/TestStack.White/InputDevices/Native.cs b/src/TestStack.White/InputDevices/Native.cs new file mode 100644 index 00000000..805ca041 --- /dev/null +++ b/src/TestStack.White/InputDevices/Native.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using TestStack.White.WindowsAPI; + +namespace TestStack.White.InputDevices +{ + static class Native + { + [DllImport("user32", EntryPoint = "SendInput")] + internal static extern int SendInput(uint numberOfInputs, ref Input input, int structSize); + + [DllImport("user32", EntryPoint = "SendInput")] + private static extern int SendInput64(int numberOfInputs, ref Input64 input, int structSize); + + [DllImport("user32.dll")] + internal static extern IntPtr GetMessageExtraInfo(); + + [DllImport("user32.dll")] + internal static extern bool GetCursorPos(ref System.Drawing.Point cursorInfo); + + [DllImport("user32.dll")] + internal static extern bool SetCursorPos(int x, int y); + + [DllImport("user32.dll")] + internal static extern bool GetCursorInfo(ref CursorInfo cursorInfo); + + [DllImport("user32.dll")] + internal static extern short GetDoubleClickTime(); + + [DllImport("user32.dll")] + internal static extern int GetSystemMetrics(SystemMetric smIndex); + + [DllImport("user32.dll")] + internal static extern short VkKeyScan(char ch); + + [DllImport("user32.dll")] + internal static extern ushort GetKeyState(uint virtKey); + + internal static void SendInput(Input input) + { + // Added check for 32/64 bit + if (IntPtr.Size == 4) + SendInput(1, ref input, Marshal.SizeOf(typeof (Input))); + else + { + var input64 = new Input64(input); + SendInput64(1, ref input64, Marshal.SizeOf(typeof (Input))); + } + } + } +} \ No newline at end of file diff --git a/src/TestStack.White/TestStack.White.csproj b/src/TestStack.White/TestStack.White.csproj index fef346ac..ad2ce39d 100644 --- a/src/TestStack.White/TestStack.White.csproj +++ b/src/TestStack.White/TestStack.White.csproj @@ -95,6 +95,7 @@ + diff --git a/src/TestStack.White/UIItems/TextBox.cs b/src/TestStack.White/UIItems/TextBox.cs index 0a301225..36cce720 100644 --- a/src/TestStack.White/UIItems/TextBox.cs +++ b/src/TestStack.White/UIItems/TextBox.cs @@ -1,8 +1,11 @@ +using System; using System.Windows.Automation; using TestStack.White.Recording; using TestStack.White.UIA; using TestStack.White.UIItemEvents; using TestStack.White.UIItems.Actions; +using TestStack.White.WindowsAPI; +using Action = TestStack.White.UIItems.Actions.Action; namespace TestStack.White.UIItems { @@ -30,7 +33,11 @@ public virtual string Text throw new WhiteException(string.Format("AutomationElement for {0} supports neither ValuePattern or TextPattern", ToString())); } - set { Enter(value); } + set + { + Enter(value); + NativeWindow.WaitForInputIdle(new IntPtr(OwnerWindow.Value.Current.NativeWindowHandle), TimeSpan.FromSeconds(1)); + } } /// diff --git a/src/TestStack.White/UIItems/UIItem.cs b/src/TestStack.White/UIItems/UIItem.cs index 5f38c99c..0685c55e 100644 --- a/src/TestStack.White/UIItems/UIItem.cs +++ b/src/TestStack.White/UIItems/UIItem.cs @@ -27,6 +27,7 @@ namespace TestStack.White.UIItems //TODO ToolStrip and Similar kind of support public class UIItem : IUIItem { + private readonly static TreeWalker windowWalker = new TreeWalker(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window)); protected readonly AutomationElement automationElement; protected ActionListener actionListener; internal static readonly Mouse mouse = Mouse.Instance; @@ -46,6 +47,7 @@ public UIItem(AutomationElement automationElement, ActionListener actionListener this.automationElement = automationElement; this.actionListener = actionListener; factory = new PrimaryUIItemFactory(new AutomationElementFinder(automationElement)); + OwnerWindow = new Lazy(() => windowWalker.GetParent(automationElement)); } /// @@ -57,6 +59,8 @@ public virtual AutomationElement AutomationElement get { return automationElement; } } + protected readonly Lazy OwnerWindow; + protected virtual object Property(AutomationProperty automationProperty) { return automationElement.GetCurrentPropertyValue(automationProperty); diff --git a/src/TestStack.White/WindowsAPI/NativeWindow.cs b/src/TestStack.White/WindowsAPI/NativeWindow.cs index 33ba3d7d..df4d6374 100644 --- a/src/TestStack.White/WindowsAPI/NativeWindow.cs +++ b/src/TestStack.White/WindowsAPI/NativeWindow.cs @@ -1,6 +1,9 @@ using System; +using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Windows; +using TestStack.White.Utility; namespace TestStack.White.WindowsAPI { @@ -23,6 +26,22 @@ public class NativeWindow [DllImport("user32.dll", SetLastError = true)] internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + public static bool WaitForInputIdle(IntPtr hWnd, TimeSpan timeout) + { + int pid; + uint tid = GetWindowThreadProcessId(hWnd, out pid); + if (tid == 0) throw new ArgumentException("Window not found"); + return Retry.For(() => IsThreadIdle(pid, tid), timeout, TimeSpan.FromMilliseconds(10)); + } + + private static bool IsThreadIdle(int pid, uint tid) + { + Process prc = Process.GetProcessById(pid); + var thr = prc.Threads.Cast().First(t => tid == t.Id); + return thr.ThreadState == ThreadState.Wait && + thr.WaitReason == ThreadWaitReason.UserRequest; + } + public NativeWindow(Point point) { handle = WindowFromPoint(new POINT((int) point.X, (int) point.Y)); From 5e3fd4879787f25d647cc2202e683b8e18d12e72 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 04:31:19 +0200 Subject: [PATCH 07/12] The problem with InvalidOperationException was related to the fact that IsSelected was false when checked, but already becomes true until control flow reaches pattern execution. Hence instead of falling back to patterns immediately, we'll wait a bit to give mouse click a chance to be handled. --- src/TestStack.White/UIItems/ListBoxItems/ListItem.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs index d89895b5..ff117d7e 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs @@ -48,7 +48,7 @@ public virtual void Select(bool shouldSelect) Logger.Debug("Selecting item with Click"); WaitForBoundsToStabilise(this); mouse.Click(Bounds.ImmediateInteriorEast(), actionListener); - if (!IsSelected) + if (!Retry.For(() => IsSelected, TimeSpan.FromSeconds(1))) { Logger.Debug("Failed to select list item via click. Falling back to automation patterns"); ((SelectionItemPattern)automationElement.GetCurrentPattern(SelectionItemPattern.Pattern)).Select(); @@ -70,7 +70,6 @@ private static void WaitForBoundsToStabilise(IUIItem item) Thread.Sleep(10); return oldBounds.Equals(item.Bounds); }, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(10)); - Thread.Sleep(100); } public abstract void Check(); From c928f086464b90c23094dfee77bcd9bfc9a11868 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 04:32:17 +0200 Subject: [PATCH 08/12] Made waiting for input idle tolerate already closed window situation --- src/TestStack.White/WindowsAPI/NativeWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestStack.White/WindowsAPI/NativeWindow.cs b/src/TestStack.White/WindowsAPI/NativeWindow.cs index df4d6374..93783943 100644 --- a/src/TestStack.White/WindowsAPI/NativeWindow.cs +++ b/src/TestStack.White/WindowsAPI/NativeWindow.cs @@ -30,7 +30,7 @@ public static bool WaitForInputIdle(IntPtr hWnd, TimeSpan timeout) { int pid; uint tid = GetWindowThreadProcessId(hWnd, out pid); - if (tid == 0) throw new ArgumentException("Window not found"); + if (tid == 0) return true; // probably closed already return Retry.For(() => IsThreadIdle(pid, tid), timeout, TimeSpan.FromMilliseconds(10)); } From 786b073d2a818a0e1c650e9c4dd98eb09d0a531b Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 04:35:51 +0200 Subject: [PATCH 09/12] Cleaner solution for #186 --- src/TestStack.White/UIItems/TextBox.cs | 1 - src/TestStack.White/UIItems/UIItem.cs | 4 ---- .../UIItems/WindowItems/Window.cs | 16 +--------------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/TestStack.White/UIItems/TextBox.cs b/src/TestStack.White/UIItems/TextBox.cs index 36cce720..bf35724c 100644 --- a/src/TestStack.White/UIItems/TextBox.cs +++ b/src/TestStack.White/UIItems/TextBox.cs @@ -36,7 +36,6 @@ public virtual string Text set { Enter(value); - NativeWindow.WaitForInputIdle(new IntPtr(OwnerWindow.Value.Current.NativeWindowHandle), TimeSpan.FromSeconds(1)); } } diff --git a/src/TestStack.White/UIItems/UIItem.cs b/src/TestStack.White/UIItems/UIItem.cs index 0685c55e..5f38c99c 100644 --- a/src/TestStack.White/UIItems/UIItem.cs +++ b/src/TestStack.White/UIItems/UIItem.cs @@ -27,7 +27,6 @@ namespace TestStack.White.UIItems //TODO ToolStrip and Similar kind of support public class UIItem : IUIItem { - private readonly static TreeWalker windowWalker = new TreeWalker(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window)); protected readonly AutomationElement automationElement; protected ActionListener actionListener; internal static readonly Mouse mouse = Mouse.Instance; @@ -47,7 +46,6 @@ public UIItem(AutomationElement automationElement, ActionListener actionListener this.automationElement = automationElement; this.actionListener = actionListener; factory = new PrimaryUIItemFactory(new AutomationElementFinder(automationElement)); - OwnerWindow = new Lazy(() => windowWalker.GetParent(automationElement)); } /// @@ -59,8 +57,6 @@ public virtual AutomationElement AutomationElement get { return automationElement; } } - protected readonly Lazy OwnerWindow; - protected virtual object Property(AutomationProperty automationProperty) { return automationElement.GetCurrentPropertyValue(automationProperty); diff --git a/src/TestStack.White/UIItems/WindowItems/Window.cs b/src/TestStack.White/UIItems/WindowItems/Window.cs index 8df6392d..df08f289 100644 --- a/src/TestStack.White/UIItems/WindowItems/Window.cs +++ b/src/TestStack.White/UIItems/WindowItems/Window.cs @@ -227,23 +227,9 @@ protected static void HourGlassWait() } } - /// - /// when window is not responding private void WaitForWindow() { - var windowPattern = GetPattern(); - - if (windowPattern == null) return; - var finalState = Retry.For( - () => windowPattern.Current.WindowInteractionState, - windowState => windowState == WindowInteractionState.NotResponding, - CoreAppXmlConfiguration.Instance.BusyTimeout()); - if (finalState == WindowInteractionState.NotResponding) - { - const string format = "Window didn't come out of WaitState{0} last state known was {1}"; - var message = string.Format(format, Constants.BusyMessage, windowPattern.Current.WindowInteractionState); - throw new UIActionException(message); - } + NativeWindow.WaitForInputIdle(new IntPtr(AutomationElement.Current.NativeWindowHandle), TimeSpan.FromSeconds(1)); } protected virtual void WaitForProcess() From d88fd3ec7b1f3e4ae41bb0b912b877222b006c6f Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 14:11:07 +0200 Subject: [PATCH 10/12] Cleanup: used GetPattern() in rest of places --- .../ControlTests/ListControls/CheckedListBoxTest.cs | 3 +-- src/TestStack.White/UIItems/ListBoxItems/ListItem.cs | 7 +++---- src/TestStack.White/UIItems/Scrolling/WPFScrollBar.cs | 3 ++- src/TestStack.White/UIItems/Spinner.cs | 3 ++- src/TestStack.White/UIItems/UIItem.cs | 7 +++---- src/TestStack.White/UIItems/WpfDatePicker.cs | 3 +-- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/TestStack.White.UITests/ControlTests/ListControls/CheckedListBoxTest.cs b/src/TestStack.White.UITests/ControlTests/ListControls/CheckedListBoxTest.cs index f9d428d3..1e1da53e 100644 --- a/src/TestStack.White.UITests/ControlTests/ListControls/CheckedListBoxTest.cs +++ b/src/TestStack.White.UITests/ControlTests/ListControls/CheckedListBoxTest.cs @@ -29,8 +29,7 @@ void CheckSelectedItem() { Assert.Equal(false, listBoxUnderTest.IsChecked("Item2")); var item = listBoxUnderTest.Item("Item2"); - ((SelectionItemPattern)item.AutomationElement.GetCurrentPattern(SelectionItemPattern.Pattern)) - .Select(); + item.GetPattern().Select(); listBoxUnderTest.Check("Item2"); Assert.Equal(true, listBoxUnderTest.IsChecked("Item2")); } diff --git a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs index ff117d7e..aeaa3835 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs @@ -39,19 +39,18 @@ public virtual void Select(bool shouldSelect) if (Bounds.IsEmpty) { Logger.Debug("Bounds empty, falling back to automation patterns"); - var selectionItemPattern = - (SelectionItemPattern) automationElement.GetCurrentPattern(SelectionItemPattern.Pattern); - selectionItemPattern.Select(); + GetPattern().Select(); } else { Logger.Debug("Selecting item with Click"); WaitForBoundsToStabilise(this); mouse.Click(Bounds.ImmediateInteriorEast(), actionListener); + if (!Retry.For(() => IsSelected, TimeSpan.FromSeconds(1))) { Logger.Debug("Failed to select list item via click. Falling back to automation patterns"); - ((SelectionItemPattern)automationElement.GetCurrentPattern(SelectionItemPattern.Pattern)).Select(); + GetPattern().Select(); } } diff --git a/src/TestStack.White/UIItems/Scrolling/WPFScrollBar.cs b/src/TestStack.White/UIItems/Scrolling/WPFScrollBar.cs index f168042d..30151a37 100644 --- a/src/TestStack.White/UIItems/Scrolling/WPFScrollBar.cs +++ b/src/TestStack.White/UIItems/Scrolling/WPFScrollBar.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Automation; +using TestStack.White.UIA; namespace TestStack.White.UIItems.Scrolling { @@ -9,7 +10,7 @@ public abstract class WpfScrollBar : IScrollBar protected WpfScrollBar(AutomationElement parent) { - ScrollPattern = (ScrollPattern) parent.GetCurrentPattern(ScrollPattern.Pattern); + ScrollPattern = parent.GetPattern(); } public abstract double Value { get; } diff --git a/src/TestStack.White/UIItems/Spinner.cs b/src/TestStack.White/UIItems/Spinner.cs index b2113e0a..6cef51e5 100644 --- a/src/TestStack.White/UIItems/Spinner.cs +++ b/src/TestStack.White/UIItems/Spinner.cs @@ -1,5 +1,6 @@ using System.Windows.Automation; using TestStack.White.AutomationElementSearch; +using TestStack.White.UIA; using TestStack.White.UIItems.Actions; namespace TestStack.White.UIItems @@ -30,7 +31,7 @@ private ValuePattern GetValuePattern() AutomationElement spinnerElementContainingValue = finder.FindChildRaw(AutomationSearchCondition.ByAutomationId(automationElement.Current.AutomationId).OfControlType(ControlType.Spinner)); if (spinnerElementContainingValue == null) throw new WhiteAssertionException("Could not find Raw Spinner Element containing the value"); - return (ValuePattern) spinnerElementContainingValue.GetCurrentPattern(ValuePattern.Pattern); + return spinnerElementContainingValue.GetPattern(); } public virtual void Increment() diff --git a/src/TestStack.White/UIItems/UIItem.cs b/src/TestStack.White/UIItems/UIItem.cs index 5f38c99c..8d012659 100644 --- a/src/TestStack.White/UIItems/UIItem.cs +++ b/src/TestStack.White/UIItems/UIItem.cs @@ -338,11 +338,10 @@ public virtual string LegacyHelpText get { var helpText = automationElement.Current.HelpText; - var automationPattern = LegacyIAccessiblePattern.Pattern; - if (string.IsNullOrEmpty(helpText) && AutomationElement.GetSupportedPatterns().Contains(automationPattern)) + var legacyIAccessiblePattern = AutomationElement.GetPattern(); + if (string.IsNullOrEmpty(helpText) && legacyIAccessiblePattern != null) { - var p = (LegacyIAccessiblePattern)AutomationElement.GetCurrentPattern(automationPattern); - helpText = p.Current.Description; + helpText = legacyIAccessiblePattern.Current.Description; } return helpText; } diff --git a/src/TestStack.White/UIItems/WpfDatePicker.cs b/src/TestStack.White/UIItems/WpfDatePicker.cs index b642f128..1d8fdf69 100644 --- a/src/TestStack.White/UIItems/WpfDatePicker.cs +++ b/src/TestStack.White/UIItems/WpfDatePicker.cs @@ -12,8 +12,7 @@ public WpfDatePicker(AutomationElement automationElement, ActionListener actionL public override void SetDate(DateTime? dateTime, DateFormat dateFormat) { - var valuePattern = (ValuePattern)AutomationElement.GetCurrentPattern(ValuePattern.Pattern); - valuePattern.SetValue(dateTime != null ? dateTime.Value.ToShortDateString() : null); + GetPattern().SetValue(dateTime != null ? dateTime.Value.ToShortDateString() : null); } } } \ No newline at end of file From 702b7ee7eae3909d5f617c0a743652dd256c3aa0 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 14:16:46 +0200 Subject: [PATCH 11/12] Added ComboBoxItemSelectionTimeout to settings so that this value could be customized --- .../Configuration/CoreAppXmlConfiguration.cs | 7 +++++++ src/TestStack.White/Configuration/CoreConfiguration.cs | 1 + src/TestStack.White/UIItems/ListBoxItems/ListItem.cs | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs b/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs index 1cc46279..b9137671 100644 --- a/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs +++ b/src/TestStack.White/Configuration/CoreAppXmlConfiguration.cs @@ -30,6 +30,7 @@ static CoreAppXmlConfiguration() DefaultValues.Add("DragStepCount", 1); DefaultValues.Add("InProc", false); DefaultValues.Add("ComboBoxItemsPopulatedWithoutDropDownOpen", false); + DefaultValues.Add("ComboBoxItemSelectionTimeout", 1000); DefaultValues.Add("RawElementBasedSearch", false); DefaultValues.Add("MaxElementSearchDepth", 10); DefaultValues.Add("DoubleClickInterval", 0); @@ -132,6 +133,12 @@ public virtual bool ComboBoxItemsPopulatedWithoutDropDownOpen set { SetUsedValue("ComboBoxItemsPopulatedWithoutDropDownOpen", value); } } + public virtual int ComboBoxItemSelectionTimeout + { + get { return Convert.ToInt32(UsedValues["ComboBoxItemSelectionTimeout"]); } + set { SetUsedValue("ComboBoxItemSelectionTimeout", value); } + } + public virtual bool MoveMouseToGetStatusOfHourGlass { get { return Convert.ToBoolean(UsedValues["MoveMouseToGetStatusOfHourGlass"]); } diff --git a/src/TestStack.White/Configuration/CoreConfiguration.cs b/src/TestStack.White/Configuration/CoreConfiguration.cs index 17d99e11..8fffacdc 100644 --- a/src/TestStack.White/Configuration/CoreConfiguration.cs +++ b/src/TestStack.White/Configuration/CoreConfiguration.cs @@ -27,6 +27,7 @@ public interface ICoreConfiguration int DragStepCount { get; set; } bool InProc { get; set; } bool ComboBoxItemsPopulatedWithoutDropDownOpen { get; set; } + int ComboBoxItemSelectionTimeout { get; set; } IWaitHook AdditionalWaitHook { get; set; } int MaxElementSearchDepth { get; set; } bool RawElementBasedSearch { get; set; } diff --git a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs index aeaa3835..e2d8153c 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Windows.Automation; +using TestStack.White.Configuration; using TestStack.White.UIA; using TestStack.White.UIItems.Actions; using TestStack.White.Utility; @@ -47,7 +48,8 @@ public virtual void Select(bool shouldSelect) WaitForBoundsToStabilise(this); mouse.Click(Bounds.ImmediateInteriorEast(), actionListener); - if (!Retry.For(() => IsSelected, TimeSpan.FromSeconds(1))) + var timeout = TimeSpan.FromMilliseconds(CoreAppXmlConfiguration.Instance.ComboBoxItemSelectionTimeout); + if (!Retry.For(() => IsSelected, timeout)) { Logger.Debug("Failed to select list item via click. Falling back to automation patterns"); GetPattern().Select(); From 82ba213898de96d6162b044374727bff281e5233 Mon Sep 17 00:00:00 2001 From: ivan-danilov Date: Mon, 13 Jan 2014 14:24:24 +0200 Subject: [PATCH 12/12] Added some precaution for ListItem.Select() in case of sluggish UIs. --- .../UIItems/ListBoxItems/ListItem.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs index e2d8153c..65c6c786 100644 --- a/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs +++ b/src/TestStack.White/UIItems/ListBoxItems/ListItem.cs @@ -52,7 +52,20 @@ public virtual void Select(bool shouldSelect) if (!Retry.For(() => IsSelected, timeout)) { Logger.Debug("Failed to select list item via click. Falling back to automation patterns"); - GetPattern().Select(); + try + { + GetPattern().Select(); + } + catch (InvalidOperationException) + { + // Sometimes Select() method throws InvalidOperationException because item is + // already selected, so it can't re-select it again, but this fact is not + // discovered before, e.g. because app's UI thread is busy and hadn't handled + // SelectedItem change before timeout. + // If as a final result this item is selected - we're fine regarless of this + // exception. Otherwise we should throw again. + if (!IsSelected) throw; + } } }