2013/08/19

Automated GUI testing of Linux Application




前陣子在用 Ubuntu 開機光碟自動化時,查了一下要如何用程式來控制 UI,在 Stackoverflow 上查到了一篇文章,原文節錄如下:


There are two different kind of tools that I know:

Based on the accessibility layer: LDTP, dogtail, strongwind
Based on image recognition: sikuli, xpresser


試用了其中的 LDTP 發覺十分好用,今天便來介紹這個工具

安裝


sudo apt-get install python-ldtp



測試一下吧!


在開始之前,想了解更多一定要參考一下這份 LDTP Tutorial,裡頭的例子是很好的入門。

開始使用 ldtp,由於是它是藉著 a11y 來控制 UI,所要先將 a11y enable

$ gsettings set org.gnome.desktop.interface toolkit-accessibility true


以下例子會用到的 ldtp functions 有

  • getwindowlist
  • getobjectlist
  • getobjectinfo
  • getobjectproperty
  • selectmenuitem

取得目前開啟的視窗

>>> from ldtp import *
>>> getwindowlist()
['frmDesktop', 'frmHome', 'frmmedia', 'dlg0', 'dlg1', 'dlg2', 'dlg3', 'dlg4', 'dlg5', 'dlg6', 'dlg7', 'dlg8', 'dlg9', 'dlg10', 'dlg11', 'dlg12', 'dlg13', 'dlgQuestion', u'frmBlogger\uff1aDoroOneTwoThree-\u4fee\u6539\u6587\u7ae0-MozillaFirefox', 'dlgOracleVMVirtualBoxManager', 'frmSystemSettings', 'frmNotes-Zim', 'frm/bin/bash', 'frmldtp-tutorial.pdf', 'frmutils.py(~/src/gnome/ldtp-2.3.1/ldtpd)-GVIM', 'frmgedit', 'pnl0', 'pnl1', 'pnl2', 'pnl3', 'pnl4', 'pnl5', 'pnl6', 'pnl7']


找一下 gedit 裡有那些 menu 可以按

