讓 dogtail 識別 UI 中的元素

在《利用 dogtail 快速進行 GUI 自動化測試》一節(jié)中,我們實現(xiàn)了一個最基礎的自動化測試程序 - 模擬鼠標點擊按鈕?,F(xiàn)在是時候更進一步了,開始訪問 UI 元素中的信息。
假設,要獲取按鈕上的文本(例如:用于斷言測試),修改之前的腳本:
#!/usr/bin/env?python3
#?-*-?coding:?utf-8?-*-
from?dogtail.tree?import?*
import?time
#?獲取應用程序(根據(jù)程序名稱查找)
app?=?root.application(appName="Sample02",?description="/home/waleon/workspace/demos/build-Samples-unknown-Debug/Sample02/Sample02")
#?獲取按鈕(根據(jù)?accessibleName?遞歸查找)
button?=?app.child('button')
#?獲取并打印按鈕上的文本
print('text:',?button.text)
#?模擬鼠標點擊
for?i?in?range(3):
????button.click()
????sleep(1)
運行之后,你會發(fā)現(xiàn) print() 打印的永遠是 None。這是什么情況,按鈕上分明有內容“test”,但為什么打印不出來?

這是因為,按鈕元素的信息沒有對外公開,以至于 dogtail 無法識別出來!
不妨用 sniff 工具查看一下,定位到我們的程序并選擇該按鈕,點擊底下的“Text”選項:

