uia 0.0.7.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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
- }