>>> [i for i in getobjectlist('*gedit*') if i.startswith('mnu')]
['mnuFile', 'mnuMakefile', 'mnuObjective-C', 'mnuEiffel', 'mnuMarkdown', 'mnuHighlightMode', 'mnuDPatch', 'mnuEmpty', 'mnuGraphvizDot', 'mnuDTD', 'mnuOpal', 'mnuEmpty6', 'mnuEmpty7', 'mnuEmpty4', 'mnuEmpty5', 'mnuEmpty2', 'mnuEmpty3', 'mnuView', 'mnuEmpty1', 'mnuEmpty8', 'mnuEmpty9', 'mnuGtkRC', 'mnuDiff', 'mnuQuit', 'mnugtk-doc', 'mnuSPARQL', 'mnuCloseAll', 'mnuOctave', 'mnuBoo', 'mnuRPMspec', 'mnuObjective-J', 'mnuBottomPanel', 'mnuSystemVerilog', 'mnuClearHighlight', 'mnuPreviousTabGroup', 'mnuawk', 'mnuDefaults', 'mnuEmpty10', 'mnuEmpty11', 'mnuEmpty12', 'mnuSelectAll', 'mnuTcl', 'mnuPlainText', 'mnudesktop', 'mnuScripts', 'mnuCobol', 'mnuAbout', 'mnuCopy', 'mnuVala', 'mnuTranslateThisApplication', 'mnuToolbar', 'mnuJSON', 'mnuCut', 'mnuPrint', 'mnuPascal', 'mnuDOSBatch', 'mnuLiterateHaskell', 'mnu168', 'mnuAutomake', 'mnuChangeLog', 'mnuHaddock', 'mnuini', 'mnuASP', 'mnuSaveAll', 'mnuGotoLine', 'mnuStatusbar', 'mnuImageJ', 'mnuF#', 'mnuPython', 'mnuRedo', 'mnuDelete', 'mnuForth', 'mnuPrintPreview', 'mnuNewTabGroup', 'mnuPreviousDocument', 'mnuOCL', 'mnuBibTeX', 'mnuOpenCL', 'mnuSQL', 'mnuReplace', 'mnuHighlightMisspelledWords', 'mnu1atxt', 'mnuObjectiveCaml', 'mnuHaskell', 'mnuGDBLog', 'mnuFind', 'mnuC#', 'mnuNextTabGroup', 'mnuOpenGLShadingLanguage', 'mnuRevert', 'mnuYacc', 'mnuFullscreen', 'mnuDocBook', 'mnuTools', 'mnuVerilog', 'mnuCSS', 'mnuFindPrevious', 'mnuPreferences', 'mnuCMake', 'mnuC/C++/ObjCHeader', 'mnuGAP', 'mnuCheckSpelling', 'mnuOthers', 'mnuBennuGD', 'mnuEdit', 'mnuTexinfo', 'mnuXML', 'mnuScientific', 'mnuGo', 'mnuJava', 'mnuAda', 'mnuMovetoNewWindow', 'mnuDocumentStatistics', 'mnuJavaScript', 'mnuPHP', 'mnuHTML', 'mnuContents', 'mnuPerl', 'mnuDocuments', 'mnuVHDL', 'mnuFortran95', 'mnuUndo', 'mnulibtool', 'mnuMarkup', 'mnuPython3', 'mnuPaste', 'mnuSaveAs', 'mnupkg-config', 'mnutxt2tags', 'mnuSetLanguage', 'mnuHelp', 'mnuSidePanel', 'mnuStandardML', 'mnuNemerle', 'mnuErlang', 'mnuMallard', 'mnuSources', 'mnuSearch', 'mnugettexttranslation', 'mnuRuby', 'mnuFindNext', 'mnuNew', 'mnuProtobuf', 'mnuSave', 'mnuFCL', 'mnum4', 'mnuScheme', 'mnu169', 'mnuScilab', 'mnuClose', 'mnuC++', 'mnuOpen', 'mnuProlog', 'mnuNSIS', 'mnuIDL', 'mnuInsertDateandTime', 'mnuXSLT', 'mnuVBNET', 'mnuOOC', 'mnush', 'mnuCUDA', 'mnuGetHelpOnline', 'mnuNextDocument', 'mnuCGShaderLanguage', 'mnuLaTeX', 'mnuMatlab', 'mnuD', 'mnuC', 'mnuLua', 'mnuR']


File 選單下有那些屬性可以用

>>> getobjectinfo('*gedit*', 'mnuFile')
['key_binding', 'label', 'parent', 'key', 'obj_index', 'class', 'children']


看一下 File 選單內有那些 children

>>> getobjectproperty('*gedit*', 'mnuFile', 'children')
'ukn1 mnuNew spr0 spr1 mnuOpen spr2 spr3 spr4 mnuSave mnuSaveAs spr5 spr6 mnuRevert spr7 spr8 spr9 spr10 spr11 mnuPrintPreview mnuPrint spr12 spr13 mnu1atxt spr14 spr15 mnuClose mnuQuit mnuEmpty'


按一下 Open 好了

>>> selectmenuitem('*gedit*', 'mnuFile;mnuOpen')
1



內部實作


呼叫 getwindowlist 時,整個流程由上往下大致上為

  • ldtp
  • pyatspi
  • at-spi2-core

由 ldtp 開始,可以找到它用了 pyatspi 來取得桌面的物件。

import pyatspi
self._desktop = pyatspi.Registry.getDesktop(0)


進入到 pyatspi,它用了 gir 的方法得到 Atspi 物件。至於 Atspi 要怎麼用?這裡附上 API 文件,自行參閱。

@pyatspi/registry.py
from gi.repository import Atspi
return Atspi.get_desktop(i)


另外,這這裡 Atspi 被擴充了一些功能像 __getitem__ 之類的運算,可以參考這個檔案 pyatspi/Accessibility.py 的最後部分。

最後 at-spi2-core 的部分 dbus 的介面使用可以在 at-spi2-core-2.4.2/atspi 裡找到,像是

@atspi/atspi-accessible.c
reply = _atspi_dbus_call_partial (obj, atspi_interface_accessible,
"GetChildAtIndex", error, "i",
child_index);


實際上的實作可以在 registryd 裡找到,如下:

@registryd/registry.c
impl_GetChildAtIndex

No comments: