Qt自动测试录制播放

希望能够实现自动测试,自动测试录制操作,录制完成后即可启动自动测试,当然可以定义更多的何时启动测试、测试结束之后重启等操作,目标平台是linux arm Qt4.8触屏设备,暂不考虑按键操作。
如何去实现呢?

  • 事件录制
    首先需要将鼠标事件都记录下来,如何记录呢?继承自 QApplication 实现 MyApplication ,重写它的 bool notify(QObject *obj, QEvent *event) 函数,通过这个函数,我们能在事件派发到控件之前,拿到发送出去的事件,这时候将其事件类型、事件位置记录下来即可,需要注意的是,当在某个控件鼠标按下的时候,实际上是先产生一个 mouseMove 事件移动到这个控件,然后再 mousePress 的,代码如下:
#include "MyApplication.h"

MyApplication::MyApplication(int & argc, char ** argv): QApplication(argc, argv)
{}

bool MyApplication::notify(QObject * obj, QEvent * event)
{
    if(event->type() == QMouseEvent::MouseButtonPress || event->type() == QMouseEvent::MouseButtonRelease ||
            event->type() == QEvent::MouseMove){
        if(isTestRecording && obj->objectName() != "btnTestRecord" && !obj->objectName().isEmpty()){
            QMouseEvent * ev = (QMouseEvent *)event;
            LOGOUT<<obj<<event->type()<<ev->pos()<<ev->globalPos();
            emit TestRecordOperate(event->type(),ev->pos(),ev->globalPos());//事件发送出去,记录下来
        }
    }
    return QApplication::notify(obj,event);
}
  • 回放
    如何将录制下来的事件,重新回放呢?
    想法一:最理想的方式是,我记录事件和位置时,只记录事件的类型和事件的全局位置,然后发送给Qt程序,由Qt程序自己定位会发送到哪个控件,会触发什么消息。那如何发送呢?在这个 linux arm 设备上,使用 qtopia4 启动程序,使用 tslib 处理事件,事件从 /dev/input/event0 中来的,未找到直接往程序发送原始事件的方法,那试着向 /dev/input/event0 写入内容试试?使用类似如下代码写入:
void AutoTestManager::SendMouseEvent(bool press)
{
    LOGOUT;
    event_.type = EV_KEY;
    event_.value = press;
    event_.code = BTN_LEFT;
    LOGOUT;
    WriteEvent();
    LOGOUT;
    SendSyn();
    LOGOUT;
}

void AutoTestManager::SendMoveEvent(int x, int y)
{
    LOGOUT;
    event_.type = EV_REL;/*EV_REL*/
    event_.value = x;
    event_.code = REL_X;
    LOGOUT;
    WriteEvent();
    LOGOUT;
    event_.type = EV_REL;/*EV_ABS*/
    event_.value = y;
    event_.code = REL_Y;
    LOGOUT;
    WriteEvent();
    LOGOUT;
    SendSyn();
    LOGOUT;
}

void AutoTestManager::SendSyn()
{
    LOGOUT;
    event_.type = EV_SYN;
    event_.value = 0;
    event_.code = SYN_REPORT;
    LOGOUT;
    WriteEvent();
    LOGOUT;
}

void AutoTestManager::WriteEvent()
{
    gettimeofday(&event_.time, 0);
    LOGOUT;
    char buf[100] = {0};
    LOGOUT;
    //sprintf(buf,"%02X%02X %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X %02X%02X",((char*)(&event_))[0],((char*)(&event_))[1],((char*)(&event_))[2],
    // ((char*)(&event_))[3],((char*)(&event_))[4],((char*)(&event_))[5],((char*)(&event_))[6],((char*)(&event_))[7],((char*)(&event_))[8],((char*)(&event_))[9],
    // ((char*)(&event_))[10],((char*)(&event_))[11],((char*)(&event_))[12],((char*)(&event_))[13],((char*)(&event_))[14],((char*)(&event_))[15]);
    LOGOUT;
    char bbbb[16];
    bbbb[0] = ((char*)(&event_))[1] ;
    bbbb[1] = ((char*)(&event_))[0] ;
    bbbb[2] = ((char*)(&event_))[3] ;
    bbbb[3] = ((char*)(&event_))[2] ;
    bbbb[4] = ((char*)(&event_))[5] ;
    bbbb[5] = ((char*)(&event_))[4] ;
    bbbb[6] = ((char*)(&event_))[7] ;
    bbbb[7] = ((char*)(&event_))[6] ;
    bbbb[8] = ((char*)(&event_))[9] ;
    bbbb[9] = ((char*)(&event_))[8] ;
    bbbb[10] = ((char*)(&event_))[11];
    bbbb[11] = ((char*)(&event_))[10];
    bbbb[12] = ((char*)(&event_))[13];
    bbbb[13] = ((char*)(&event_))[12];
    bbbb[14] = ((char*)(&event_))[15];
    bbbb[15] = ((char*)(&event_))[14];
    sprintf(buf,"%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x",bbbb[0],bbbb[1],bbbb[2],
            bbbb[3],bbbb[4],bbbb[5],bbbb[6],bbbb[7],bbbb[8],bbbb[9],
            bbbb[10],bbbb[11],bbbb[12],bbbb[13],bbbb[14],bbbb[15]);
    LOGOUT<<write(fdTouch_, bbbb, sizeof(event_))<<buf;
    LOGOUT;
    char aaaa[64] = {0};
    LOGOUT<<read(fdTouch_,aaaa,64);
    LOGOUT;
}

