LoginSignup
26
23

More than 5 years have passed since last update.

PythonでUI Automation Part 2

Posted at

PythonでUI Automation の続き
今回はボタンをクリックしてみる。

ControlPattern

UI Automationをサポートしているコントロールは、操作可能なインターフェースをContorolPatternという形式で提供している。
ボタンの場合、InvokePattern をサポートしているので、こいつを取得して、InvokePatternのメソッドである、Invoke()を呼ぶと、ボタンクリックを実現できる。
AutomationElementがInvokePatternをサポートしているかは、そのエレメントのIsInvokePatternAvailable を調べるとわかる。

def click_button(element):
    """指定したelement をIUIAutomationInvokePattern.Invoke() でクリックする

    指定したelementのIsInvokePatternAvailableプロパティがFalseの場合何もしない
    """
    isClickable = element.GetCurrentPropertyValue(UIA_IsInvokePatternAvailablePropertyId)
    if isClickable == True:
        ptn = element.GetCurrentPattern(UIA_InvokePatternId)
        ptn.QueryInterface(IUIAutomationInvokePattern).Invoke()

詳しくは以下。
UI Automation Control Patterns Overview
http://msdn.microsoft.com/en-us/library/ee671194(v=vs.85).aspx#controlpatterninterfaces

.NET版の方は日本語訳(機械翻訳だけど)あり。
UI オートメーション コントロール パターンの概要
http://msdn.microsoft.com/ja-jp/library/ms752362(v=vs.110).aspx

準備

実際にボタンをクリックしてみる前に、前回までの分含めて、一つにまとめておく。

uiauti.py
# -*- coding: utf-8 -*-
"""
uiautil : UI Automation utility

Windows上でしか動きません。
"""
import comtypes
from comtypes import CoCreateInstance
import comtypes.client
from comtypes.gen.UIAutomationClient import *

__uia = None
__root_element = None

def __init():
    global __uia, __root_element
    __uia = CoCreateInstance(CUIAutomation._reg_clsid_,
                             interface=IUIAutomation,
                             clsctx=comtypes.CLSCTX_INPROC_SERVER)
    __root_element = __uia.GetRootElement()

def get_window_element(title):
    """titleに指定したウィンドウのAutomationElement を取得する

    デスクトップ上でtitleが重複している場合、最初に見つかったものを返す
    """
    win_element = __root_element.FindFirst(TreeScope_Children,
                                           __uia.CreatePropertyCondition(
                                           UIA_NamePropertyId, title))
    return win_element

def find_control(base_element, ctltype):
    """指定したbase elementのサブツリーから指定したコントロールタイプIDを持つ
    エレメントのシーケンスを返す
    """
    condition = __uia.CreatePropertyCondition(UIA_ControlTypePropertyId, ctltype)
    ctl_elements = base_element.FindAll(TreeScope_Subtree, condition)
    return [ ctl_elements.GetElement(i) for i in range(ctl_elements.Length) ]

def lookup_by_name(elements, name):
    """指定したエレメントのシーケンスから、指定したNameプロパティを持つエレメント
    のうち、最初のものを返す。
    ヒットしなければ Noneを返す

   """
    for element in elements:
        if element.CurrentName == name:
            return element

    return None

def lookup_by_automationid(elements, id):
    """指定したエレメントのシーケンスから、指定したAutomationIdプロパティを持つ
    エレメントのうち、最初のものを返す。
    ヒットしなければ Noneを返す

   """
    for element in elements:
        if element.CurrentAutomationId == id:
            return element
    return None

def click_button(element):
    """指定したelement をIUIAutomationInvokePattern.Invoke() でクリックする

    指定したelementのIsInvokePatternAvailableプロパティがFalseの場合何もしない
    """
    isClickable = element.GetCurrentPropertyValue(UIA_IsInvokePatternAvailablePropertyId)
    if isClickable == True:
        ptn = element.GetCurrentPattern(UIA_InvokePatternId)
        ptn.QueryInterface(IUIAutomationInvokePattern).Invoke()


if __name__ == '__main__':
    __init()

電卓で自動計算してみる

IDLE上でuiautil.pyを実行してテスト。
電卓を起動しておいて、各ボタンを識別するプロパティを決める。
今回はAutomationIdを使ってみる。

>>> 
>>> win = get_window_element('電卓')
>>> btns = find_control(win, UIA_ButtonControlTypeId)
>>> for b in btns: print btns.index(b), b.CurrentName, b.CurrentAutomationId

0 メモリのクリア 122
1 バックスペース 83
2 7 137
3 4 134
4 1 131
5 0 130
<中略>
21 減算 94
22 加算 93
23 メモリ減算 126
24 平方根 110
25 パーセント 118
26 逆数 114
27 等号 121
28 最小化 
29 最大化 
30 閉じる 
>>> def one_plus_one():
    btn_1 = lookup_by_automationid(btns, "131")
    btn_plus = lookup_by_automationid(btns, "93")
    btn_equal = lookup_by_automationid(btns, "121")
    click_button(btn_1)
    click_button(btn_plus)
    click_button(btn_1)
    click_button(btn_equal)


>>> one_plus_one()
>>> 

電卓に、2って表示された!

今回はWindows8.1 Pro(x64)で実行しているけど、下記の通り、AutomaionIdはビルド・リリース間で保証されないみたいなので、Windows7など別OSなどではAutomaionIdが異なるかも。あと、最小化や最大化のようにAutomationIdが振られてない場合もある。

http://msdn.microsoft.com/en-us/library/ee684017(v=vs.85).aspx の UIA_AutomationIdPropertyId のDescriptionより。

Although support for AutomationId is always recommended for better automated testing support, this property is not mandatory. Where it is supported, AutomationId is useful for creating a test automation script that runs regardless of the UI language. Clients should make no assumptions regarding the AutomationId values exposed by other applications. AutomationId is not guaranteed to be stable across different releases or builds of an application.

AutomationElementの調べ方

何に基づいてAutomationElementを指定するかは操作対象のアプリケーションの作りに依存しそう。なので、操作対象アプリのAutomationElementたちがどんなプロパティをもってるかを事前に調べたいと思うようになる。上の例のように、実際にAutomationElement をひとつひとつ当たる方法では手間がかかって仕方がないので、簡単に調べられるツールはないかと探してみると、、、、
あった。

それも Windows SDKに最初から。

Windows SDKに入ってる、Inspect.exeやVisual UI Automation Verify を使えばデスクトップ配下のAutomationElementツリーの各要素のプロパティを調べることができる。

Windows Software Development Kit (SDK) for Windows 8.1
http://msdn.microsoft.com/en-us/windows/desktop/bg162891.aspx

Windows SDKをインストールすると、以下に入る(xxx はx86, x64, arm。バージョンところも入れてるSDKのバージョンによって違うかも)。Visual Studioは不要です。

%ProgramFiles%\Windows Kits\8.1\bin\xxx

ツールの使い方について、詳しくは以下。
Inspect
http://msdn.microsoft.com/en-us/library/windows/desktop/dd318521(v=vs.85).aspx

Visual UI Automation Verify
http://msdn.microsoft.com/en-us/library/windows/desktop/jj160544(v=vs.85).aspx

26
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
23