也许很多让对ruby中的开源工具Watir,都有耳闻,也就是Ruby其实可以对Web的自动化测试有了很好的工具了。不过在做Web的自动化测试的时候,不知道有没有遇到过弹出窗口的问题,甚至有没有想过Ruby是否可以Window的应用进行一定的自动化测试呢?
如果有很简单的思路,既然ruby可以调用Window的Com接口,那我们是不是可以利用Win32api的接口,做些事情呢?我也是在网上很多大神的提醒下,开始做这方面的探索,不过我对C++简直就是抵触啊,C的话还算能接受,所以在做这方面的探讨有点班门弄斧,我想这个就算是我的学习笔记吧,也不能算是什么技术博客了。
做好准备
让ruby帮我们启动外部程序
似乎标题很清楚了,我们要迈出的第一步,就是要完成先把被测的软件启动起来,那么ruby提供了我们很简单的方法:
system 'C:\Path\To\Program.exe'
不要着急,任务是启动了,但是代码好像是阻塞在这里了,不往下执行了,ruby会等待程序一直执行下去,这可怎么办呢?
system 'start c:\Path\To\Program.exe'
Yes,这样就可以了。我们可以进行下面的步骤。
开始工作
获取主窗口
当我们启动了一个软件后,一定会有一个窗口弹出来,这个就是主窗口,如果我们要做自动化测试,那么我们下一步,就应该考虑怎么获取这个窗口。这个时候我们就需要使用一个win32api的函数了
HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName);
先看看这个函数的两个参数:
- [lpClassName] 这个参数主要是用来说明寻找的主窗口的类型,一般我们寻找一般的Window窗口,设置成null就可以了,当然在ruby空是用nil表示的。
- [lpWindowName] 这个参数是用来生成窗口的Title名称的。简单地说就是窗口左上角图标旁边的的文字。还是弄不清楚,下载一个spy++,定位一下吧。
- 返回值是一个HWND,说白了,这个就是窗口的句柄。什么?句柄是什么东东,去网上查查吧。
下面的问题来了,既然我们知道有这么一个方法了,那么我们应该怎么使用ruby去调用这个方法来获取这个窗口呢?
在ruby里要使用win32api,首先你要引入win32api的库,那么
require 'win32api'
这个就必不可少了。 下面针对win32api里的函数需要做一个声明,声明方式如下:
find_window = Win32API.new 'user32' , 'FindWindow' , ['P' , 'P' ], 'L'
这里需要注意的就是,两个参数类型都是LPCTSTR类型,我们在ruby里用'P'来表示字符串的指针,而返回类型是一个句柄,这个用'L'来表示。至于这都是为什么呢,主要就是看变量的对应关系。 然后不用说了,我们声明完了,该调用了。
handle = find_window.call nil, 'Window Title'
调用的方法如上,这样我们就获得了hanle的一个句柄。
这里我要说明的一点,如果你刚刚运行一个程序,就获取主窗口可能获取的时候有问题,可能是因为窗口还没有打开就去获取。这种情况,可以用sleep等待上几秒,或者获取看看返回是否不为0,如果为了等待几秒再获取,这样的方式。
写个函数简化声明吧
def user32(name, param_types, return_value)
Win32API.new 'user32' , name, param_types, return_value
end
做点键盘操作
比方说我们打开一个记事本,那么现在我们要在记事本里输入文字,那么现在我们就要通过底层代码实现键盘操作。这个时候需要另外一个win32api函数。
void keybd_event(
BYTE keyCode,
BYTE unused,
DWORD event,
DWORD extraInfo);
这个函数是实现通过代码发送键盘事件给操作系统的。但是自己看他的说明,你会发现,他使用起来没有那么简单。这里最主要的是两个参数:
- keyCode 表示你使用的是哪个按键
- event 表示是keyup事件,还是keydown事件。说白了就是按键按下,还是按键起来。
keybd_event = user32 'keybd_event' , ['I' , 'I' , 'L' , 'L' ], 'V'
KEYEVENTF_KEYDOWN = 0
KEYEVENTF_KEYUP = 2
定义的时候,BYTE可以使用int类型,而DWORD用long类型,返回为空用"V" 然后这里定义了两种事件,就是刚才所说是按键的keyup和keydown
下面我就通过下面的方法来进行按键
"this is some text".upcase.each_byte do |b|
keybd_event.call b, 0, KEYEVENTF_KEYDOWN, 0
sleep 0.05
keybd_event.call b, 0, KEYEVENTF_KEYUP, 0
sleep 0.05
end
这样代码就会顺序在页面输入文本信息了。
激活窗口
有的时候窗口是在后侧的,不是在去前面,鼠标键盘的焦点也不在该窗口上,如果进行键盘操作的的话,可能会在其他窗口上。所以我们需要把激活,放到电脑最前面来。 这里需要win32api里面的一个函数:
BOOL SetForegroundWindow(HWND hWnd)
这里需要的函数只需要一个参数,这个参数就是窗口的句柄,返回是布尔型,如果失败返回0,成功返回1,那么可以用'L', ruby的代码就可以这么写
set_fore_window = user32 'SetForegroundWindow', ['L'], 'L'
set_fore_window.call main_window
PS: 可能该操作要进行多次,直到返回值为1为止,这里的代码比较简单,只是执行了一次
关闭窗口
打开 必有 关闭,我们打开的窗口,现在研究一下如何关闭窗口,关闭窗口。通过win32api关闭窗口,使用一个想窗口传递消息的函数:
BOOL PostMessage(
HWND window,
UINT message,
WPARAM wParam,
LPARAM lParam);
这里第一个参数不用说了,窗口的句柄,第二个参数就是消息的具体内容,后面是相关的参数。我们这里要用WM_SYSCOMMAND(0x0112)消息来发送关闭窗口,代码如下:
post_message = user32 'PostMessage' , ['L' , 'L' , 'L' , 'L' ], 'L'
WM_SYSCOMMAND = 0x0112
SC_CLOSE = 0xF060
post_message.call main_window, WM_SYSCOMMAND, SC_CLOSE, 0
具体WM_SYSCOMMAND意义,可以参看文档[http://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx]
如果你用记事本的话,他可能会发送完关闭消息后,会弹出一个窗口,询问是否保存,看来我们还有工作要做啊。
定位窗口的控件
我们下面试着用鼠标,来把上面窗口上的按钮点了。不用说,要搞到页面上的控件,加入我们要点“否”的按钮。使用一个函数
HWND GetDlgItem(HWND dialog, int control);
改函数会获取对话框上的控件,control会是对话窗上按钮的ID,我们可以用spy++看到,当然一般系统对否的按钮默认就是7
get_dlg_item = user32 'GetDlgItem' , ['L' , 'L' ], 'L'
dialog = find_window.call nil, 'Steganos LockNote' )
IDNO = 7
button = get_dlg_item.call dialog, IDNO
代码很简单,先找到对话框,然后找其中的控件
获取控件坐标
找到了控件,我们需要计算出他的位置,然后把鼠标移动到相应的位置,然后执行鼠标的点击事件。 首先计算位置:
BOOL GetWindowRect(HWND window, LPRECT rectangle);
这个函数会获得控件的坐标和大小,会写入rectangle函数里,这里我们使用ruby的pack方法来设置这个值,我还是先上代码吧
get_window_rect = user32 'GetWindowRect' , ['L' , 'P' ], 'I'
rectangle = [0, 0, 0, 0].pack 'L*'
get_window_rect.call button, rectangle
left, top, right, bottom = rectangle.unpack 'L*'
在ruby里我们用'P'来作为来作为rectangle的类型,call之前先初始化,然后使用pack和unpack来分别获取坐标和长宽。 这里细节讲太细,会有很多边边角角的。不过这种用法可以直接记住,在使用的时候直接使用即可。
执行鼠标的点击事件
知道位置我们就可以计算位置,然后移动鼠标了,然后点击了。最后这一步,其实很简单了,就是多了两个方法,直接上代码来看吧:
set_cursor_pos = user32 'SetCursorPos' , ['L' , 'L' ], 'I'
mouse_event = user32 'mouse_event' , ['L' , 'L' , 'L' , 'L' , 'L' ], 'V'
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
center = [(left + right) / 2, (top + bottom) / 2]
set_cursor_pos.call *center
mouse_event.call MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event.call MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
首先 SetCursorPos会将光标移动到指定位置,而mouse_event和我们之前的键盘输入很相近了,移动好位置,直接点击OK,大功告成。 最后这点我没有仔细分析了,因为这部分代码我觉得都可以直接写个函数,经常用了。
##小小总结 其实写了这么长,基本上在不断调用win32api而已,所以只要看看win32api,我们能在window的界面上做很多事情。
##写在最后 其实这类代码在vb中有很多,很多让写游戏插件,脚本什么的,都曾用过,网上一大堆一大堆的。可以通过借鉴vb的方法,来学习使用ruby来进行自动化测试。