Skip to content

softek/WinUIScraper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 

Repository files navigation

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)
     {
     }
  }
}

About

Harvest data from other windows applications. A declarative approach using UIAutomation or Accessibility APIs.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages