微软Windows应用程序编程接口(API)为编写windows应用程序提供了许多构建好的模块。它包含了多种多样丰富的功能,如可以获取鼠标当前位置的坐标,一个窗口的句柄或它的颜色等等。在QTP中,你可以通过调用含有这些功能的DLL文件来使用它们,但是由于VBScript的局限性,有一部分API在QTP中是无法使用的。
对于本章示例所使用的API函数,读者若想进行深入学习,可以在MSDN或使用Visul Studio附带的API查看工具进行查询。
Extern对象
QTP提供了一个名为Extern的通用对象来声明并调用API函数。
语法:
Extern.Declare (RetType, MethodName, LibName, Alias [, ArgType(s)])
详情请参阅QTP用户手册。
VB中调用API 的语法
Private Declare Function GetForegroundWindow Lib “user32.dll” () As Long
如果想要使用这个API函数,我们需要根据它的定义,在QTP中选择合适的参数来声明:RetType = micLong(函数返回值的类型)
MethodName = “GetForegroundWindow” (可以使用任意值但最好是API函数的名称)
LibName = “user32.dll” (当不使用windows的系统DLL时,我们就必须提供一个正确的文件地址,如”C:\MyApp\Lib\mylic.dll”)
Alias = ““ or “GetForegroundWindow” (如果Alias与MethodName相同,可以留白)
ArgType(s) = (这个函数不需要其它参数)
QTP 中API 的定义
Extern.Declare micLong,” GetForegroundWindow”,”user32.dll”,” GetForegroundWindow”
通过下面的几个示例,我们将学习如何通过API来解决一些常见的问题。
Problem 17-1. 怎样判断当前桌面最顶端的窗口是否是浏览器?
‘声明GetForegroundWindow
Extern.Declare micLong,"GetForegroundWindow","user32.dll","GetForegroundWindow"
‘获取最顶端的窗口的句柄
Hwnd = extern. GetForegroundWindow()
‘检查拥有该句柄的浏览器是否存在
isBrowser = Browser("hwnd:=" & hwnd).Exist(1)
If isBrowser then
Msgbox "The top most window is a browser"
End if
Problem 17-2. 怎样获取Windows的环境变量(注意Windows的环境变量与QTP的环境变量是不同的)
‘声明一个变量
Dim s_EnvValue
‘声明 GetEnvironmentVaribale
Extern.Declare micLong,"GetEnvironmentVariable","kernel32.dll","GetEnvironmentVariableA", _
micString,micString+micByRef,micLong
‘取得环境变量 “TEMP” 的值
Extern.GetEnvironmentVariable "TEMP",s_EnvValue,255 ‘获的temp文件夹的路径
MsgBox s_EnvValue
Problem 17-3. 怎样通过API检查(选取)列表框中的一个选项
‘声明函数
Extern.Declare micLong,"SendMessage","user32.dll","SendMessageA",micLong,micLong,micLong,micLong
‘在列表中选择一个选项
Const LB_SETSEL = &H185
‘函数功能:在指定的位置开始检查列表框
Function CheckListBox(hwnd, index)
extern.SendMessage hwnd, LB_SETSEL, True, index
End Function
‘取消选中该项
Function UnCheckListBox(hwnd, index)
extern.SendMessage hwnd, LB_SETSEL, False, index
End Function
Problem 17-4. 怎样获取一个文本框的背景色(当必填项的背景颜色与其他项不同时就可以使用这招)
‘声明要用到的API函数
Extern.Declare micLong,"GetPixel","gdi32","GetPixel",micLong,micLong,micLong
Extern.Declare micLong,"GetWindowDC","user32","GetWindowDC",micLong
Extern.Declare micLong,"ReleaseDC","user32","ReleaseDC",micLong,micLong
Extern.Declare micLong,"GetDC","user32","GetDC",micLong
Extern.Declare micLong,"SetForegroundWindow","user32","SetForegroundWindow",micLong
Dim hDCSource
Dim hWndSource
Dim backColor
‘获取文本框句柄
hWndSource = Window("Window").WinEdit("MandatoryField1").GetROProperty("hwnd")
‘将该窗口置于前端,这是使用GetPixel的前提,它只在该像素可见时才有用
extern.SetForegroundWindow hWndSource
‘获取控件的句柄
hDCSource = Clng(Extern.GetDC(hWndSource))
‘获取控件的(1,1)点的背景色
backColor = Clng(Extern.GetPixel(hDCSource, Clng(1),Clng(1)))
MsgBox backColor
‘释放控件句柄
Extern.ReleaseDC hWndSource, hDCSource
Problem 17-5. 怎样通过API模拟键盘操作?
‘声明keybd_event函数
Extern.Declare micVoid, “keybd_event”, “user32”, “keybd_event”, micByte, micByte, micLong, micLong
Const KEYEVENTF_EXTENDENKEY = &H1
Const KEYEVENTF_KEYUP = &H2
Const KEYEVENTF_ KEYDOWN = &H0
Sub KeyDown (KeyAscii)
keyCode = extern.MapVirtualKey (KeyAscii, 0)
‘按下键
Extern.keybd_event KeyAscii, keyCode, Const KEYEVENTF_ KEYDOWN, 0
End Sub
Sub KeyUp (KeyAscii)
keyCode = extern.MapVirtualKey (KeyAscii, 0)
‘松开键
Extern.keybd_event KeyAscii, keyCode, Const KEYEVENTF_ KEYUP, 0
End Sub
Sub KeyPress (KeyAscii)
KeyDown KeyAscii
KeyUp KeyAscii
End Sub
你可以对计算器工具使用上述代码,观看效果。
‘声明常量来表示对应的键
Const vbKey1 = 49
Const vbKey2 = 50
Const vbKeyAdd = 107
Const vbKeyReturn = 13
Systemutil.Run “calc.exe”
Window(“title:=Claculator”).Activate
Call KeyPress (vbKey1)
Call KeyPress (vbKey2)
Call KeyPress (vbKeyAdd)
Call KeyPress (vbKeyReturn)
上述代码可以模拟同时按下 CTRL + ALT + S
‘声明常量来表示对应的键
Const vbKeyControl = 17
Const vbKeyAlt = 18
Const vbKeyS = 83
Call KeyDown (vbKeyControl)
Call KeyDown (vbKeyAlt)
Call KeyDown (vbKeyS)
Call KeyUp (vbKeyS)
Call KeyUp (vbKeyAlt)
Call KeyUp (vbKeyControl)
Problem 17-6. 怎样防止屏幕保护程序的触发?
有时我们需要在无人值守的情况下运行测试脚本,这时屏幕保护程序就会将电脑锁住,导致QTP脚本无法正常执行,我们可以通过运行下列代码来模拟键盘或鼠标操作来避免这种情况。
‘C:\PreventPCLock.vbs
Const micVoid = 0
Const micByte = 26
Const micLong = 3
Const KEYEVENTF_KEYUP = &H2
‘创建Extern对象
Set Extern = CreateObiect (“Mercury.ExternObj”)
Extern.Declare micVoid, “keybd_event”, “user32”, “keybd_event”, micByte, micByte, micLong, micLong
Extern.Declare micVoid, “Sleep”, “kernal32”, “Sleep”, micLong
While True
Extern.keybd_event 0, 0, KEYEVENTF_KEYUP, 0
Extern.Sleep 20000
Wend
注意: 这个代码可能会产生一个”无法找到指定模块”的错误。这是DLL中的这些类在注册表信息中的路径不完整导致的。将QTP的Bin文件夹添加到Windows的PATH环境变量中即可解决此问题。
我们也可以使用下述代码来将Bin文件夹添加到PATH环境变量中:
‘函数功能:将一个地址添加到PATH环境变量中
Public Function AddToSystemPath(ByVal Path)
Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)
‘获取Windows环境变量
Set colItems = objWMIService.ExecQuery (“Select * from Win32_Environment where Name = ‘Path’”)
For each objItem of colItems
‘Add to PATH only if the path does not already exists
If InStr(objItem.VariableValue, PATH) = 0 Then
‘If path does not exist already then add it
strPath = objItem.VariableValue & “;” & Path
objItem.VariableValue = strPath
objItem.Put_
End If
Next
End Functino
AddToSystemPath “C:\Program Files\Mercury Interactive\QuickTest Professional\bin”
向keybd_event函数发送0可以触发一个没有按键的键盘事件。读者可以讲上述代码保存到vbs文件中执行。
Problem 17-7. 怎样使一个窗口或浏览器最大化?
‘声明
Private Const SW_MAXIMIZE = 3
Extern.Declare micLong, “ShowWindow”, “user32.dll”, “ShowWindow”, micHwnd, micLong
‘想要最大化一个窗口必先先获取它的句柄
hWndWindow = Browser(“creationtime:=0”).GetROProperty(“hwnd”)
‘最大化该窗口
Extern.ShowWindow hWndWindow, SW_MAXIMIZE
Problem 17-8. 怎样通过URL下载文件到本机?
‘声明函数 URLDownLoadToFile Lib “urlmon” Alias “URLDownloadToFileA”
‘(ByVal pCaller As Long, ByVal szURL As String, ByVal szFileName As String,
‘ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Extern.Declare micLong, “URLDownloadToFile”, “urlmon”, “URLDownloadToFIleA”, micLong, micString, micString, micLong, micLong
sSourceURL = “http://mysite/logo.gif“
sTargetFile = “C:\logo.gif”
Extern.URLDownloadToFile 0, sSourceURL, sTargetFile, 0, 0
QTP API的局限性
由于VBScript只支持variant数据类型,我们调用API函数时会碰到许多限制,本节中我们将对其应变方法进行探讨。
在VB6中使用API函数调用 COM 对象
GetCursorPos函数的作用是通过点阵来获取鼠标当前的坐标,我们首先在VB6中依照下述方法创建一个COM对象然后利用它在QTP中执行GetCursorPos函数。
Image
打开VB6 ,创建一个空的 “ActiveX DLL” 工程,如图17-1。
创建成功后,工程会自动生成一个类,我们可以给工程和类起一个恰当的名字,这里我们将工程命名为”WindowsAPI”,将类命名为”API”。
打开Visual Studio的 API查看器,并添加要用到的API函数 (GetCursorPos) 及结构 (POINTAPI) ,如图17-2所示。
Image
然后我们将下列定义拷贝到一个文件中。
‘声名API函数
Publick Declare Function GetCursorPos Lib “user32” Alias “GetCursorPos” (lpPoint As POINTAPI) As Long
Public Type POINTAPI
X As Long
Y As Long
End Type
我们继续在类中创建一个函数来执行GetCursorPos函数并返回执行结果。这个函数有两个参数X和U,都是按地址传递的,它将执行GetCursorPos函数并将返回值分别赋给X和Y。我们将下面的代码添加到类中(注意要将参数声名为variant类型,不然QTP在调用该函数时会抛出一个”类型不匹配”的异常):
‘声名函数,变量
Option Explicit
Public Function GetCursorPosition (ByRef x As Variant, ByRef y As Variant) As Long
Dim lpPoint As POINTAPI
GetCursorPosition = GetCursorPos(lpPoint)
X = lpPoint.x
Y = lpPoint.y
End Function
我们还需要为其他的API添加函数,一切完成之后再生成一个DLL库文件。点击 文件à生成WIndowsAPI.dll 并将其保存:
Image
如果想要使用这些函数,我们还需要将DLL文件在系统中注册一下。将DLL文件拷贝到我们要用的机器上,然后用REGSVR32 命令注册。如下所示:
Image
在QTP中调用我们的DLL文件,还需要创建这个DLL的对象。CreateObject方法需要传入形为”<ProjectName>.<ClassName>“的参数,如下所示:
‘创建COM对象
Set winAPI = CreateObject(“WindowsAPI.API”)
Dim x,y
winAPI.GetCursorPosition x,y
Msgbox “(“ & x & “,” & y & “)”
这个方法也有其不足之处:
l 使用本方法生成的DLL文件的电脑都需要复制并注册该DLL文件,这需要用户具有管理员权限。
l ActiveX 对象有时会显示”ActiveX组件无法创建对象”的信息。
l VB6或其他工具,想要创建一个自定义的COM库的话,通常购买需要一个额外的许可证。
通过Excel执行API
本节我们探讨的方法,比之上节要简单一些,因为大多数的电脑中都安装了Excel。本方法是将所需函数嵌入到Excel工作簿中并通过Excel的COM API来调用。
按照下述步骤在一个Excel工作簿中嵌入一个与上节示例相同功能的COM库:
新建一个Excel文件,同时按下ALT + F11打开VBA编辑器,或在Tools à Marco à Visual Basic Editor 中将其打开。
添加一个模块,然后将下列代码拷贝到模块中:
‘声明GetCursorPos 函数
Public Declare Function GetCursorPos lib “user32” Alias “GetCursorPos” (lpPoint As POINTAPI) As Long
Public Type POINTAPI
X As Long
Y As Long
End Type
在”ThisWorkbook”工作表中写入我们要执行的程序,如图17-5所示。
Option Explicit
Public Function GetCursorPosition (ByRef x As Variant, ByRef y As Variant) As Long
Dim lpPoint As POINTAPI
GetCursorPosition = GetCursorPos(lpPoint)
X = lpPoint.x
Y=lpPoint.y
End Function
Image
保存工作表,本例中我们将其保存为C:\APICode.xls
在QTP中输入下述代码并执行,可调用我们保存在Excel中的函数:
‘打开保存着API代码的工作表
Set xlsApp = CreateObject(“Excel.Application”)
Set xlsWorkBook = xlsApp.WorkBooks.open(“C:\APICode.xls”)
‘声明传入GetCursorPosition函数时按地址传递的变量
Dim x, y
‘执行”ThisWorkBool”中的函数
xlsWorkBook.GetCursorPosition x,y
Msgbox “(“ & x & “,” & y & “)”
‘关闭Excel
xlsWorkBook.close
xlsApp.quit
set xlsWorkBool = Nothing
使用Excel的好处是:
l 代码会自动添加到QTP的数据表中,这是在每台装有QTP的电脑上都可以实现的
l 无需创建一个COM库
l 方便修改,而上节的方法每次修改都要重新编译
注意: 不要将宏保存到数据表(通常情况下就是该脚本下的Default.xls文件)中,因为一旦你保存测试脚本,这个工作表就会被覆盖。
有的人可能不喜欢这个方法,下一节我们将讲述一个更简单的方法,它将在程序运行时通过VBE类自动生成宏。
自动生成Excel宏
将下述代码保存到一个空文件中:
‘C:\MyCode.bas
Attribute VB_Name = “MyCode”
Option Explicit
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Declare Function GetCursorPos lib “user32” Alias “GetCursorPos” (lpPoint As POINTAPI) As Long
Public Function GetCursorPosition (ByRef x As Variant, ByRef y As Variant) As long
Dim lpPoint As POINTAPI
GetCursorPosition = GetCursorPos(lpPoint)
X = lpPoint.x
Y = lpPoint.y
End Function
提示:我们也可以通过 FileSystemObject 在运行脚本时生成该文件
‘创建一个Excel对象
Set xlsApp = CreateObject(“Excel.Application”)
‘新增一个工作簿
Set xlsWorkBook = xlsApp.WorkBools.Add
‘获取组件
Set xlsVBCompoonents = xlsWorkBook.VBProject.VBComponents
‘导入代码
xlsVBComponents.Import “C:\MyCode.bas”
Dim x,y
‘执行宏命令
xlsApp.Run “GetCursorPosition”,x,y
Msgbox “X:=“ & x & “, Y:=“ & y
注意: 如果在Excel宏设置中未勾选”信任对VBA工程对象模型的访问”,上述代码在执行时会出错。在Tools à Macro à Security…àTrusted Publishers (Tab)中,勾选”信任对VBA工程对象模型的访问”前面的复选框。
Image
我们还可以通过注册表来修改Excel的安全设置:
‘创建 WSCript shell 对象
Set oShell = CreateObject(“Wscript.Shell”)
‘启用对VB的信任
oShell.RegWrite “HKLW\SOFTWARE\Microsoft\Office\11.0\Excel\Security\AccseeVBOM”, 1, “REG_DWORD”
‘取消对VB信任
oShell.RegDelete “HKLW\SOFTWARE\Microsoft\Office\11.0\Excel\Security\AccseeVBOM”
警告: 我们在代码执行结束后一定要在注册表中删除这个键,以免你的系统受到太多的威胁。
然后导入上述代码模块:
‘生成VB 组件的实例
Set xlVBComponents = xlWorkBook.VBProject.VBComponents
xlVBComponents.Import “C:\MyCode.bas”
这里还有其他的途径来生成这个模块:
方法1:
‘创建模块文件
Const vbtext_ct_StdModule = 1
Set oModule = xlWorkBook.VBPeoject.VBComponents.Add (vbtext_ct_StdModule)
oModule.Name = “MyCode”
‘读取文件中的代码
oModule.CodeModule.AddFromFile “C:\MyCode.bas”
方法2:
‘获取ThisWorkBook的实例
Set oModule = xlWorkBook.VBPeoject.VBComponents.Item (“ThisWorkBook”)
‘在一个字符串中保存代码
Dim newCode
newCode = “Public Sub TestThis(ByVal pParam as string)” + vbNewLine
newCode = newCode + “Msgbox pParam” + vbNewLine
newCode = newCode + “End Sub” + vbNewLine
‘读取字符串中的代码
oModule.CodeModule.AddFromString newCode
‘调用刚添加的功能
xlsWorkBool.Testthis “Tarun Lalwani”
注意: 方法2中我们可以像调用普通的函数一样来调用TestThis 函数,而在方法1中需要通过组件对象来执行代码。
本节提出的技术有以下优点:
l 摆脱了QTP/VBS的限制,可以使用任何API函数及VBA支持的功能
l 为QTP自动化框架指出了新的方向
l 可以用来创建带有可选参数的函数
模式对话框
当被测程序在一个模式对话框需要关闭时,QTP脚本就无法继续执行,我们看一下下面的这个HTML页面:
<HTML>
<BODY>
<P>Select the file
<INPUT type=file name=mod>
</BODY>
</HTML>
现在让我们在这个页面尝试一下下述QTP代码:
‘通过DOM点击
Browser(“creationtime:=0”).Page(“micClass:=Page”).WebFile(“name:=mod”).object.click
Msgbox “2”
QTP会无限期等待,直到click事件完成后才会执行msgbox语句。这时由于并没有出错,场景恢复功能是不起作用的。在这种情况下,我们必须通过手动、或外部程序来关闭该对话框,或者模拟鼠标事件来完成点击。
下面让我们看看是否能通过API来解决这个问题。
FindWindow
本函数用来获取指定窗口的句柄T
FindWindowEx
本函数用来获取指定窗口的子对象的句柄,如获取一个消息框的一个按钮的句柄。
PostMessage
本函数用来向指定窗口的消息队列中添加信息
SetActiveWindow
本函数用来激活一个窗口并置于桌面顶层。
现在,我们在下面的代码中结合这些API函数来关闭一个非QTP的消息框。我们先根据标题获取一个对话框的句柄,然后获取它的关闭按钮的句柄,最后发送一个消息来点击这个按钮:
‘声明API函数
Extern.Declare micLong, “Findwindow”, “user32.dll”, “FindWindowA”, micString, micString
Extern.Declare micLong, “FindwindowEx”, “user32.dll”, “FindWindowExA”, micLong, micLong, micString, micString
Extern.Declare micLong, “PostMessage”, “user32.dll”, “PostMessage A”, micLong, micLong, micLong, micRef + micLong
Extern.Declare micLong, “SetActiveWindow”, “user32.dll”, “SetActiveWindow”, micLong
‘定义一个常量来模拟点击按钮
Private Const BN_CLICK = 245
‘这里要注意窗口及按钮名称都是大小写敏感的
sWindowName = “Test Window”
sWindowButton = “OK”
‘根据窗口标题获取其句柄
hwndWindow = Extern.FindWindow (vbNullString, SWindowName)
‘如果返回的句柄非零
If hwndWindow Then
‘获取窗口中按钮的句柄
hwndButton = Extern.FindeWindowEx(hwndWindow, 0, vbNullString, sWindowButton)
If hwndButton Then
Msgbox “Got the button, activating the window and clicking the button”
‘激活窗口
Extern.SetActiveWindow hwndWindwo
‘连续发送2次BN_CLICK信息,因为有时第一条信息会丢失
Extern.PostMessage hwndButton, BN_CLICK, 0, 0
Extern.PostMessage hwndButton, BN_CLICK, 0, 0
Else
Msgbox “Cannot find the button”
End If
Else
Msgbox “Cannot find the window”
End If
大家可以创建一个包含以下代码的VBS文件来测试上述代码:
‘使用msgbox函数创建一个模式对话框
Msgbox “Kill me now?”, “vbOKOnly”, “Test Window”
先执行这个脚本弹出一个消息框,然后再执行我们的QTP脚本,观察消息框有没有被关掉。 本方法还是有一些缺陷:
l 代码通过QTP执行,所以在执行时我们不能做任何事。
l 如果我们不能保证一定会有窗口出现,这段代码就有问题
l 不能用来关闭QTP产生的对话框
要避免上述问题,我们需要把代码写到VBS文件中,然后再QTP中用WSH Shell对象来调用。这意味着我们将离开QTP的环境来执行代码,所以Extern对象不再适用。由于QTP Extern对象是”Mercury.ExtrenObj”的COM对象,只要我们在机器上安装了QTP,我们就可以在VBS文件中重写这个功能。因为这个方法是独立于QTP之外的,我们还需要QTP执行时必需的参数:
‘C:\AutoClick.vbs:
Const micLong = 3
Const micString = 8
Const micByRef = 32768
Set Extern = CreateObject(“Mercury.ExternObj”)
Extern.Declare micLong, “Findwindow”, “user32.dll”, “FindWindowA”, micString, micString
Extern.Declare micLong, “FindwindowEx”, “user32.dll”, “FindWindowExA”, micLong, micLong, micString, micString
Extern.Declare micLong, “PostMessage”, “user32.dll”, “PostMessage A”, micLong, micLong, micLong, micRef + micLong
Extern.Declare micLong, “SetActiveWindow”, “user32.dll”, “SetActiveWindow”, micLong
‘定义一个常量来模拟点击按钮
Private Const BN_CLICK = 245
‘这里要注意窗口及按钮名称都是大小写敏感的
‘获取目的窗口及按钮的名称
sWindowName = WScript.Arguments(0)
sWidowButton = Wscript.Arguments(1)
‘直到窗口被点击后,再开始执行脚本
While Not AutoClickButton(sWindowName, sWindowButton)
Wend
Set Extern = Nothing
Private Function AutoClickButton(ByVal sWindowName, ByVal sWindowButton)
AutoClickButton = False
‘获取窗口句柄
hwndWindow = Extern.FindWindow (vbNullString, sWindowName)
If hwndWindow Then
‘获取窗口中按钮的句柄
hwndButton = Extern.FindeWindowEx(hwndWindow, 0, vbNullString, sWindowButton)
If hwndButton Then
‘激活窗口
Extern.SetActiveWindow hwndWindwo
‘连续发送2次BN_CLICK信息,因为有时第一条信息会丢失
Extern.PostMessage hwndButton, BN_CLICK, 0, 0
Extern.PostMessage hwndButton, BN_CLICK, 0, 0
End If
End If
End Function
现在让我们执行一下下述QTP脚本:
‘创建shell对象
Set WshShell = CreateObject(“Wscript.Shell”)
‘用双引号将字符串括起来。如果不这样做,”Test Window”就会被认为是Test,Window两‘个参数而不是一个字符串。
sWindowName = ““““ & “Test Window” & ““““
sWindowButton = ““““ & “OK” & ““““
Return = WshShell.Run(“C:\AutoClick.vbs “ & sWindowName & “ “ & sWindowButton, 1, False)
Set WshShell = Nothing
Msgbox “Kill me now?”, vbOKOnly, “Test Window”
注意:这里不会对WScript的工作方式进行阐述,若想获知更多细节请参考VBScript的参考手册。
执行上述代码,消息框就会自动关闭。我们的代码是一次性的因为一旦它成功关掉一个窗口就就会结束。存在多个窗口需要关闭时,可用下述代码多次调用此功能:
‘初始化自动点击脚本
Ret = WshShell.Run(“C:\AutoClick.vbs “ & sWindowName & “ “ & sWindowButton, 1, False)
此方法也可用于关闭Outlook的安全警告对话框。