summaryrefslogtreecommitdiffstats
path: root/cucumber/features/support/helpers/dogtail.rb
diff options
context:
space:
mode:
Diffstat (limited to 'cucumber/features/support/helpers/dogtail.rb')
-rw-r--r--cucumber/features/support/helpers/dogtail.rb233
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