diff options
Diffstat (limited to 'cucumber/features/support/helpers/dogtail.rb')
-rw-r--r-- | cucumber/features/support/helpers/dogtail.rb | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/cucumber/features/support/helpers/dogtail.rb b/cucumber/features/support/helpers/dogtail.rb new file mode 100644 index 00000000..2a92649b --- /dev/null +++ b/cucumber/features/support/helpers/dogtail.rb @@ -0,0 +1,233 @@ +module Dogtail + module Mouse + LEFT_CLICK = 1 + MIDDLE_CLICK = 2 + RIGHT_CLICK = 3 + end + + TREE_API_NODE_SEARCHES = [ + :button, + :child, + :childLabelled, + :childNamed, + :dialog, + :menu, + :menuItem, + :tab, + :textentry, + ] + + TREE_API_NODE_SEARCH_FIELDS = [ + :parent, + ] + + TREE_API_NODE_ACTIONS = [ + :click, + :doubleClick, + :grabFocus, + :keyCombo, + :point, + :typeText, + ] + + TREE_API_APP_SEARCHES = TREE_API_NODE_SEARCHES + [ + :dialog, + :window, + ] + + # We want to keep this class immutable so that handles always are + # left intact when doing new (proxied) method calls. This way we + # can support stuff like: + # + # app = Dogtail::Application.new('gedit') + # menu = app.menu('Menu') + # menu.click() + # menu.something_else() + # menu.click() + # + # i.e. the object referenced by `menu` is never modified by method + # calls and can be used as expected. + + class Application + @@node_counter ||= 0 + + def initialize(app_name, opts = {}) + @var = "node#{@@node_counter += 1}" + @app_name = app_name + @opts = opts + @opts[:user] ||= LIVE_USER + @find_code = "dogtail.tree.root.application('#{@app_name}')" + script_lines = [ + "import dogtail.config", + "import dogtail.tree", + "import dogtail.predicate", + "dogtail.config.logDebugToFile = False", + "dogtail.config.logDebugToStdOut = False", + "dogtail.config.blinkOnActions = True", + "dogtail.config.searchShowingOnly = True", + "#{@var} = #{@find_code}", + ] + run(script_lines) + end + + def to_s + @var + end + + def run(code) + code = code.join("\n") if code.class == Array + c = RemoteShell::PythonCommand.new($vm, code, user: @opts[:user]) + if c.failure? + raise RuntimeError.new("The Dogtail script raised: #{c.exception}") + end + return c + end + + def child?(*args) + !!child(*args) + rescue + false + end + + def exist? + run("dogtail.config.searchCutoffCount = 0") + run(@find_code) + return true + rescue + return false + ensure + run("dogtail.config.searchCutoffCount = 20") + end + + def self.value_to_s(v) + if v == true + 'True' + elsif v == false + 'False' + elsif v.class == String + "'#{v}'" + elsif [Fixnum, Float].include?(v.class) + v.to_s + else + raise "#{self.class.name} does not know how to handle argument type '#{v.class}'" + end + end + + # Generates a Python-style parameter list from `args`. If the last + # element of `args` is a Hash, it's used as Python's kwargs dict. + # In the end, the resulting string should be possible to copy-paste + # into the parentheses of a Python function call. + # Example: [42, {:foo => 'bar'}] => "42, foo = 'bar'" + def self.args_to_s(args) + return "" if args.size == 0 + args_list = args + args_hash = nil + if args_list.class == Array && args_list.last.class == Hash + *args_list, args_hash = args_list + end + ( + (args_list.nil? ? [] : args_list.map { |e| self.value_to_s(e) }) + + (args_hash.nil? ? [] : args_hash.map { |k, v| "#{k}=#{self.value_to_s(v)}" }) + ).join(', ') + end + + # Equivalent to the Tree API's Node.findChildren(), with the + # arguments constructing a GenericPredicate to use as parameter. + def children(*args) + non_predicates = [:recursive, :showingOnly] + findChildren_opts = [] + findChildren_opts_hash = Hash.new + if args.last.class == Hash + args_hash = args.last + non_predicates.each do |opt| + if args_hash.has_key?(opt) + findChildren_opts_hash[opt] = args_hash[opt] + args_hash.delete(opt) + end + end + end + findChildren_opts = "" + if findChildren_opts_hash.size > 0 + findChildren_opts = ", " + self.class.args_to_s([findChildren_opts_hash]) + end + predicate_opts = self.class.args_to_s(args) + nodes_var = "nodes#{@@node_counter += 1}" + find_script_lines = [ + "#{nodes_var} = #{@var}.findChildren(dogtail.predicate.GenericPredicate(#{predicate_opts})#{findChildren_opts})", + "print(len(#{nodes_var}))", + ] + size = run(find_script_lines).stdout.chomp.to_i + return size.times.map do |i| + Node.new("#{nodes_var}[#{i}]", @opts) + end + end + + def get_field(key) + run("print(#{@var}.#{key})").stdout.chomp + end + + def set_field(key, value) + run("#{@var}.#{key} = #{self.class.value_to_s(value)}") + end + + def text + get_field('text') + end + + def text=(value) + set_field('text', value) + end + + def name + get_field('name') + end + + def roleName + get_field('roleName') + end + + TREE_API_APP_SEARCHES.each do |method| + define_method(method) do |*args| + args_str = self.class.args_to_s(args) + method_call = "#{method.to_s}(#{args_str})" + Node.new("#{@var}.#{method_call}", @opts) + end + end + + TREE_API_NODE_SEARCH_FIELDS.each do |field| + define_method(field) do + Node.new("#{@var}.#{field}", @opts) + end + end + + end + + class Node < Application + + def initialize(expr, opts = {}) + @expr = expr + @opts = opts + @opts[:user] ||= LIVE_USER + @find_code = expr + @var = "node#{@@node_counter += 1}" + run("#{@var} = #{@find_code}") + end + + TREE_API_NODE_SEARCHES.each do |method| + define_method(method) do |*args| + args_str = self.class.args_to_s(args) + method_call = "#{method.to_s}(#{args_str})" + Node.new("#{@var}.#{method_call}", @opts) + end + end + + TREE_API_NODE_ACTIONS.each do |method| + define_method(method) do |*args| + args_str = self.class.args_to_s(args) + method_call = "#{method.to_s}(#{args_str})" + run("#{@var}.#{method_call}") + end + end + + end +end |