uia 0.0.7.3 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/ChangeLog +10 -0
  2. data/ext/UiaDll/Release/UIA.Helper.dll +0 -0
  3. data/ext/UiaDll/Release/UiaDll.dll +0 -0
  4. data/ext/UiaDll/UIA.Helper/Clicker.cs +1 -1
  5. data/ext/UiaDll/UIA.Helper/Element.cs +11 -0
  6. data/ext/UiaDll/UIA.Helper/Extensions.cs +8 -0
  7. data/ext/UiaDll/UiaDll/ElementMethods.cpp +8 -4
  8. data/ext/UiaDll/UiaDll/ElementStructures.h +0 -23
  9. data/ext/UiaDll/UiaDll/PatternInformationStructures.h +2 -22
  10. data/ext/UiaDll/UiaDll/TableMethods.cpp +11 -0
  11. data/ext/UiaDll/UiaDll/TextMethods.cpp +17 -0
  12. data/ext/UiaDll/UiaDll/UiaDll.cpp +6 -0
  13. data/ext/UiaDll/UiaDll/UiaDll.vcxproj +1 -0
  14. data/ext/UiaDll/UiaDll/UiaDll.vcxproj.filters +3 -0
  15. data/ext/UiaDll/UiaDll/WindowMethods.cpp +8 -0
  16. data/ext/UiaDll/UiaDll.Test/PatternInformationTest.cpp +0 -5
  17. data/ext/UiaDll/UiaDll.Test/UiaDll.Test.vcxproj +0 -1
  18. data/ext/UiaDll/UiaDll.Test/UiaDll.Test.vcxproj.filters +0 -3
  19. data/lib/core_ext/symbol.rb +7 -0
  20. data/lib/uia/element.rb +9 -14
  21. data/lib/uia/finder.rb +67 -1
  22. data/lib/uia/keys.rb +76 -0
  23. data/lib/uia/library/element_structs.rb +0 -12
  24. data/lib/uia/library/pattern_structs.rb +1 -6
  25. data/lib/uia/library/win32.rb +35 -0
  26. data/lib/uia/library.rb +16 -0
  27. data/lib/uia/patterns/selection.rb +1 -1
  28. data/lib/uia/patterns/table.rb +3 -3
  29. data/lib/uia/patterns/text.rb +13 -0
  30. data/lib/uia/patterns/window.rb +4 -0
  31. data/lib/uia/version.rb +3 -3
  32. data/lib/uia.rb +1 -15
  33. data/spec/spec_helper.rb +10 -0
  34. data/spec/uia/element_spec.rb +22 -11
  35. data/spec/uia/keys_spec.rb +30 -0
  36. data/spec/uia/patterns/text_spec.rb +20 -0
  37. data/spec/uia/patterns/window_spec.rb +14 -0
  38. data/spec/uia_spec.rb +9 -2
  39. data/uia.gemspec +1 -1
  40. metadata +16 -9
  41. data/ext/UiaDll/UiaDll.Test/ElementsTest.cpp +0 -22
data/ChangeLog CHANGED
@@ -1,3 +1,13 @@
1
+ === Version 0.0.8 / 2013-12-02
2
+ * Enhancements
3
+ * Uia#find_element and Element#find can locate by :title
4
+ * Element#send_keys
5
+ * Window#close
6
+ * TextPattern support to get / set the text
7
+
8
+ * Bug Fixes
9
+ * Fix issue with selecting cells in a table
10
+
1
11
  === Version 0.0.7.3 / 2013-11-02
2
12
  * Enhancements
3
13
  * Element#visible?
Binary file
Binary file
@@ -23,7 +23,7 @@ namespace UIA.Helper
23
23
  public static void MouseClick(AutomationElement element)
24
24
  {
25
25
  element.ScrollToIfPossible();
26
- element.SetFocus();
26
+ element.TryToFocus();
27
27
 
28
28
  var clickablePoint = element.GetClickablePoint();
29
29
  Cursor.Position = new System.Drawing.Point((int)clickablePoint.X, (int)clickablePoint.Y);
@@ -22,6 +22,12 @@ namespace UIA.Helper
22
22
  return (TPattern) _element.GetCurrentPattern(pattern);
23
23
  }
24
24
 
25
+ public void SendKeys(string keysToSend)
26
+ {
27
+ _element.TryToFocus();
28
+ System.Windows.Forms.SendKeys.SendWait(keysToSend);
29
+ }
30
+
25
31
  public virtual int[] RuntimeId
26
32
  {
27
33
  get { return _element.GetRuntimeId(); }
@@ -97,6 +103,11 @@ namespace UIA.Helper
97
103
  return new Element(element);
98
104
  }
99
105
 
106
+ public static Element[] From(AutomationElement[] elements)
107
+ {
108
+ return elements.Select(x => new Element(x)).ToArray();
109
+ }
110
+
100
111
  public static Element ById(string automationId)
