RFT 描述性编程.docx
《RFT 描述性编程.docx》由会员分享,可在线阅读,更多相关《RFT 描述性编程.docx(17页珍藏版)》请在冰豆网上搜索。
RFT描述性编程
RFT描述性编程
1问题与背景
在RFT中,识别web对象的方式有两种,静态识别-通过对象库的方式,动态方式-通过RFT的find方法或者通过child逐级查找方式来识别。
在动态识别时,存在这样三个问题:
1.识别速度慢,可能需要4-5秒才能完成一个对象的识别。
2.对编写不规范的html对象,很难识别。
对于没有id,name属性的对象,很难准确识别。
3.脚本编写复杂,不易读。
WLink.findHtmlLinkFromTextProp("登录",browser).click()
WTextFieldusernameText=newWTextField("username",".name","Html.INPUT.text",browser);
usernameText.setText("abc");
特别是在识别文本框时,需要通过查看html代码的方式来查看该对象的id,name属性。
在识别一些编写不规范的html对象的时候,如图片等,如果没有id,name属性,将很难准确识别。
可以通过增加以下3个特性,来解决这些问题:
Ø缓存节点路径,提高识别速度。
Ø根据页面信息来描述对象,增加识别手段。
Ø描述性编程,使脚本更易读。
2描述性编程
在RFT中,识别web对象的方式有两种,静态识别-通过对象库的方式,动态方式-通过RFT的find方法或者自定义的查找方式来识别。
在IBM的框架中,如要查找一个输入框,其属性.name的值为username:
WBrowserbrowser=newWBrowser(BrowserOps.findBrowser());
WTextFieldusernameText=newWTextField("username",".name","Html.INPUT.text",browser);
usernameText.setText("123");
首先获取到浏览器对象,然后在浏览器中不断的递归查找节点,当其节点的属性符合.name的值为username,.class的值为Html.INPUT.text时,即可返回。
在QuickTest中,可以通过描述性编程的方式来动态识别一个Web对象:
Browser().Page().WebEdit("name:
=username").set"123"
相比之下,QTP的这种描述对象的方式,将属性名称与值作为同一个字符串输入,并且可以输入多个属性名称与值作为参数,使用起来更加方便。
在RFT中,也可以使用同样的描述性编程的方式来识别一个对象:
Browserbrowser=newBrowser();
browser.getWebEdit(".name:
=username",".class:
=Html.INPUT.text").setText("123");
或者
browser.getWebEdit(".name:
=username").setText("123");
实现的步骤为:
2.1定位浏览器对象
RFT有一个特性,新打开的最后一个浏览器,通过getDomains()查找时,一定是最先找到的。
通过getDomains()获得Html进程后,通过getTopObjects()方法,就可以获得浏览器对象。
publicstaticTopLevelTestObjectgetLastBrowser(StringtoType){
RationalTestScript.sleep(0.5);
DomainTestObjectdomains[]=RationalTestScript.getDomains();
StringdomainName;
TestObject[]topObjects;
Hashtableht;
StringsClass;
booleanvisible=false;
for(inti=0;itry{
domainName=domains[i].getName().toString();
if(domainName.equalsIgnoreCase("Html")){
topObjects=domains[i].getTopObjects();
if(topObjects!
=null){
for(intj=0;jht=topObjects[j].getProperties();
sClass=ht.get(".class").toString();
if(toType.equalsIgnoreCase(sClass)&&sClass.equalsIgnoreCase("Html.Dialog")){
if(ht!
=null){
visible=(boolean)ht.get(".visible");
if(visible){
return(TopLevelTestObject)topObjects[j];
}
}
}elseif(toType.equalsIgnoreCase(sClass)&&sClass.equalsIgnoreCase("Html.HtmlBrowser")){
return(BrowserTestObject)topObjects[j];
}
}
}
}
}catch(Exceptione){
//logger.error("Exception:
",e);
//noop-continueiftargethassincedisappeared
}
}
logger.error("foundnovalid:
"+toType);
returnnull;
}
如果查找不到浏览器对象,可以检查:
1浏览器是否打开。
2浏览器的add-on中,RFT的插件是否正确加载。
3如果安装的是jre1.7,win764位操作系统下,可能会出现不能识别浏览器的情况,可以尝试卸载jre1.7。
4win7下,通过runasadministrator来运行RFT,否则可能会出现脚本不能正确执行的情况。
2.2封装Web对象
下图是RFT的web对象继承关系图。
通过动态查找的方式来识别web对象时,都是从browser对象开始定位的,即BrowerTestObject.
为了便于操作,将页面上的对象封装成基类WebElement,所有的页面对象都继承自WebElement,实际就是对应于GuiTestObject.
类名
名称
主要识别属性
典型操作
典型对象
WebElement
所有的页面对象
.class
.id,.name
click
Html.DIV,Html.TR,Html.LI
WebButton
按钮
.class
.id,.value
click
Html.INPUT.button
Html.BUTTON
WebEdit
输入框
.class
.id,.name
setText
click
getText
Html.INPUT.text
Html.INPUT.password
Html.TEXTAREA
WebImage
图片
.class
.id,.name,.title,.src
click
Html.INPUT.image
Html.IMG
WebLink
链接
.class
.id,.text
click
Html.A
WebCheckBox
复选框
.class
.id,.name
check
uncheck
isChecked
Html.INPUT.check
WebRadioButton
单选按钮
.class
.id,.name
select
isSelected
Html.INPUT.radio
WebList
下拉框
多选框
.class
.id,.name
select
Html.SELECT
WebTable
表格
.class
.id,.name
getRowCount
getColumnCount
getCell
...
Html.TABLE
WinButton
弹出框按钮
.class
.text
click
Html.DialogButton
WinStatic
弹出框文本信息
.class
(index)
getText
Html.DialogStatic
publicclassWebElement{
GuiTestObjectwebElement=null;
publicWebElement(TestObjectto){
if(to==null){
webElement=null;
}else{
if(toinstanceofGuiTestObject){
webElement=(GuiTestObject)to;
}else{
logger.error("TestObjectclassisnotGuiTestObject:
"+to.getObjectClassName());
webElement=null;
}
}
}
}
publicclassWebEditextendsWebElement{
publicWebEdit(TestObjectto){
super(to);
}
publicvoidsetText(Strings){
this.click();//activatetextfield
this.clearText();
this.inputChars(s);
webElement.unregister();
}
}
2.3根据描述属性查找对象
识别一个Web对象,可能需要使用1个或者多个属性。
查找对象时,通过getChildren()方法来逐层查找,匹配这些属性,当属性值完全符合时,则认为已经找到。
1动态个数的输入参数,可以使用String...args来解决。
描述属性的key和value值通过:
=来分割。
如:
browser.getWebEdit(".class:
=Html.INPUT.text",".name:
=username").setText("123")
2将默认的.class属性写入,减少手工输入代码的工作量
publicWebEditgetWebEdit(String...properties){
HashMapdescMap=WebUtil.getDescMap(properties);
if(descMap!
=null){
if(!
descMap.containsKey(".class")){
descMap.put(".class","Html.INPUT.text");
}
}
WebElementtestObj=getWebElement(descMap);
if(testObj==null){
returnnewWebEdit(null);
}
logger.debug("foundtheobj");
return(WebEdit)testObj;
}
2先将字符串数组properties解析出属性的键值对,存放到HashMap中
`/**
*将".class:
=Html.A",".text:
=链接"的字符串描述转换成HashMap
*@paramproperties
*@return
*/
publicstaticHashMapgetDescMap(String[]properties){
HashMapdescMap=newHashMap();
String[]tempAttrbute;
for(Stringproperty:
properties){
tempAttrbute=property.split(":
=");
if(tempAttrbute.length==2){
descMap.put(tempAttrbute[0],tempAttrbute[1]);
}else{
logger.error("Error:
descriptionstringisinvalid:
"
+property);
returnnull;
}
}
returndescMap;
}
3按照属性在页面上查找对象
查找对象的方法,使用递归查找child的方法,其好处是,可靠性较好,只要页面上存在指定的对象,则必定可以查找到。
缺点就是查找速度慢,无论是按照深度优先,还是按照广度优先查找,因为必须将前序节点的属性都逐一进行检查。
不过这个问题可以通过后续的缓存路径的方式来解决。
publicstaticTestObjectgetChildObjects(HashMapdescMap,TestObjectparent){
TestobjecttestObj=null;
if(isMatchObject(parent,descMap)){
returnparent;
TestObject[]childObjs=parent.getChildren();
//循环查找直接子对象
for(inti=0;itestObj=getChildObjects(descMap,childObjs[i);
}
returntestObj;
}
4属性匹配。
优先匹配.class属性
publicstaticbooleanisMatchObject(TestObjecttestObj,HashMapdescMap){
StringactualClass=null;
StringexpectVal;
StringactualVal;
HashtableproTable=testObj.getProperties();
if(descMap.containsKey(".class")){//优先匹配.class属性
actualClass=proTable.get(".class").toString();
if(!
actualClass.equalsIgnoreCase(descMap.get(".class"))){
returnfalse;
}
}
booleanisMatch=true;
for(Stringproperty:
descMap.keySet()){
if(!
property.equalsIgnoreCase(".class")){//逐个遍历剩下的属性
expectVal=descMap.get(property).trim();
if(proTable.containsKey(property)){
actualVal=proTable.get(property).toString().trim();
isMatch=expectVal.equalsIgnoreCase(actualVal);
}else{
isMatch=false;
}
if(!
isMatch){
break;
}
}
}
returnisMatch;
}
5增加index的识别属性。
通过描述的属性,可能会查找到多个符合属性的元素,可以增加index的属性,默认查找第一个,如果指定了index的值,则按照index的值返回对应的元素。
3缓存节点路径
通过深度优先或广度优先的顺序来查找页面节点,速度必然会慢。
为了加快速度,可以将第一次查找到节点的路径保存起来,第二次查找时,则根据之前保存的路径直接比对,以此提高二次查找的效率。
对每一层的节点按照顺序进行编号,每一层的起始编号均为0。
如上图,username的输入框的路径为0-1-0-0-0,密码的输入框的路径为0-1-0-0-1。
节点路径会保存在缓存文件(key-value的txt文件)中,key和value之间用两个#号分割,key为Web对象的描述属性按照指定顺序连接起来的字符串,value为节点的数字路径。
.class:
=Html.INPUT.text,.name:
=userId##0_3_0_1_0_4_3_0_0_0_0_0_3_0
.class:
=Html.INPUT.password,.id:
=oldPassword##0_3_0_1_0_4_3_0_0_0_1_0_3_0
由于同一个缓存文件中,并不能区分web对象所在的页面,所以,如果两个不同的页面上,都存在同样属性的web对象,如"确定"按钮,就会出现互相覆盖,导致缓存错误的情况。
另外在有些业务系统中,每个用户的权限不同,或者业务流程不同,同一个对象在同一个页面上的路径也会不同,如菜单,每个用户登录后的菜单顺序都会不同。
因此就需要采取以下措施来避免:
1将这两个对象保存到两个不同的缓存文件中。
2增加额外的描述属性来区别这两个对象。
如增加descName,extName这两个属性,它们不参与对象的属性匹配,但是会保存成不同的key值,形成两条不同的记录。
查找对象时的逻辑为:
1根据对象描述属性,生成唯一的缓存key值
2检查缓存文件,缓存文件中是否存在该key值。
2.1如果存在该key值,取出value--节点的路径
2.1.1在页面上,直接根据路径,找到对应的节点
2.1.2检查找到的节点是否符合描述的属性,
2.1.2.1如果符合描述的属性,则返回该对象。
2.1.2.2如果不符合描述的属性,根据缓存路径查找对象失败,跳转到根据属性查找对象流程
2.2如果不存在该key值,则根据缓存路径查找对象失败,跳转到根据属性查找流程
3根据描述属性,逐级在页面上查找该对象
4如果查找到符合属性的对象,将该节点的路径保存到缓存文件中,然后返回该对象进行后续操作
5如果未查找到符合属性的对象,提示错误。
通过缓存来查找web对象,大约0.5秒就能查找到正确的对象,足够满足自动化测试的需要。
publicstaticWebElementgetChildByCachePath(TestObjecttestObj,HashMapdescMap,StringcachePath){
if(cachePath!
=null&&cachePath.length()>0){
String[]cacheStep=cachePath.split("_");
intseq=0;
for(inti=1;iseq=Integer.parseInt(cacheStep[i]);
TestObject[]tos=testObj.getChildren();
if(tos.length>seq){
testObj=tos[seq];
}else{
logger.warn("notfoundobjectbycachepath:
level="+i+"childcnt="+tos.length+",butexpectsequence="+seq);
returnnull;
}
}
logger.debug("foundone");
try{
if(isMatchObject(testObj,descMap,cachePath)){
returnsubTypeWebElement(testObj);
}else{
logger.warn("foundobjectbycachepath,butnotmatch:
"+descMap.toString()+"\t"+cachePath);
returnnull;
}
}catch(Exceptione){
logger.error("foundobjectbycachepath,butexcption");
logger.error("Exception:
",e);
returnnull;
}
}
logger.debug("nocachepathfor:
"+descMap.toString());
returnnull;
4根据页面信息来描述对象
一个普通的文本输入框:
username:
|___________|
这个文本框的html结构可能有这样几种形式:
uesername: /td> | |
或者
现有的识别方式,都要通过查看这个输入框的id,name属性来识别。
对于这样一个输入框,人肉眼是如何来识别这个输入框的呢?
必定是根据输入框前的文本信息"username",而不是该输入框的id,name属性。
4.1通过左边文字来描述对象
那么在程序中如何来通过左边的文字来识别一个文本框呢?
browser.getWebEdit(
展开阅读全文
相关搜索
|