沒有任何內容,這就說明按鈕的文本是不可訪問的。
1
實現(xiàn)可訪問性
要為 UI 元素提供可訪問性支持,需要使用 Qt 的 Accessibility 技術。即應實現(xiàn) QAccessibleInterface,分發(fā)的形式有兩種:
實現(xiàn)可訪問的插件:子類化 QAccessiblePlugin,重新實現(xiàn)純虛函數(shù) create(),并使用 Q_PLUGIN_METADATA() 宏導出該類(如果想在運行時按需加載,則將接口作為插件分發(fā)比較方便)。
將接口編譯到應用程序中:使用接口工廠 - QAccessible::InterfaceFactory(如果是靜態(tài)鏈接,或不想增加插件的復雜性,則推薦該方式)。
下面以接口工廠為例,來實現(xiàn)對按鈕文本的可訪問性(要對文本進行操作,需要用到 QAccessibleTextInterface):
#ifndef?ACCESSIBLE_BUTTON_H
#define?ACCESSIBLE_BUTTON_H
#include?
#include?
#include?
class?AccessibleButton?:?public?QAccessibleWidget
????????,?public?QAccessibleTextInterface
{
public:
????explicit?AccessibleButton(QPushButton?*button);
????~AccessibleButton();
????void?*interface_cast(QAccessible::InterfaceType?t)?Q_DECL_OVERRIDE;
????QString?text(QAccessible::Text?t)?const?Q_DECL_OVERRIDE;
????QString?text(int?startOffset,?int?endOffset)?const?Q_DECL_OVERRIDE;
????void?selection(int?selectionIndex,?int?*startOffset,?int?*endOffset)?const?Q_DECL_OVERRIDE;
????int?selectionCount()?const?Q_DECL_OVERRIDE;
????void?addSelection(int?startOffset,?int?endOffset)?Q_DECL_OVERRIDE;
????void?removeSelection(int?selectionIndex)?Q_DECL_OVERRIDE;
????void?setSelection(int?selectionIndex,?int?startOffset,?int?endOffset)?Q_DECL_OVERRIDE;
????int?cursorPosition()?const?Q_DECL_OVERRIDE;
????void?setCursorPosition(int?position)?Q_DECL_OVERRIDE;
????int?characterCount()?const?Q_DECL_OVERRIDE;
????QRect?characterRect(int?offset)?const?Q_DECL_OVERRIDE;
????int?offsetAtPoint(const?QPoint?&point)?const?Q_DECL_OVERRIDE;
????void?scrollToSubstring(int?startIndex,?int?endIndex)?Q_DECL_OVERRIDE;
????QString?attributes(int?offset,?int?*startOffset,?int?*endOffset)?const?Q_DECL_OVERRIDE;
private:
????QPushButton?*m_button;
};
#endif?//?ACCESSIBLE_BUTTON_H
由于訪問的是按鈕的文本,所以重點關注 interface_cast()、text() 即可,其他接口可以“忽略”:
#include?"accessiblebutton.h"
#include?
AccessibleButton::AccessibleButton(QPushButton?*button)
????:?QAccessibleWidget(button)
????,?m_button(button)
{
}
AccessibleButton::~AccessibleButton()
{
}
void?*AccessibleButton::interface_cast(QAccessible::InterfaceType?t)
{
????switch?(t)?{
????case?QAccessible::ActionInterface:
????????return?static_cast(this);
????case?QAccessible::TextInterface:
????????return?static_cast(this);
????default:
????????return?nullptr;
????}
}
QString?AccessibleButton::text(QAccessible::Text?t)?const
{
????switch?(t)?{
????case?QAccessible::Name:
????????return?m_button->accessibleName();
????case?QAccessible::Description:
????????return?m_button->accessibleDescription();
????default:
????????return?QString();
????}
}
QString?AccessibleButton::text(int?startOffset,?int?endOffset)?const
{
????Q_UNUSED(startOffset)
????Q_UNUSED(endOffset)
????return?m_button->text();
}
void?AccessibleButton::selection(int?selectionIndex,?int?*startOffset,?int?*endOffset)?const
{
????Q_UNUSED(selectionIndex)
????Q_UNUSED(startOffset)
????Q_UNUSED(endOffset)
}
int?AccessibleButton::selectionCount()?const
{
????return?0;
}
void?AccessibleButton::addSelection(int?startOffset,?int?endOffset)
{
????Q_UNUSED(startOffset)
????Q_UNUSED(endOffset)
}
void?AccessibleButton::removeSelection(int?selectionIndex)
{
?????Q_UNUSED(selectionIndex)
}
void?AccessibleButton::setSelection(int?selectionIndex,?int?startOffset,?int?endOffset)
{
????Q_UNUSED(selectionIndex)
????Q_UNUSED(startOffset)
????Q_UNUSED(endOffset)
}
int?AccessibleButton::cursorPosition()?const
{
????return?0;
}
void?AccessibleButton::setCursorPosition(int?position)
{
????Q_UNUSED(position)
}
int?AccessibleButton::characterCount()?const
{
????return?0;
}
QRect?AccessibleButton::characterRect(int?offset)?const
{
????Q_UNUSED(offset)
????return?QRect();
}
int?AccessibleButton::offsetAtPoint(const?QPoint?&point)?const
{
????Q_UNUSED(point)
????return?0;
}
void?AccessibleButton::scrollToSubstring(int?startIndex,?int?endIndex)
{
????Q_UNUSED(startIndex)
????Q_UNUSED(endIndex)
}
QString?AccessibleButton::attributes(int?offset,?int?*startOffset,?int?*endOffset)?const
{
????Q_UNUSED(offset)
????Q_UNUSED(startOffset)
????Q_UNUSED(endOffset)
????return?QString();
}
2
實現(xiàn)接口工廠
工廠是一個函數(shù)指針,其簽名如下:
typedef?QAccessibleInterface*?myFactoryFunction(const?QString?&key,?QObject?*);
該函數(shù)接收一個 QString 和一個 QObject 指針,其中 QString 是標識接口的鍵。QObject 用于傳遞到 QAccessibleInterface,以便它可以保存對其的引用。
注意:如果 key 和 QObject 沒有對應的 QAccessibleInterface,將返回一個空指針。
來看一下我們要實現(xiàn)的工廠示例:
//?接口工廠
QAccessibleInterface?*accessibleFactory(const?QString?&classname,?QObject?*object)
{
????QAccessibleInterface?*interface?=?nullptr;
????if?(classname?==?"QPushButton"?&&?object?&&?object->isWidgetType())
????????interface?=?new?AccessibleButton(static_cast(object));
????return?interface;
}
3
安裝工廠
完成之后,需要使用 QAccessible::installFactory 對上述工廠進行安裝:
int?main(int?argc,?char?*argv[])
{
????QApplication?a(argc,?argv);
????//?安裝工廠
????QAccessible::installFactory(accessibleFactory);
????QPushButton?button("test");
????QObject::connect(&button,?&QPushButton::clicked,?[&]()?{
????????static?int?index?=?0;
????????index++;
????????button.setText(QString("click?%1").arg(index));
????});
????//?將被輔助技術識別
????button.setAccessibleName("button");
????button.setAccessibleDescription("this?is?a?simple?button");
????button.resize(300,?200);
????button.show();
????return?a.exec();
}
4
執(zhí)行自動化
運行程序,再使用 sniff 工具查看一下,這時已經可以檢測出按鈕的文本了:

然后重新運行下自動化腳本,文本也可以正常打印了:

恭喜,這說明 dogtail 已經可以成功識別 UI 中的元素了。
最后,在自動化測試的過程中,有可能還會有其他元素的信息無法識別,參考上述方式即可。
·END·