但是很遗憾,这个方法写入没有保错,返回值都正常,但是Qt程序毫无反应,使用 hexdump /dev/input/event0 也毫无反应,猜测可能是驱动虚拟的文件,与实际文件不一样吧。。。反正是没走通的.

想法二:既然没有办法直接往程序加入全局坐标的事件,那就只能在程序中实现,自己查找全局坐标下的控件,然后再往控件相对坐标事件。如何查找全局坐标下的控件呢?有两个 API 可以参考:
1、QWidget *QWidget::childAt(const QPoint &p):
在这里插入图片描述
2、QWidget *QApplication::widgetAt(int x, int y):
在这里插入图片描述
事实上 Qt 在定位事件的时候用的也是 widgetAt 的,当然要复杂的多…,childAt 也试了,有些问题…,所以最终用 widgetAt 实现,
大概实现代码:
.h

#ifndef AUTOTESTMANAGER_H
#define AUTOTESTMANAGER_H

#include <QObject>
#include <QFile>
#include <QEvent>
#include <QTimer>
#include "Public/PublicStructs.h"

const static int POSSTOP = -1; //表示停止

struct TestStruct
{
    TestStruct(int cmd,int x,int y,int time) {
        this->cmd = cmd;
        this->x = x;
        this->y = y;
        this->time = time;
    }
    bool IsValid(){
        if(cmd == QEvent::MouseButtonPress || cmd == QEvent::MouseButtonRelease || cmd == QEvent::MouseMove){
            return /*x >= 0 && x <= 1080 && y >= 0 && y <= 1920 &&*/ time > 0;
        }
        return cmd == QEvent::None && time > 0;
    }
    QString Print(){
        return QString(" cmd:%1 x:%2 y:%3 time:%4").arg(cmd).arg(x).arg(y).arg(time);
    }
    QString ToString(){
        return QString("%1 %2 %3 %4\n").arg(cmd).arg(x).arg(y).arg(time);
    }
    int cmd;
    int x;
    int y;
    int time; //msec
};

class MainWindow;

class AutoTestManager : public QObject
{
    Q_OBJECT

public:
    explicit AutoTestManager(QObject *parent = 0);
    ~AutoTestManager();

private slots:
    void OnTestRecordOperate(int type,QPoint pos,QPoint gPos);
    void OnTestRecord(bool finish,bool start,bool reboot);
    void OnTestStart(bool start = true);
    void TestTimeOut();

private:
    int GetTimeSplit();
    bool InitData();
    void SaveData();
    bool CheckValid();
    void Print();

private:
    std::vector<TestStruct> vec;
    QDateTime time_;
    int curPos_;
    MainWindow * parent_;
    QWidget * widget_;
};

#endif // AUTOTESTMANAGER_H

.cpp

#include "AutoTestManager.h"
#include <QMouseEvent>
#include <QApplication>
#include "Main/MainWindow.h"

AutoTestManager::AutoTestManager(QObject *parent) : QObject(parent)
{
    LOGOUT<<parent<<this->parent();
    widget_ = NULL;
    curPos_ = POSSTOP;
    parent_ = (MainWindow *)parent;
    time_ = QDateTime::currentDateTime();
    InitData();
    //Print();
    if(vec.size() > 2 && vec[vec.size() - 1].x == 1){ //start on the program begin.
        SIGSHOT(5000,OnTestStart);
    }
}

AutoTestManager::~AutoTestManager()
{
}

void AutoTestManager::OnTestRecordOperate(int type, QPoint pos, QPoint gPos)
{
    if(vec.size() > 0){
        vec[vec.size() - 1].time = GetTimeSplit();
    }
    if(type == QEvent::MouseButtonPress && vec.size() > 0){ //移除press之前的move消息
        int time = 1;
        while (vec.size() > 0){
            if(vec[vec.size() - 1].cmd == QEvent::MouseMove){
                time += vec[vec.size() - 1].time;
                vec.erase(vec.begin() + vec.size() - 1); //erase the last move.
                LOGOUT<<"record erased."<<vec.size()<<time;
            }else{
                break;
            }
        }
        vec.push_back(TestStruct(QEvent::MouseMove,gPos.x(),gPos.y(),10));
        vec.push_back(TestStruct(QEvent::MouseMove,pos.x(),pos.y(),time));
    }
    vec.push_back(TestStruct(type,pos.x(),pos.y(),1));
    LOGOUT<<vec[vec.size() - 1].Print()<<vec.size();
}

void AutoTestManager::OnTestRecord(bool finish, bool start, bool reboot)
{
    curPos_ = POSSTOP;
    widget_ = NULL;
    if(finish){ //录制结束
        vec.push_back(TestStruct(QEvent::None,start,reboot,10));
        Print();
        SaveData();
    }else{ //开始录制
        vec.clear();
        time_ = QDateTime::currentDateTime();
        vec.push_back(TestStruct(QEvent::None,0,0,GetTimeSplit()));
    }
    LOGOUT<<vec[vec.size() - 1].Print();
}

