讓 dogtail 識(shí)別 UI 中的元素

在《利用 dogtail 快速進(jìn)行 GUI 自動(dòng)化測(cè)試》一節(jié)中,我們實(shí)現(xiàn)了一個(gè)最基礎(chǔ)的自動(dòng)化測(cè)試程序 - 模擬鼠標(biāo)點(diǎn)擊按鈕?,F(xiàn)在是時(shí)候更進(jìn)一步了,開(kāi)始訪問(wèn) UI 元素中的信息。
假設(shè),要獲取按鈕上的文本(例如:用于斷言測(cè)試),修改之前的腳本:
#!/usr/bin/env?python3
#?-*-?coding:?utf-8?-*-
from?dogtail.tree?import?*
import?time
#?獲取應(yīng)用程序(根據(jù)程序名稱(chēng)查找)
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)
#?模擬鼠標(biāo)點(diǎn)擊
for?i?in?range(3):
????button.click()
????sleep(1)
運(yùn)行之后,你會(huì)發(fā)現(xiàn) print() 打印的永遠(yuǎn)是 None。這是什么情況,按鈕上分明有內(nèi)容“test”,但為什么打印不出來(lái)?

這是因?yàn)?,按鈕元素的信息沒(méi)有對(duì)外公開(kāi),以至于 dogtail 無(wú)法識(shí)別出來(lái)!
不妨用 sniff 工具查看一下,定位到我們的程序并選擇該按鈕,點(diǎn)擊底下的“Text”選項(xiàng):

沒(méi)有任何內(nèi)容,這就說(shuō)明按鈕的文本是不可訪問(wèn)的。
1
實(shí)現(xiàn)可訪問(wèn)性
要為 UI 元素提供可訪問(wèn)性支持,需要使用 Qt 的 Accessibility 技術(shù)。即應(yīng)實(shí)現(xiàn) QAccessibleInterface,分發(fā)的形式有兩種:
實(shí)現(xiàn)可訪問(wèn)的插件:子類(lèi)化 QAccessiblePlugin,重新實(shí)現(xiàn)純虛函數(shù) create(),并使用 Q_PLUGIN_METADATA() 宏導(dǎo)出該類(lèi)(如果想在運(yùn)行時(shí)按需加載,則將接口作為插件分發(fā)比較方便)。
將接口編譯到應(yīng)用程序中:使用接口工廠 - QAccessible::InterfaceFactory(如果是靜態(tài)鏈接,或不想增加插件的復(fù)雜性,則推薦該方式)。
下面以接口工廠為例,來(lái)實(shí)現(xiàn)對(duì)按鈕文本的可訪問(wèn)性(要對(duì)文本進(jì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
由于訪問(wèn)的是按鈕的文本,所以重點(diǎn)關(guān)注 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
實(shí)現(xiàn)接口工廠
工廠是一個(gè)函數(shù)指針,其簽名如下:
typedef?QAccessibleInterface*?myFactoryFunction(const?QString?&key,?QObject?*);
該函數(shù)接收一個(gè) QString 和一個(gè) QObject 指針,其中 QString 是標(biāo)識(shí)接口的鍵。QObject 用于傳遞到 QAccessibleInterface,以便它可以保存對(duì)其的引用。
注意:如果 key 和 QObject 沒(méi)有對(duì)應(yīng)的 QAccessibleInterface,將返回一個(gè)空指針。
來(lái)看一下我們要實(shí)現(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 對(duì)上述工廠進(jìn)行安裝:
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));
????});
????//?將被輔助技術(shù)識(shí)別
????button.setAccessibleName("button");
????button.setAccessibleDescription("this?is?a?simple?button");
????button.resize(300,?200);
????button.show();
????return?a.exec();
}
4
執(zhí)行自動(dòng)化
運(yùn)行程序,再使用 sniff 工具查看一下,這時(shí)已經(jīng)可以檢測(cè)出按鈕的文本了:

然后重新運(yùn)行下自動(dòng)化腳本,文本也可以正常打印了:

恭喜,這說(shuō)明 dogtail 已經(jīng)可以成功識(shí)別 UI 中的元素了。
最后,在自動(dòng)化測(cè)試的過(guò)程中,有可能還會(huì)有其他元素的信息無(wú)法識(shí)別,參考上述方式即可。
·END·
01:dotnet
02:java
03:android
04:C++
05:qt
06:react
沒(méi)有的資源,也可以給我留言,我會(huì)去尋找的哦。
另:大部分資源可在我的網(wǎng)站搜索哦:https://dotnet9.com
