让 dogtail 识别 UI 中的元素

高效程序员

共 5937字,需浏览 12分钟

 ·

2020-09-15 06:20


在《利用 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 
#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

实现接口工厂


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


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(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(300200);
    button.show();

    return a.exec();
}



4

执行自动化


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



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



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


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


·END·

浏览 126
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报