WinUIScraper
- Harvest data from other windows applications (example)
- Declaratively test that your user interface displays the right information (example).
Screen Scraping
Use a declarative approach to screen scraping with WinUIScraper.
For your convenience, WinUIScraper.cs contains the entire library as a single C# file. Of course if you'd rather use the Visual Studio Solution, that's found here.
Current Document in Visual Studio
The Visual Studio example shows how to find the current document's filename by getting the selected document tab and get the name of that tab.
public static class VisualStudioSample
{
public static string GetCurrentDocument(Process visualStudioProcess)
{
// Declare what you are looking for
Node uiElementsToFind = BuildVisualStudio2010Tree();
// Pick the AutomationElement to get data from
AutomationElement mainWindowElement = GetMainWindowElement(visualStudioProcess);
// Get values of matched elements
var dictionaryOfFoundValues = new HierarchicalValueProvider(mainWindowElement).GetValues(uiElementsToFind);
// Read the flattened tree
List<string> files = dictionaryOfFoundValues["file"];
return files.FirstOrDefault();
}
static Node BuildVisualStudio2010Tree()
{
// AnonymousNodes have no name or values, but they are great as landmarks.
// ValueNodes represent the values you are looking for.
return
new AnonymousNode(DescendentDocumentGroups()) // Find DocumentGroup
{ // Foreach DocumentGroup
new AnonymousNode(SelectedChildren()) // Find selected document
{ // Foreach document
new ValueNode("file", GetName), // Save name as "file"
}
};
}
static AutomationElement GetMainWindowElement(Process process)
{
return AutomationElement.FromHandle(process.MainWindowHandle);
}
static Func<AutomationElement, IEnumerable<AutomationElement>> DescendentDocumentGroups()
{
return element => element.FindDescendantsByControlType(ControlType.Tab).Where(e => "DocumentGroup" == e.GetClassName());
}
static Func<AutomationElement, IEnumerable<AutomationElement>> SelectedChildren()
{
return element => element.FindChildrenBy(SelectionItemPattern.IsSelectedProperty, true);
}
static string GetName(AutomationElement element)
{
return element.GetName();
}
}
// This HierarchicalValueProvider using UIAutomation. Keys are strings, and values are strings.
// But of course you can use different types of library, keys, or values.
class HierarchicalValueProvider : HierarchicalValueProvider<AutomationElement, string, string>
{
public HierarchicalValueProvider(AutomationElement element)
: base(element)
{
}
}
// These are trivial alias types to be more descriptive.
class Node : KeyedTreeNode<string, IElementsProvider<AutomationElement, string>>
{...}
class ValueNode : Node
{...}
class AnonymousNode : Node
{...}
class AutomationElementsProvider : DelegatedElementsProvider<AutomationElement, string>
{...}
Notepad Example
The notepad example shows a MSTest unit test verifying that expected menus exist in Notepad.
[TestClass]
public class NotepadTests
{
private static readonly string[] NotepadMenuNames = new[] {"File", "Edit", "Format", "View", "Help"};
[TestMethod]
public void StartAndFindNotepad()
{
RunTestWithNotepad(
windowElement => Assert.AreEqual("Untitled - Notepad", windowElement.GetName()));
}
private static void RunTestWithNotepad(Action<AutomationElement> test)
{
using (var process = StartNotepadAndWaitForInputIdle())
{
try
{
test(GetMainWindowOf(process));
}
finally
{
process.Kill();
}
}
}
private static Process StartNotepadAndWaitForInputIdle()
{
var process = Process.Start(@"notepad.exe");
if (process == null)
throw new Exception("Notepad did not start");
process.WaitForInputIdle();
return process;
}
private static AutomationElement GetMainWindowOf(Process process)
{
return AutomationElement.FromHandle(process.MainWindowHandle);
}
[TestMethod]
public void EnsureNotepadHasExpectedMenus()
{
RunTestWithNotepad(EnsureNotepadHasExpectedMenus);
}
private static void EnsureNotepadHasExpectedMenus(AutomationElement windowElement)
{
var menuBar = windowElement.FindFirstDescendantById("MenuBar");
menuBar.ExecuteWithUpdatedCache(
AutomationExtensions.BuildCacheRequest(TreeScope.Element | TreeScope.Children, AutomationElement.NameProperty),
m => CollectionAssert.AreEqual(NotepadMenuNames, GetChildNames(m)));
}
private static List<string> GetChildNames(AutomationElement ae)
{
return ae.CachedChildren.Cast<AutomationElement>().Select(AutomationExtensions.GetName).ToList();
}
[TestMethod]
public void EnsureNotepadHasExpectedMenusWithHierarchicalValueProvider()
{
RunTestWithNotepad(EnsureNotepadHasExpectedMenusWithHierarchicalValueProvider);
}
private static void EnsureNotepadHasExpectedMenusWithHierarchicalValueProvider(AutomationElement windowElement)
{
var hvp = new HierarchicalValueProvider<AutomationElement, string, string>(windowElement);
var menuBar = windowElement.FindFirstDescendantById("MenuBar");
menuBar.ExecuteWithUpdatedCache(
AutomationExtensions.BuildCacheRequest(TreeScope.Element | TreeScope.Children, AutomationElement.NameProperty),
m =>
{
var values = hvp.GetValues(BuildNotepadMenuTree());
CollectionAssert.AreEqual(NotepadMenuNames, values["menu"]);
});
}
private static KeyedTreeNode<string, IElementsProvider<AutomationElement, string>> BuildNotepadMenuTree()
{
var menubar = new KeyedTreeNode<string, IElementsProvider<AutomationElement, string>>(
null,
new AutomationElementsProvider(
windowElement => new[] {windowElement.FindFirstDescendantById("MenuBar")},
null));
menubar.Add(new KeyedTreeNode<string, IElementsProvider<AutomationElement, string>>(
"menu",
new AutomationElementsProvider(
windowElement => windowElement.CachedChildren.Cast<AutomationElement>(),
AutomationExtensions.GetName)));
return menubar;
}
private class AutomationElementsProvider : DelegatedElementsProvider<AutomationElement, string>
{
public AutomationElementsProvider(Func<AutomationElement, IEnumerable<AutomationElement>> sourcesSelector, Func<AutomationElement,string> dataSelector)
: base (sourcesSelector, dataSelector)
{
}
}
}