101
112
  {
102
113
  return FindFirst(automationId.IdCondition());
@@ -30,6 +30,14 @@ namespace UIA.Helper
30
30
 
31
31
  public static class ElementExtensions
32
32
  {
33
+ public static bool TryToFocus(this AutomationElement automationElement)
34
+ {
35
+ try
36
+ {
37
+ automationElement.SetFocus();
38
+ return true;
39
+ } catch { return false; }
40
+ }
33
41
 
34
42
  public static bool ScrollToIfPossible(this AutomationElement automationElement)
35
43
  {
@@ -15,10 +15,6 @@ extern "C" {
15
15
  delete elementInformation;
16
16
  }
17
17
 
18
- __declspec(dllexport) void Element_ReleaseMany(ElementsPtr elements) {
19
- delete elements;
20
- }
21
-
22
18
  __declspec(dllexport) void Element_Refresh(ElementInformationPtr element, char* errorInfo, const int errorInfoLength) {
23
19
  try {
24
20
  element->Refresh(Find(element));
@@ -27,6 +23,14 @@ extern "C" {
27
23
  }
28
24
  }
29
25
 
26
+ __declspec(dllexport) void Element_SendKeys(ElementInformationPtr element, const char* keysToSend, char* errorInfo, const int errorInfoLength) {
27
+ try {
28
+ Find(element)->SendKeys(gcnew String(keysToSend));
29
+ } catch(Exception^ e) {
30
+ StringHelper::CopyToUnmanagedString(e->Message, errorInfo, errorInfoLength);
31
+ }
32
+ }
33
+
30
34
  __declspec(dllexport) ElementInformationPtr Element_FindById(const char* automationId, char* errorInfo, const int errorLength) {
31
35
  try {
32
36
  return ElementInformation::From(Element::ById(gcnew String(automationId)));
@@ -65,26 +65,3 @@ private:
65
65
  }
66
66
 
67
67
  } ElementInformation, *ElementInformationPtr;
68
-
69
- typedef struct _Elements {
70
- int length;
71
- ElementInformation* elements;
72
-
73
- _Elements() : length(0), elements(NULL) {}
74
-
75
- _Elements(array<Element^>^ elements) : length(0), elements(NULL) {
76
- if( nullptr == elements ) return;
77
-
78
- length = elements->Length;
79
- if( length > 0 ) this->elements = new ElementInformation[length];
80
-
81
- auto index = 0;
82
- for each(auto element in elements) {
83
- this->elements[index++].Refresh(element);
84
- }
85
- }
86
-
87
- ~_Elements() {
88
- delete[] elements;
89
- }
90
- } Elements, *ElementsPtr;
@@ -127,31 +127,11 @@ private:
127
127
  typedef struct _TableInformation {
128
128
  int RowCount;
129
129
  int ColumnCount;
130
- ElementsPtr Headers;
131
-
132
- _TableInformation(int rowCount, int columnCount, ...array<Element^> ^headers) {
133
- init(rowCount, columnCount, headers);
134
- }
135
130
 
136
131
  _TableInformation(TablePattern::TablePatternInformation^ tableInfo) {
137
- auto headers = tableInfo->GetColumnHeaders();
138
- auto toElementFunc = gcnew Func<AutomationElement^, Element^>(Element::From);
139
-
140
- init(tableInfo->RowCount, tableInfo->ColumnCount, Enumerable::ToArray(Enumerable::Select<AutomationElement^, Element^>(headers, toElementFunc)));
141
- }
142
-
143
- ~_TableInformation() {
144
- delete Headers;
132
+ RowCount = tableInfo->RowCount;
133
+ ColumnCount = tableInfo->ColumnCount;
145
134
  }
146
-
147
- private:
148
- void init(int rowCount, int columnCount, ...array<Element^> ^headers) {
149
- RowCount = rowCount;
150
- ColumnCount = columnCount;
151
-
152
- Headers = new Elements(headers);
153
- }
154
-
155
135
  } TableInformation, *TableInformationPtr;
156
136
 
157
137
  typedef struct _TableItemInformation {
@@ -13,4 +13,15 @@ extern "C" {
13
13
  return NULL;
14
14
  }
15
15
  }
16
+
17
+ __declspec(dllexport) int Table_Headers(ElementInformationPtr element, ElementInformation** headers, char* errorInfo, const int errorInfoLength) {
18
+ try {
19
+ auto headerElements = Find(element)->As<TablePattern^>(TablePattern::Pattern)->Current.GetColumnHeaders();
20
+ *headers = ElementInformation::From(Element::From(headerElements));
21
+ return headerElements->Length;
22
+ } catch(Exception^ e) {
23
+ StringHelper::CopyToUnmanagedString(e->Message, errorInfo, errorInfoLength);
24
+ return 0;
25
+ }
26
+ }
16
27
  }
@@ -0,0 +1,17 @@
1
+ #include "Stdafx.h"
2
+
3
+ extern "C" {
4
+ __declspec(dllexport) int Text_GetText(ElementInformationPtr element, char* text, const int textLength, char* errorInfo, const int errorInfoLength) {
5
+ try {
6
+ auto theText = Find(element)->As<TextPattern^>(TextPattern::Pattern)->DocumentRange->GetText(-1);
7
+ if( NULL != text ) {
8
+ StringHelper::CopyToUnmanagedString(theText, text, textLength);
9
+ }
10
+
11
+ return theText->Length;
12
+ } catch(Exception^ e) {
13
+ StringHelper::CopyToUnmanagedString(e->Message, errorInfo, errorInfoLength);
14
+ return 0;
15
+ }
16
+ }
17
+ }
@@ -5,5 +5,11 @@ extern "C" {
5
5
  __declspec(dllexport) void initialize(char* privateAssemblyDirectory) {
6
6
  DynamicAssemblyResolver::PrivatePath = gcnew String(privateAssemblyDirectory);
7
7
  }
8
+
9
+ __declspec(dllexport) void Automation_WarmUp() {
10
+ try {
11
+ auto warmed = Element::Windows;
12
+ } catch(Exception^) {}
13
+ }
8
14
  }
9
15
 
@@ -101,6 +101,7 @@
101
101
  </ClCompile>
102
102
  <ClCompile Include="TableItemMethods.cpp" />
103
103
  <ClCompile Include="TableMethods.cpp" />
104
+ <ClCompile Include="TextMethods.cpp" />
104
105
  <ClCompile Include="ToggleMethods.cpp" />
105
106
  <ClCompile Include="UiaDll.cpp" />
106
107
  <ClCompile Include="ValuePatternMethods.cpp" />
@@ -83,5 +83,8 @@
83
83
  <ClCompile Include="RangeValueMethods.cpp">
84
84
  <Filter>Source Files\Patterns</Filter>
85
85
  </ClCompile>
86
+ <ClCompile Include="TextMethods.cpp">
87
+ <Filter>Source Files\Patterns</Filter>
88
+ </ClCompile>
86
89
  </ItemGroup>
87
90
  </Project>
@@ -21,4 +21,12 @@ extern "C" {
21
21
  StringHelper::CopyToUnmanagedString(e->Message, errorInfo, errorInfoLength);
22
22
  }
23
23
  }
24
+
25
+ __declspec(dllexport) void Window_Close(ElementInformationPtr element, char* errorInfo, const int errorInfoLength) {
26
+ try {
27
+ Find(element)->As<WindowPattern^>(WindowPattern::Pattern)->Close();
28
+ } catch(Exception^ e) {
29
+ StringHelper::CopyToUnmanagedString(e->Message, errorInfo, errorInfoLength);
30
+ }
31
+ }
24
32
  }
@@ -41,8 +41,3 @@ TEST(WindowInformation, CanBeInitialized) {
41
41
  ASSERT_STREQ("Minimized", windowInformation.VisualState);
42
42
  ASSERT_STREQ("Closing", windowInformation.InteractionState);
43
43
  }
44
-
45
- TEST(TableInformation, NullHeadersYieldsEmptyElements) {
46
- auto tableInformation = TableInformation(0, 0, nullptr);
47
- ASSERT_EQ(0, tableInformation.Headers->length);
48
- }
@@ -105,7 +105,6 @@
105
105
  <ItemGroup>
106
106
  <ClCompile Include="AssemblyInfo.cpp" />
107
107
  <ClCompile Include="ElementInformationTest.cpp" />
108
- <ClCompile Include="ElementsTest.cpp" />
109
108
  <ClCompile Include="PatternInformationTest.cpp" />
110
109
  <ClCompile Include="stdafx.cpp">
111
110
  <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
@@ -52,9 +52,6 @@
52
52
  <ClCompile Include="ElementInformationTest.cpp">
53
53
  <Filter>Source Files</Filter>
54
54
  </ClCompile>
55
- <ClCompile Include="ElementsTest.cpp">
56
- <Filter>Source Files</Filter>
57
- </ClCompile>
58
55
  <ClCompile Include="StringHelperTest.cpp">
59
56
  <Filter>Source Files</Filter>
60
57
  </ClCompile>
@@ -2,4 +2,11 @@ class Symbol
2
2
  def to_camelized_s
3
3
  self.to_s.split('_').map(&:capitalize).join(' ')
4
4
  end
5
+
6
+ # :selection_item => Uia::Patterns::SelectionItem
7
+ def to_pattern_const
8
+ "Uia::Patterns::#{self.to_s.capitalize}".split('::').reduce(Object) do |m, current|
9
+ m.const_get current.split('_').map(&:capitalize).join
10
+ end
11
+ end
5
12
  end
data/lib/uia/element.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'uia/library/element_attributes'
2
2
  require 'uia/library/pattern_info_attributes'
3
+ require 'uia/keys'
3
4
 
4
5
  module Uia
5
6
  class UnsupportedPattern < StandardError
@@ -9,6 +10,7 @@ module Uia
9
10
  end
10
11
 
11
12
  class Element
13
+ include Finder
12
14
  extend ElementAttributes
13
15
 
14
16
  def initialize(element)
@@ -24,20 +26,17 @@ module Uia
24
26
  Library::Constants::ControlTypes.find(@default) { |_, v| v == @element.control_type_id }.first
25
27
  end
26
28
 
29
+ def send_keys(*keys)
30
+ Library.send_keys @element, Keys.encode(keys)
31
+ end
32
+
27
33
  def refresh
28
34
  Library.refresh @element
29
35
  self
30
36
  end
31
37
 
32
38
  def find(locator)
33
- scope = (locator[:scope] || :descendants).to_s.capitalize
34
-
35
- case
36
- when locator[:id]
37
- Library::find_child_by_id(@element, locator[:id], scope)
38
- when locator[:name]
39
- Library::find_child_by_name(@element, locator[:name], scope)
40
- end
39
+ find_child(@element, locator)
41
40
  end
42
41
 
43
42
  def locators_match?(locator)
@@ -51,17 +50,13 @@ module Uia
51
50
  end
52
51
  end
53
52
 
54
- def select(locator)
53
+ def filter(locator)
55
54
  descendants.select { |e| e.locators_match? locator }
56
55
  end
57
56
 
58
57
  def as(pattern)
59
58
  raise UnsupportedPattern.new(pattern, patterns) unless patterns.include? pattern
60
-
61
- which = "Uia::Patterns::#{pattern.to_s.capitalize}".split('::').reduce(Object) do |m, current|
62
- m.const_get current.split('_').map(&:capitalize).join
63
- end
64
- extend which
59
+ extend pattern.to_pattern_const
65
60
  end
66
61
 
67
62
  def patterns
data/lib/uia/finder.rb CHANGED
@@ -1,13 +1,66 @@
1
+ require 'uia/library/win32'
2
+
1
3
  module Uia
4
+ class BadLocator < StandardError
5
+ def initialize(locator)
6
+ super "#{locator} is not a valid locator"
7
+ end
8
+ end
9
+ class BadChildLocator < BadLocator; end
10
+
2
11
  module Finder
12
+ def find_from_root(locator)
13
+ case
14
+ when locator[:id]
15
+ find_by_id locator[:id]
16
+ when locator[:name], locator[:value]
17
+ find_by_name locator[:name] || locator[:value]
18
+ when locator[:pid]
19
+ find_by_pid locator[:pid]
20
+ when locator[:runtime_id]
21
+ find_by_runtime_id locator[:runtime_id]
22
+ when locator[:handle]
23
+ find_by_handle locator[:handle]
24
+ when locator[:title]
25
+ find_by_title locator[:title]
26
+ else
27
+ raise BadLocator, locator
28
+ end
29
+ end
30
+
31
+ def find_child(parent, locator)
32
+ scope = (locator[:scope] || :descendants).to_s.capitalize
33
+
34
+ case
35
+ when locator[:id]
36
+ find_child_by_id parent, locator[:id], scope
37
+ when locator[:name], locator[:value]
38
+ name_or_value = locator[:name] || locator[:value]
39
+ find_child_by_name parent, name_or_value, scope
40
+ when locator[:title]
41
+ find_by_title locator[:title], parent.handle
42
+ else
43
+ raise BadChildLocator, locator
44
+ end
45
+ end
46
+
47
+ private
3
48
  def find_by_id(id)
4
49
  find_by_property(:id, id)
5
50
  end
6
51
 
52
+ def find_child_by_id(parent, id, scope)
53
+ Library.find_child_by_id(parent, id, (scope || :descendants).to_s.capitalize)
54
+ end
55
+
7
56
  def find_by_name(name)
8
57
  find_by_property(:name, name)
9
58
  end
10
59
 
60
+ def find_child_by_name(parent, name, scope)
61
+ Library.find_child_by_name(parent, name, (scope || :descendants).to_s.capitalize)
62
+ end
63
+
11
64
  def find_by_pid(pid)
12
65
  Library.find_by_pid(pid)
13
66
  end
@@ -20,7 +73,20 @@ module Uia
20
73
  Library.find_by_handle handle
21
74
  end
22
75
 
23
- private
76
+ def find_by_title(title, parent=0)
77
+ found_window = Win32.find_window(parent) do |handle|
78
+ case title
79
+ when Regexp
80
+ Win32.window_title(handle) =~ title
81
+ else
82
+ Win32.window_title(handle) == title
83
+ end
84
+ end
85
+ find_by_handle found_window if found_window
86
+ rescue
87
+ nil
88
+ end
89
+
24
90
  def find_by_property(property, what)
25
91
  case what
26
92
  when String
data/lib/uia/keys.rb ADDED
@@ -0,0 +1,76 @@
1
+ module Uia
2
+ class InvalidKey < StandardError; end
3
+ class Keys
4
+ # http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys(v=vs.110).aspx
5
+ KEYS = {
6
+ :shift => '+',
7
+ :control => '^',
8
+ :alt => '%',
9
+ :space => ' ',
10
+ :backspace => '{BS}',
11
+ :break => '{BREAK}',
12
+ :caps_lock => '{CAPSLOCK}',
13
+ :delete => '{DEL}',
14
+ :down => '{DOWN}',
15
+ :end => '{END}',
16
+ :enter => '{ENTER}',
17
+ :escape => '{ESC}',
18
+ :help => '{HELP}',
19
+ :home => '{HOME}',
20
+ :insert => '{INS}',
21
+ :left => '{LEFT}',
22
+ :number_lock => '{NUMLOCK}',
23
+ :page_down => '{PGDN}',
24
+ :page_up => '{PGUP}',
25
+ :print_screen => '{PRTSC}',
26
+ :right => '{RIGHT}',
27
+ :scroll_lock => '{SCROLLLOCK}',
28
+ :tab => '{TAB}',
29
+ :up => '{UP}',
30
+ :f1 => '{F1}',
31
+ :f2 => '{F2}',
32
+ :f3 => '{F3}',
33
+ :f4 => '{F4}',
34
+ :f5 => '{F5}',
35
+ :f6 => '{F6}',
36
+ :f7 => '{F7}',
37
+ :f8 => '{F8}',
38
+ :f9 => '{F9}',
39
+ :f10 => '{F10}',
40
+ :f11 => '{F11}',
41
+ :f12 => '{F12}',
42
+ :f13 => '{F13}',
43
+ :f14 => '{F14}',
44
+ :f15 => '{F15}',
45
+ :f16 => '{F16}',
46
+ :add => '{ADD}',
47
+ :subtract => '{SUBTRACT}',
48
+ :multiply => '{MULTIPLY}',
49
+ :divide => '{DIVIDE}'}
50
+
51
+ SPECIAL_KEYS = ['\+', '\^', '%', '~', '\(', '\)', '\{', '\}', '\[', '\]']
52
+
53
+ def self.encode(keys)
54
+ keys.reduce('') do |encoded, key|
55
+ encoded << case key
56
+ when String
57
+ encode_str(key)
58
+ when Symbol
59
+ encode_sym(key)
60
+ when Array
61
+ "#{encode([key.shift])}(#{encode(key)})"
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.encode_str(s)
67
+ s.gsub(/([#{SPECIAL_KEYS.join ''}])/, '{\1}')
68
+ end
69
+
70
+ def self.encode_sym(sym)
71
+ found = KEYS[sym]
72
+ raise InvalidKey, "#{sym} is not a valid key" unless found
73
+ found
74
+ end
75
+ end
76
+ end
@@ -63,17 +63,5 @@ module Uia
63
63
  class ElementStruct < FFI::Struct
64
64
  include ElementLayout
65
65
  end
66
-
67
- class Elements < FFI::Struct
68
- layout :length, :int,
69
- :items, :pointer
70
-
71
- def children
72
- self[:length].times.collect do |i|
73
- pointer = self[:items] + i * ElementStruct.size
74
- Uia::Element.new(ElementStruct.new(pointer))
75
- end
76
- end
77
- end
78
66
  end
79
67
  end
@@ -95,15 +95,10 @@ module Uia
95
95
  extend StructAttributes
96
96
 
97
97
  layout :row_count, :int,
98
- :column_count, :int,
99
- :headers, Elements.ptr
98
+ :column_count, :int
100
99
 
101
100
  struct_attr :row_count, :column_count
102
101
 
103
- def headers
104
- self[:headers].children
105
- end
106
-
107
102
  def self.release(pointer)
108
103
  Library.release_table_info(pointer)
109
104
  end
@@ -0,0 +1,35 @@
1
+ require 'ffi'
2
+
3
+ module Win32
4
+ extend FFI::Library
5
+
6
+ ffi_lib 'user32'
7
+
8
+ callback :enum_callback, [:long, :long], :bool
9
+
10
+ attach_function :enum_child_windows, :EnumChildWindows,
11
+ [:long, :enum_callback, :long], :long
12
+ attach_function :_window_title, :GetWindowTextA,
13
+ [:long, :pointer, :int], :int
14
+ attach_function :window_title_length, :GetWindowTextLengthA,
15
+ [:long], :int
16
+
17
+ class << self
18
+ def window_title(handle)
19
+ length = window_title_length(handle) + 1
20
+ title = FFI::MemoryPointer.new :char, length
21
+ _window_title handle, title, length
22
+ title.read_string
23
+ end
24
+
25
+ def find_window(parent, &block)
26
+ found_window = nil
27
+ window_callback = FFI::Function.new(:bool, [:long, :pointer], {convention: :stdcall}) do |handle, _|
28
+ found_window = handle if block.call handle
29
+ !found_window
30
+ end
31
+ Win32.enum_child_windows parent, window_callback, 0
32
+ found_window
33
+ end
34
+ end
35
+ end
data/lib/uia/library.rb CHANGED
@@ -16,6 +16,9 @@ module Uia
16
16
  attach_function :init, :initialize, [:string], :void
17
17
  init(uia_directory)
18
18
 
19
+ attach_function :warm_up_automation, :Automation_WarmUp, [], :void
20
+ warm_up_automation
21
+
19
22
  def self.attach_throwable_function(name_alias, name, arg_types, return_type, &block)
20
23
  attach_function name, arg_types + [:pointer, :int], return_type
21
24
  define_singleton_method(name_alias) do |*args|
@@ -62,6 +65,7 @@ module Uia
62
65
  attach_function :Element_FindByRuntimeId, [:pointer, :int, :pointer, :int], ManagedElementStruct.by_ref
63
66
 
64
67
  # element methods
68
+ attach_throwable_function :send_keys, :Element_SendKeys, [:pointer, :string], :void
65
69
  attach_throwable_function :find_child_by_id, :Element_FindChildById, [:pointer, :string, :string], ManagedElementStruct.by_ref, &element_or_nil
66
70
  attach_throwable_function :find_child_by_name, :Element_FindChildByName, [:pointer, :string, :string], ManagedElementStruct.by_ref, &element_or_nil
67
71
  elements_from :children, :Element_Children, [:pointer]
@@ -72,6 +76,7 @@ module Uia
72
76
  # WindowPattern methods
73
77
  attach_throwable_function :window_information, :Window_Information, [:pointer], WindowInformation.by_ref
74
78
  attach_throwable_function :set_visual_state, :Window_SetVisualState, [:pointer, :string], :void
79
+ attach_throwable_function :close_window, :Window_Close, [:pointer], :void
75
80
 
76
81
  # ValuePattern methods
77
82
  attach_throwable_function :set_value, :Value_Set, [:pointer, :string], :void
@@ -100,6 +105,7 @@ module Uia
100
105
 
101
106
  # TablePattern methods
102
107
  attach_throwable_function :table_info, :Table_Information, [:pointer], TableInformation.by_ref
108
+ elements_from :table_headers, :Table_Headers, [:pointer]
103
109
 
104
110
  # TableItemPattern methods
105
111
  attach_throwable_function :table_item_info, :TableItem_Information, [:pointer], TableItemInformation.by_ref
@@ -108,6 +114,16 @@ module Uia
108
114
  attach_throwable_function :range_value_info, :RangeValue_Information, [:pointer], RangeValueInformation.by_ref
109
115
  attach_throwable_function :set_range_value, :RangeValue_SetValue, [:pointer, :double], :void
110
116
 
117
+ # TextPattern methods
118
+ attach_function :Text_GetText, [:pointer, :pointer, :int, :pointer, :int], :int
119
+
120
+ def self.get_text(element)
121
+ length = can_throw(:Text_GetText, element, nil, 0) + 1
122
+ p = FFI::MemoryPointer.new :pointer, length
123
+ can_throw(:Text_GetText, element, p, length)
124
+ p.read_string
125
+ end
126
+
111
127
  def self.find_by_runtime_id(id)
112
128
  p = FFI::MemoryPointer.new :int, id.count
113
129
  p.write_array_of_int(id)
@@ -2,7 +2,7 @@ module Uia
2
2
  module Patterns
3
3
  module Selection
4
4
  def selection_items
5
- select(pattern: :selection_item).map {|e| e.as :selection_item }
5
+ filter(pattern: :selection_item).map {|e| e.as :selection_item }
6
6
  end
7
7
 
8
8
  def multi_select?
@@ -3,7 +3,7 @@ module Uia
3
3
  module Table
4
4
  module Row
5
5
  def items
6
- select(pattern: :table_item).each { |e| e.as :table_item }
6
+ filter(pattern: :table_item).each { |e| e.as :table_item }
7
7
  end
8
8
  end
9
9
 
@@ -16,11 +16,11 @@ module Uia
16
16
  end
17
17
 
18
18
  def headers
19
- table_info.headers
19
+ Library.table_headers @element
20
20
  end
21
21
 
22
22
  def rows
23
- select(control_type: :data_item).each { |e| e.extend Row }
23
+ filter(control_type: :data_item).each { |e| e.extend Row }
24
24
  end
25
25
 
26
26
  private
@@ -0,0 +1,13 @@
1
+ module Uia
2
+ module Patterns
3
+ module Text
4
+ def text=(value)
5
+ send_keys [:control, :home], [:control, :shift, :end], value
6
+ end
7
+
8
+ def text
9
+ Library.get_text @element
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,6 +1,10 @@
1
1
  module Uia
2
2
  module Patterns
3
3
  module Window
4
+ def close
5
+ Library.close_window @element
6
+ end
7
+
4
8
  def visual_state
5
9
  window_information.visual_state.to_snake_case_sym
6
10
  end
data/lib/uia/version.rb CHANGED
@@ -1,3 +1,3 @@
1
- module Uia
2
- VERSION = '0.0.7.3'
3
- end
1
+ module Uia
2
+ VERSION = '0.0.8'
3
+ end
data/lib/uia.rb CHANGED
@@ -9,7 +9,6 @@ require_rel 'uia/patterns'
9
9
  require_rel 'core_ext'
10
10
 
11
11
  module Uia
12
- class BadLocator < StandardError; end
13
12
  extend Finder
14
13
 
15
14
  def self.children
@@ -17,19 +16,6 @@ module Uia
17
16
  end
18
17
 
19
18
  def self.find_element(how)
20
- case
21
- when how[:id]
22
- find_by_id how[:id]
23
- when how[:name]
24
- find_by_name how[:name]
25
- when how[:pid]
26
- find_by_pid how[:pid]
27
- when how[:runtime_id]
28
- find_by_runtime_id how[:runtime_id]
29
- when how[:handle]
30
- find_by_handle how[:handle]
31
- else
32
- raise BadLocator, "#{how} is not a valid locator"
33
- end
19
+ find_from_root(how)
34
20
  end
35
21
  end
data/spec/spec_helper.rb CHANGED
@@ -9,6 +9,16 @@ require 'uia'
9
9
 
10
10
  include Uia
11
11
 
12
+ def wait_until(timeout=10, &block)
13
+ start = Time.now
14
+ until (result = block.call) || (Time.now - start > timeout)
15
+ sleep 0.25
16
+ end
17
+
18
+ raise 'Timed out' unless result
19
+ result
20
+ end
21
+
12
22
  RSpec.configure do |config|
13
23
  config.before(:all) do
14
24
  @app = ChildProcess.build('spec/app/WindowsForms.exe').start
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Uia::Element do
4
- Given(:element) { Uia.find_element(id: 'MainFormWindow') }
5
- Given(:about_box) { Uia.find_element(id: 'AboutBox') }
4
+ Given(:element) { wait_until { Uia.find_element(id: 'MainFormWindow') } }
5
+ Given(:about_box) { wait_until { Uia.find_element(id: 'AboutBox') } }
6
6
  Given { element.as(:window).visual_state = :normal }
7
7
 
8
8
  context 'properties' do
@@ -54,15 +54,31 @@ describe Uia::Element do
54
54
  end
55
55
  end
56
56
 
57
+ context '#send_keys' do
58
+ Given(:text_field) { element.find(id: 'textField').as :value }
59
+ When { text_field.send_keys 'abcde', [:left] * 3, ' fgh ' }
60
+ Then { text_field.value == 'ab fgh cde'}
61
+ end
62
+
57
63
  context '#find' do
58
64
  context 'id' do
59
65
  Then { element.find(id: 'textField') != nil }
60
66
  Then { element.find(id: 'does not exist') == nil }
61
67
  end
62
68
 
63
- context 'name' do
69
+ context 'name, value' do
64
70
  Then { element.find(name: 'No option selected') != nil }
65
71
  Then { element.find(name: 'does not exist') == nil }
72
+ Then { element.find(value: 'No option selected') != nil }
73
+ end
74
+
75
+ context 'title' do
76
+ Then { element.find(title: /Group.*of radio/i) != nil }
77
+ end
78
+
79
+ context 'invalid' do
80
+ When(:bad_locator) { element.find(bad_locator: 123) }
81
+ Then { bad_locator.should have_failed BadLocator, "{:bad_locator=>123} is not a valid locator" }
66
82
  end
67
83
 
68
84
  context 'limiting scope' do
@@ -72,16 +88,16 @@ describe Uia::Element do
72
88
 
73
89
  context '#select' do
74
90
  context 'control_type' do
75
- When(:buttons) { element.select(control_type: :radio_button) }
91
+ When(:buttons) { element.filter(control_type: :radio_button) }
76
92
  Then { buttons.map(&:control_type) == [:radio_button] * 3 }
77
93
  end
78
94
 
79
95
  context 'pattern' do
80
- Then { element.select(pattern: :value).count == 4 }
96
+ Then { element.filter(pattern: :value).count == 4 }
81
97
  end
82
98
 
83
99
  context 'combinations' do
84
- Then { element.select(control_type: :button, name: 'About')[0].id == 'aboutButton' }
100
+ Then { element.filter(control_type: :button, name: 'About')[0].id == 'aboutButton' }
85
101
  end
86
102
  end
87
103
 
@@ -94,11 +110,6 @@ describe Uia::Element do
94
110
  Then { about_box != nil }
95
111
  And { about_box.children.find { |c| c.name == 'OK' }.click }
96
112
  end
97
-
98
- context 'disabled elements' do
99
- When(:click_disabled) { disabled_checkbox.click }
100
- Then { click_disabled.should have_failed('Target element cannot receive focus.') }
101
- end
102
113
  end
103
114
 
104
115
  context '#children' do
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uia::Keys do
4
+ def encode(*keys)
5
+ Keys.encode keys
6
+ end
7
+
8
+ Then { encode('Bacon sandwich') == 'Bacon sandwich' }
9
+ Then { encode([:control, :shift, 'a']) == '^(+a)' }
10
+
11
+ context 'special keys' do
12
+ Then { encode(:shift, :f2, :left) == '+{F2}{LEFT}' }
13
+ end
14
+
15
+ context 'special characters' do
16
+ Then { encode('1 + 2') == '1 {+} 2' }
17
+ Then { encode('1 ^^ 2') == '1 {^}{^} 2' }
18
+ Then { encode('a%b') == 'a{%}b' }
19
+ Then { encode('~ish') == '{~}ish' }
20
+ Then { encode('(1 - 2)') == '{(}1 - 2{)}' }
21
+ Then { encode('{DUDE}') == '{{}DUDE{}}' }
22
+ Then { encode('[DUDE]') == '{[}DUDE{]}' }
23
+ Then { encode('(Bacon + sand%wich^ [sweet] {}') == '{(}Bacon {+} sand{%}wich{^} {[}sweet{]} {{}{}}' }
24
+ end
25
+
26
+ context 'invalid' do
27
+ When(:bad_keys) { encode('something', :bad_key) }
28
+ Then { bad_keys.should have_failed InvalidKey, "#{:bad_key} is not a valid key" }
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uia::Patterns::Text do
4
+ Given(:text_field) do
5
+ Uia.find_element(id: 'MainFormWindow').children.find { |e| e.id == 'multiLineTextField' }.as :text
6
+ end
7
+
8
+ context '#text' do
9
+ context 'getting / setting' do
10
+ When { text_field.text = '{text} to be expected!' }
11
+ Then { text_field.text == '{text} to be expected!' }
12
+ end
13
+
14
+ context 'updating' do
15
+ Given { text_field.text = 'about to be overwritten' }
16
+ When { text_field.text = 'expected update' }
17
+ Then { text_field.text == 'expected update' }
18
+ end
19
+ end
20
+ end
@@ -28,4 +28,18 @@ describe Uia::Patterns::Window do
28
28
  Then { window.visual_state == :normal }
29
29
  end
30
30
  end
31
+
32
+ context '#close' do
33
+ def notepad_window
34
+ Uia.find_element(title: /^Untitled.*Notepad/i)
35
+ end
36
+
37
+ Given(:notepad) do
38
+ ChildProcess.build('notepad.exe').start
39
+ wait_until { notepad_window }.as :window
40
+ end
41
+ When { notepad.close }
42
+ Then { notepad_window == nil }
43
+ end
44
+
31
45
  end
data/spec/uia_spec.rb CHANGED
@@ -14,9 +14,10 @@ describe Uia do
14
14
  Then { Uia.find_element(id: 'not there') == nil }
15
15
  end
16
16
 
17
- context 'by name' do
17
+ context 'by name / value' do
18
18
  Then { expect(Uia.find_element(name: 'MainFormWindow')).to be_instance_of(Element) }
19
- Then { expect(Uia.find_element(name: /[Mm]ain.*Window/ )).to be_instance_of(Element) }
19
+ Then { expect(Uia.find_element(name: /[Mm]ain.*Window/)).to be_instance_of(Element) }
20
+ Then { expect(Uia.find_element(value: /[Mm]ain.*Window/)).to be_instance_of(Element) }
20
21
  Then { Uia.find_element(name: 'not there') == nil }
21
22
  end
22
23
 
@@ -39,6 +40,12 @@ describe Uia do
39
40
  Then { expect { Uia.find_element(handle: 0x0) }.to raise_error }
40
41
  end
41
42
 
43
+ context 'by title' do
44
+ Then { expect(Uia.find_element(title: 'MainFormWindow')).to be_instance_of(Element) }
45
+ Then { expect(Uia.find_element(title: /Main[Ff]orm/)).to be_instance_of(Element) }
46
+ Then { Uia.find_element(title: 'probably will not find') == nil }
47
+ end
48
+
42
49
  context 'invalid locators' do
43
50
  When(:bad_input) { Uia.find_element(bad: 123) }
44
51
  Then { bad_input.should have_failed(Uia::BadLocator, '{:bad=>123} is not a valid locator') }
data/uia.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
26
  spec.require_paths = ['lib']
27
27
 
28
- spec.add_runtime_dependency 'ffi'
28
+ spec.add_runtime_dependency 'ffi', '1.9.0'
29
29
  spec.add_runtime_dependency 'require_all'
30
30
 
31
31
  spec.add_development_dependency 'bundler', '~> 1.3'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7.3
4
+ version: 0.0.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-02 00:00:00.000000000 Z
12
+ date: 2013-12-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 1.9.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ! '>='
27
+ - - '='
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
29
+ version: 1.9.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: require_all
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -147,7 +147,6 @@ files:
147
147
  - ext/UiaDll/UiaDll.Test/AssemblyInfo.cpp
148
148
  - ext/UiaDll/UiaDll.Test/ElementInformationTest.cpp
149
149
  - ext/UiaDll/UiaDll.Test/ElementStub.h
150
- - ext/UiaDll/UiaDll.Test/ElementsTest.cpp
151
150
  - ext/UiaDll/UiaDll.Test/MemoryLeakDetector.h
152
151
  - ext/UiaDll/UiaDll.Test/PatternInformationTest.cpp
153
152
  - ext/UiaDll/UiaDll.Test/ReadMe.txt
@@ -179,6 +178,7 @@ files:
179
178
  - ext/UiaDll/UiaDll/StringHelper.h
180
179
  - ext/UiaDll/UiaDll/TableItemMethods.cpp
181
180
  - ext/UiaDll/UiaDll/TableMethods.cpp
181
+ - ext/UiaDll/UiaDll/TextMethods.cpp
182
182
  - ext/UiaDll/UiaDll/ToggleMethods.cpp
183
183
  - ext/UiaDll/UiaDll/UiaDll.cpp
184
184
  - ext/UiaDll/UiaDll/UiaDll.h
@@ -192,6 +192,7 @@ files:
192
192
  - lib/uia.rb
193
193
  - lib/uia/element.rb
194
194
  - lib/uia/finder.rb
195
+ - lib/uia/keys.rb
195
196
  - lib/uia/library.rb
196
197
  - lib/uia/library/constants.rb
197
198
  - lib/uia/library/element_attributes.rb
@@ -199,6 +200,7 @@ files:
199
200
  - lib/uia/library/pattern_info_attributes.rb
200
201
  - lib/uia/library/pattern_structs.rb
201
202
  - lib/uia/library/struct_attributes.rb
203
+ - lib/uia/library/win32.rb
202
204
  - lib/uia/patterns/expand_collapse.rb
203
205
  - lib/uia/patterns/invoke.rb
204
206
  - lib/uia/patterns/range_value.rb
@@ -206,6 +208,7 @@ files:
206
208
  - lib/uia/patterns/selection_item.rb
207
209
  - lib/uia/patterns/table.rb
208
210
  - lib/uia/patterns/table_item.rb
211
+ - lib/uia/patterns/text.rb
209
212
  - lib/uia/patterns/toggle.rb
210
213
  - lib/uia/patterns/value.rb
211
214
  - lib/uia/patterns/window.rb
@@ -215,6 +218,7 @@ files:
215
218
  - spec/app/WindowsForms.exe
216
219
  - spec/spec_helper.rb
217
220
  - spec/uia/element_spec.rb
221
+ - spec/uia/keys_spec.rb
218
222
  - spec/uia/patterns/expand_collapse_spec.rb
219
223
  - spec/uia/patterns/invoke_spec.rb
220
224
  - spec/uia/patterns/range_value_spec.rb
@@ -222,6 +226,7 @@ files:
222
226
  - spec/uia/patterns/selection_spec.rb
223
227
  - spec/uia/patterns/table_item_spec.rb
224
228
  - spec/uia/patterns/table_spec.rb
229
+ - spec/uia/patterns/text_spec.rb
225
230
  - spec/uia/patterns/toggle_spec.rb
226
231
  - spec/uia/patterns/value_spec.rb
227
232
  - spec/uia/patterns/window_spec.rb
@@ -244,7 +249,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
244
249
  version: '0'
245
250
  segments:
246
251
  - 0
247
- hash: 100309903
252
+ hash: 720468909
248
253
  required_rubygems_version: !ruby/object:Gem::Requirement
249
254
  none: false
250
255
  requirements:
@@ -253,7 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
253
258
  version: '0'
254
259
  segments:
255
260
  - 0
256
- hash: 100309903
261
+ hash: 720468909
257
262
  requirements: []
258
263
  rubyforge_project:
259
264
  rubygems_version: 1.8.24
@@ -266,6 +271,7 @@ test_files:
266
271
  - spec/app/WindowsForms.exe
267
272
  - spec/spec_helper.rb
268
273
  - spec/uia/element_spec.rb
274
+ - spec/uia/keys_spec.rb
269
275
  - spec/uia/patterns/expand_collapse_spec.rb
270
276
  - spec/uia/patterns/invoke_spec.rb
271
277
  - spec/uia/patterns/range_value_spec.rb
@@ -273,6 +279,7 @@ test_files:
273
279
  - spec/uia/patterns/selection_spec.rb
274
280
  - spec/uia/patterns/table_item_spec.rb
275
281
  - spec/uia/patterns/table_spec.rb
282
+ - spec/uia/patterns/text_spec.rb
276
283
  - spec/uia/patterns/toggle_spec.rb
277
284
  - spec/uia/patterns/value_spec.rb
278
285
  - spec/uia/patterns/window_spec.rb
@@ -1,22 +0,0 @@
1
- #include "stdafx.h"
2
- #include "ElementStub.h"
3
- #include <ElementStructures.h>
4
-
5
- class ElementsTest : public ::testing::Test {
6
- public:
7
- array<Element^>^ ElementsWithNames(...array<String^>^ names) {
8
- auto elements = gcnew array<Element^>(names->Length);
9
- auto index = 0;
10
- for each(auto name in names) {
11
- elements[index++] = gcnew ElementStub(name);
12
- }
13
- return elements;
14
- }
15
- };
16
-
17
- TEST_F(ElementsTest, ItCanBeInitialized)
18
- {
19
- auto elements = Elements(ElementsWithNames("First", "Second"));
20
- ASSERT_STREQ("First", elements.elements[0].name);
21
- ASSERT_STREQ("Second", elements.elements[1].name);
22
- }