void AutoTestManager::OnTestStart(bool start)
{
    LOGOUT<<start;
    if(start){
        if(!GlobalData::IsTestRecording() && curPos_ == POSSTOP && vec.size() > 1){
            curPos_ = 0;
            widget_ = NULL;
            SIGSHOT(1000,TestTimeOut);
        }
    }else{
        curPos_ = POSSTOP;
    }
}

void AutoTestManager::TestTimeOut()
{
    if(curPos_ > POSSTOP && curPos_ < vec.size()){
        TestStruct * test = &vec[curPos_];
        LOGOUT<<curPos_<<test->Print();
        if(curPos_ > 0 && (test->cmd == QEvent::MouseButtonPress || test->cmd == QEvent::MouseButtonRelease ||
                           test->cmd == QEvent::MouseMove)){
            if(!widget_ && test->cmd == QEvent::MouseMove){ //find
                widget_ = QApplication::widgetAt(test->x,test->y);
                LOGOUT<<"widgetAt:"<<curPos_<<test->Print()<<widget_;
            }else if(widget_ && !widget_->objectName().isEmpty()){
                QMouseEvent ev((QEvent::Type)test->cmd,QPoint(test->x,test->y),Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
                LOGOUT<<widget_<<curPos_<<test->Print()<<QApplication::sendEvent(widget_,&ev);
            }
            if(!widget_ || (widget_ && widget_->objectName().isEmpty())){
                LOGOUT<<"ERROR: widget is NULL or object name is empty."<<widget_<<curPos_<<test->Print();
                parent_->OnTestSendEvent((QEvent::Type)test->cmd,test->x,test->y);
            }
            widget_ = test->cmd == QEvent::MouseButtonRelease ? NULL : widget_;
        }else if(curPos_ == vec.size() - 1){
            widget_ = NULL;
            if(test->y == 1){
                VecsClient::RemoteClient()->SetReboot(true);
                LOGOUT<<"reboot.";
                return;
            }
        }
        ++curPos_;
        if(curPos_ < vec.size()){
            SIGSHOT(test->time,TestTimeOut);
        }
    }
}

int AutoTestManager::GetTimeSplit()
{
    QDateTime time = QDateTime::currentDateTime();
    int ret = time_ >= time ? 1 : time.toMSecsSinceEpoch() - time_.toMSecsSinceEpoch();
    time_ = time;
    return ret;
}

bool AutoTestManager::InitData()
{
    vec.clear();
    QFile file("/mnt/opt/Qtopia4.8.6/bin/testRecord.dat");
    if(file.open(QIODevice::ReadWrite)){
        QStringList list = QString(file.readAll()).split('\n');
        for(int i = 0;i < list.size();++i){
            QStringList data = list[i].split(" ");
            if(data.size() == 4){
                vec.push_back(TestStruct(data[0].toInt(),data[1].toInt(),data[2].toInt(),data[3].toInt()));
            }else{
                vec.clear();
                TOAST("测试录制文件不正常.");
                break;
            }
        }
        file.close();
    }else{
        TOAST("打开测试录制文件失败.");
    }
    LOGOUT<<"testRecord size:"<<vec.size();
    return CheckValid();
}

void AutoTestManager::SaveData()
{
    if(CheckValid()){
        QFile file("/mnt/opt/Qtopia4.8.6/bin/testRecord.dat");
        if(file.open(QIODevice::ReadWrite | QIODevice::Truncate)){
            QString str;
            for(size_t i = 0;i < vec.size();++i){
                str += vec[i].ToString();
            }
            qint64 len = file.write(str.toStdString().c_str(),str.size() - 1);
            LOGOUT<<"dataLen:"<<str.size()<<" writeLen:"<<len;
            TOAST("保存成功");
            file.close();
        }else{
            TOAST("打开测试录制文件失败.");
        }
    }
}

bool AutoTestManager::CheckValid()
{
    LOGOUT<<"command size:"<<vec.size();
    if(vec.size() >= 2 && vec[0].cmd == QEvent::None && vec[vec.size() - 1].cmd == QEvent::None){
        size_t i = 0;
        for(;i < vec.size();++i){
            if(!vec[i].IsValid()){
                LOGOUT<<i<<vec[i].Print()<<"is invalid";
                break;
            }
        }
        if(i == vec.size()){
            TOAST("测试录制文件正常.");
            return true;
        }
    }
    LOGOUT<<"测试录制文件不正常.";
    TOAST("测试录制文件不正常.");
    vec.clear();
    return false;
}

void AutoTestManager::Print()
{
    LOGOUT<<vec.size();
    for(size_t i = 0;i < vec.size();++i){
        LOGOUT<<QString::number(i)<<vec[i].Print();
    }
}

实现下来还有的问题就是,比如 QComboBox 这样的控件,给它发送一些事件,它是不直接处理的,会有问题,这种针对这些控件单独处理就可以了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值