让 dogtail 识别 UI 中的元素

在《利用 dogtail 快速进行 GUI 自动化测试》一节中,我们实现了一个最基础的自动化测试程序 - 模拟鼠标点击按钮。现在是时候更进一步了,开始访问 UI 元素中的信息。

假设,要获取按钮上的文本(例如:用于断言测试),修改之前的脚本:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from dogtail.tree import *
import time

# 获取应用程序(根据程序名称查找)
app = root.application(appName="Sample02", description="/home/waleon/workspace/demos/build-Samples-unknown-Debug/Sample02/Sample02")

# 获取按钮(根据 accessibleName 递归查找)
button = app.child('button')

# 获取并打印按钮上的文本
print('text:', button.text)

# 模拟鼠标点击
for i in range(3):
    button.click()
    sleep(1)

运行之后,你会发现 print() 打印的永远是 None。这是什么情况,按钮上分明有内容“test”,但为什么打印不出来?

这是因为,按钮元素的信息没有对外公开,以至于 dogtail 无法识别出来!

不妨用 sniff 工具查看一下,定位到我们的程序并选择该按钮,点击底下的“Text”选项:

没有任何内容,这就说明按钮的文本是不可访问的。

1

实现可访问性

要为 UI 元素提供可访问性支持,需要使用 Qt 的 Accessibility 技术。即应实现 QAccessibleInterface,分发的形式有两种:

  • 实现可访问的插件:子类化 QAccessiblePlugin,重新实现纯虚函数 create(),并使用 Q_PLUGIN_METADATA() 宏导出该类(如果想在运行时按需加载,则将接口作为插件分发比较方便)。

  • 将接口编译到应用程序中:使用接口工厂 - QAccessible::InterfaceFactory(如果是静态链接,或不想增加插件的复杂性,则推荐该方式)。

下面以接口工厂为例,来实现对按钮文本的可访问性(要对文本进行操作,需要用到 QAccessibleTextInterface):

#ifndef ACCESSIBLE_BUTTON_H
#define ACCESSIBLE_BUTTON_H

#include <QAccessibleInterface>
#include <QAccessibleWidget>
#include <QPushButton>

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 <QPushButton>

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<QAccessibleActionInterface *>(this);
    case QAccessible::TextInterface:
        return static_cast<QAccessibleTextInterface *>(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

实现接口工厂

工厂是一个函数指针,其签名如下:

typedef QAccessibleInterface* myFactoryFunction(const QString &key, QObject *);

该函数接收一个 QString 和一个 QObject 指针,其中 QString 是标识接口的键。QObject 用于传递到 QAccessibleInterface,以便它可以保存对其的引用。

注意:如果 key 和 QObject 没有对应的 QAccessibleInterface,将返回一个空指针。

来看一下我们要实现的工厂示例:

// 接口工厂
QAccessibleInterface *accessibleFactory(const QString &classname, QObject *object)
{
    QAccessibleInterface *interface = nullptr;

    if (classname == "QPushButton" && object && object->isWidgetType())
        interface = new AccessibleButton(static_cast<QPushButton *>(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

执行自动化

运行程序,再使用 sniff 工具查看一下,这时已经可以检测出按钮的文本了:

然后重新运行下自动化脚本,文本也可以正常打印了:

恭喜,这说明 dogtail 已经可以成功识别 UI 中的元素了。

最后,在自动化测试的过程中,有可能还会有其他元素的信息无法识别,参考上述方式即可。

·END·

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页
实付 59.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值