元素对象管理:
元素对象(以下称为locator)的维护与管理很麻烦,因为locator比较多,每个页面上要操作的可能有几十个,如何快速的查找及维护好能够使我们写脚本的速度及维护速度大大提升。在前端开发中,开发人员通常是把UI样式放在CSS文件中,受此影响,我们也可以把我们的locator放在一个专门的文件中,按照页面来分类,提取其公共的locator放在公共的文件中,这样或许可以提升些许编写脚本速度及后期维护成本,效果就是如果UI变了,我们只需要修改对应的页面中的locator就行了,脚本都不需要重新编译(如果是用需要编译的语言,如JAVA),下面我将介绍一下如何放在专门的文件中,如何解析该文件,及在脚本中如何调用。下面的脚本语言为JAVA。
- 文件类型------yaml
- java解析yaml文件所需要的jar包:jyaml-1.3.jar,需自已在网上下载。
- 格式介绍:
a. baidu_input后面接上":",直接回车,然后空两格
b. type与value这两个key是固定的,后面接上":",然后空一格,也可以不空,如果value后面是xpath,建议用加上引号,具体去看下yaml的格式,百度一大堆。
c. 在webdriver中,有By.id,By.name,By.xpath,By.className,By.linkText等,我们选取这几种常见的,所以type的冒包后面可用的值为id,name,xpath
d. value的值为type中对应的类型的值,比如百度首页上的输入框的id='kw',所以在yaml文件中的写法如上图所示 - 解析上述的yaml文件:
12345678910111213141516171819202122232425262728import
org.ho.yaml.Yaml;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.util.HashMap;
public
class
Demo {
private
String yamlFile;
public
Demo() {
yamlFile =
"demo"
;
this
.getYamlFile();
}
private
HashMap<String, HashMap<String, String>> ml;
@SuppressWarnings
(
"unchecked"
)
public
void
getYamlFile() {
File f =
new
File(
"locator/"
+ yamlFile +
".yaml"
);
try
{
ml = Yaml.loadType(
new
FileInputStream(f.getAbsolutePath()),
HashMap.
class
);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
}
可以在本地创建一个demo.yaml文件,保存在locator目录中,locator与src同级目录,然后写个main方法来调用一个getYamlFile方法,可以看到解析demo.yaml后的值都赋给了变量ml。解析过程如此简单,解析速度如此之快,yaml文件也比较直观,这是我选择用yaml文件的原因,当然可能还有其它更好的选择,大家可以自行尝试。
- 我们在写脚本时,元素对象一般是这样写的WebElement element = driver.findElement(By.id("kw"));所以接下来我们要把ml变量里的"value"转换成By对象。添加如下代码
12345678910111213141516171819
private
By getBy(String type, String value) {
By by =
null
;
if
(type.equals(
"id"
)) {
by = By.id(value);
}
if
(type.equals(
"name"
)) {
by = By.name(value);
}
if
(type.equals(
"xpath"
)) {
by = By.xpath(value);
}
if
(type.equals(
"className"
)) {
by = By.className(value);
}
if
(type.equals(
"linkText"
)) {
by = By.linkText(value);
}
return
by;
}
这样通过ml中的type与value的值就对产生一个By对象。
- By对象产生后,就可以把这个对象传给driver.findElement方法,继而生成一个WebElement对象.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import
org.ho.yaml.Yaml;
import
org.openqa.selenium.By;
import
org.openqa.selenium.WebDriver;
import
org.openqa.selenium.WebElement;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.util.HashMap;
public
class
Demo {
private
String yamlFile;
public
WebDriver driver;
public
Demo() {
driver = DriverInstance.getInstance();
yamlFile =
"demo"
;
this
.getYamlFile();
}
private
HashMap<String, HashMap<String, String>> ml;
@SuppressWarnings
(
"unchecked"
)
public
void
getYamlFile() {
File f =
new
File(
"locator/"
+ yamlFile +
".yaml"
);
try
{
ml = Yaml.loadType(
new
FileInputStream(f.getAbsolutePath()),
HashMap.
class
);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
private
By getBy(String type, String value) {
By by =
null
;
if
(type.equals(
"id"
)) {
by = By.id(value);
}
if
(type.equals(
"name"
)) {
by = By.name(value);
}
if
(type.equals(
"xpath"
)) {
by = By.xpath(value);
}
if
(type.equals(
"className"
)) {
by = By.className(value);
}
if
(type.equals(
"linkText"
)) {
by = By.linkText(value);
}
return
by;
}
public
WebElement getElement(String key) {
String type = ml.get(key).get(
"type"
);
String value = ml.get(key).get(
"value"
);
return
driver.findElement(
this
.getBy(type, value));
}
public
static
void
main(String[] args){
Demo d =
new
Demo();
WebElement element = d.getElement(
"baidu_input"
);
element.sendKeys(
""
);
}
}
- 到这里,已经成功了一半,因为已经把yaml文件中保存的元素成功的转化成了WebElement对象。但是还不够,接下来我们引入一下同步点的概念,就是在调用locator时,保证locator是显示在页面上的,webdriver中有个WebDriverWait对象,代码如下:
123456789101112131415private
WebElement watiForElement(
final
By by) {
WebElement element =
null
;
int
waitTime = Integer.parseInt(Config.getConfig(
"waitTime"
));
try
{
element =
new
WebDriverWait(driver, waitTime)
.until(
new
ExpectedCondition<WebElement>() {
public
WebElement apply(WebDriver d) {
return
d.findElement(by);
}
});
}
catch
(Exception e) {
System.out.println(by.toString() +
" is not exist until "
+ waitTime);
}
return
element;
}
于是乎getElement方法里面就可以改为
12345public
WebElement getElement(String key) {
String type = ml.get(key).get(
"type"
);
String value = ml.get(key).get(
"value"
);
return
this
.watiForElement(
this
.getBy(type, value));
}
- 到这一步,又改进了一点,新的问题也随之产生了,watiForElement这个方法,返回的WebElement对象包括隐藏的,如果是隐藏的,那么在操作的时候,自然而然会报错,所以,我们得把隐藏的去掉,只显示displayed的元素对象,增加一个方法。
1234567891011121314151617private
boolean
waitElementToBeDisplayed(
final
WebElement element) {
boolean
wait =
false
;
if
(element ==
null
)
return
wait;
try
{
wait =
new
WebDriverWait(driver, Integer.parseInt(Config
.getConfig(
"waitTime"
)))
.until(
new
ExpectedCondition<Boolean>() {
public
Boolean apply(WebDriver d) {
return
element.isDisplayed();
}
});
}
catch
(Exception e) {
System.out.println(element.toString() +
" is not displayed"
);
}
return
wait;
}
如此一来,getElement方法又可以改进一下了。
12345678public
WebElement getElement(String key) {
String type = ml.get(key).get(
"type"
);
String value = ml.get(key).get(
"value"
);
WebElement element =
this
.watiForElement(
this
.getBy(type, value));
if
(!
this
.waitElementToBeDisplayed(element))
element =
null
;
return
element;
}
- 既然有等待元素对象显示的,那么反之就有等待元素对象消失的方法。
123456789101112131415161718public
boolean
waitElementToBeNonDisplayed(
final
WebElement element) {
boolean
wait =
false
;
if
(element ==
null
)
return
wait;
try
{
wait =
new
WebDriverWait(driver, Integer.parseInt(Config
.getConfig(
"waitTime"
)))
.until(
new
ExpectedCondition<Boolean>() {
public
Boolean apply(WebDriver d) {
return
!element.isDisplayed();
}
});
}
catch
(Exception e) {
System.out.println(
"Locator ["
+ element.toString()
+
"] is also displayed"
);
}
return
wait;
}
- 看上去一切很美好了,but....如果我们要验证一个元素对象不出现在页面上,就会出现问题了,于是增加一个方法
1234567891011public
WebElement getElementNoWait(String key) {
WebElement element =
null
;
String type = ml.get(key).get(
"type"
);
String value = ml.get(key).get(
"value"
);
try
{
element = driver.findElement(
this
.getBy(type, value));
}
catch
(Exception e){
element =
null
;
}
return
element;
}
- 现在的问题是getElement与getElementNoWait的方法体很接近,于是我们来重构下这部分的代码,先增加一个方法,存放相同的方法体
123456789101112131415161718192021222324private
WebElement getLocator(String key,
boolean
wait) {
WebElement element =
null
;
if
(ml.containsKey(key)) {
HashMap<String, String> m = ml.get(key);
String type = m.get(
"type"
);
String value = m.get(
"value"
);
By by =
this
.getBy(type, value);
if
(wait) {
element =
this
.watiForElement(by);
boolean
flag =
this
.waitElementToBeDisplayed(element);
if
(!flag)
element =
null
;
}
else
{
try
{
element = driver.findElement(by);
}
catch
(Exception e) {
element =
null
;
}
}
}
else
System.out.println(
"Locator "
+ key +
" is not exist in "
+ yamlFile
+
".yaml"
);
return
element;
}
再把getElement与getElementNoWait方法进行修改
1234567public
WebElement getElement(String key) {
return
this
.getLocator(key,
true
);
}
public
WebElement getElementNoWait(String key) {
return
this
.getLocator(key,
false
);
}
- 到现在为止,已经可以满足绝大部分的需求了,完全可以使用了,下面的任务就是来点锦上添花了,举个例子,在yaml文件中,允许使用参数,比如
123456baidu_input:
type: id
value:
"%s%s"
baidu_button:
type: id
value:
"%s"
在这里面的参数用%s来表示,于是在脚本中,我们调用getElement与getElementNoWait方法时需要我们把value给传进去,我们再来处理下这部分,增加一个方法。
123456private
String getLocatorString(String locatorString, String[] ss) {
for
(String s : ss) {
locatorString = locatorString.replaceFirst(
"%s"
, s);
}
return
locatorString;
}
在上面的方法中,我们可以看到,对于value值,我们是通过一个数组循环的去替代里面的%s,再把该方法结合到getLocator方法中去。
1234567891011121314151617181920212223242526private
WebElement getLocator(String key, String[] replace,
boolean
wait) {
WebElement element =
null
;
if
(ml.containsKey(key)) {
HashMap<String, String> m = ml.get(key);
String type = m.get(
"type"
);
String value = m.get(
"value"
);
if
(replace !=
null
)
value =
this
.getLocatorString(value, replace);
By by =
this
.getBy(type, value);
if
(wait) {
element =
this
.watiForElement(by);
boolean
flag =
this
.waitElementToBeDisplayed(element);
if
(!flag)
element =
null
;
}
else
{
try
{
element = driver.findElement(by);
}
catch
(Exception e) {
element =
null
;
}
}
}
else
System.out.println(
"Locator "
+ key +
" is not exist in "
+ yamlFile
+
".yaml"
);
return
element;
}
可以看到getLocator方法的参数变了,于是要重新的更改getElement与getElementNoWait方法,同时重载这两个方法。
123456789101112131415public
WebElement getElement(String key) {
return
this
.getLocator(key,
null
,
true
);
}
public
WebElement getElementNoWait(String key) {
return
this
.getLocator(key,
null
,
false
);
}
public
WebElement getElement(String key, String[] replace) {
return
this
.getLocator(key, replace,
true
);
}
public
WebElement getElementNoWait(String key, String[] replace) {
return
this
.getLocator(key, replace,
false
);
}
- 惊喜?更大的还在后面。再举个例子:
123456baidu_input:
type: xpath
value:
"//div[@id='%productId%']//div"
baidu_button:
type: xpath
value:
"//div[@id='%productId%']//input[@name='button']"
类似于上面这种,整个里面都含有productId, 于是我们可以通过一个方法,调用这个方法后,里面的都会被替换掉,该方法如下。
1234567public
void
setLocatorVariableValue(String variable, String value){
Set<String> keys = ml.keySet();
for
(String key:keys){
String v = ml.get(key).get(
"value"
).replaceAll(
"%"
+variable+
"%"
, value);
ml.get(key).put(
"value"
,v);
}
}
- 再比如,有一些元素对象是每个页面都会出现的,是公共的,这些公共的locator只是有时候要用到,大部分时候都不用,所以,我们把这些公共的放在一个特定的文件里,在需要的时候通过外部加载来使用这些公共的locator,增加一个变量与方法。
123456789101112private
HashMap<String, HashMap<String, String>> extendLocator;
@SuppressWarnings
(
"unchecked"
)
public
void
loadExtendLocator(String fileName){
File f =
new
File(
"locator/"
+ fileName +
".yaml"
);
try
{
extendLocator = Yaml.loadType(
new
FileInputStream(f.getAbsolutePath()),
HashMap.
class
);
ml.putAll(extendLocator);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
- 到此为止,整个元素对象管理就结束了,这只是提供一个思路,大家如果有耐心从上到下的给按着来写一遍,应该会了解不少。最后来个总结性的代码。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170public
class
Page {
public
WebDriver driver;
private
String yamlFile;
public
Page() {
driver = DriverInstance.getInstance();
yamlFile =
"demo"
;
this
.getYamlFile();
}
private
HashMap<String, HashMap<String, String>> ml;
private
HashMap<String, HashMap<String, String>> extendLocator;
@SuppressWarnings
(
"unchecked"
)
protected
void
getYamlFile() {
File f =
new
File(
"locator/"
+ yamlFile +
".yaml"
);
try
{
ml = Yaml.loadType(
new
FileInputStream(f.getAbsolutePath()),
HashMap.
class
);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
@SuppressWarnings
(
"unchecked"
)
public
void
loadExtendLocator(String fileName){
File f =
new
File(
"locator/"
+ fileName +
".yaml"
);
try
{
extendLocator = Yaml.loadType(
new
FileInputStream(f.getAbsolutePath()),
HashMap.
class
);
ml.putAll(extendLocator);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
public
void
setLocatorVariableValue(String variable, String value){
Set<String> keys = ml.keySet();
for
(String key:keys){
String v = ml.get(key).get(
"value"
).replaceAll(
"%"
+variable+
"%"
, value);
ml.get(key).put(
"value"
,v);
}
}
private
String getLocatorString(String locatorString, String[] ss) {
for
(String s : ss) {
locatorString = locatorString.replaceFirst(
"%s"
, s);
}
return
locatorString;
}
private
By getBy(String type, String value) {
By by =
null
;
if
(type.equals(
"id"
)) {
by = By.id(value);
}
if
(type.equals(
"name"
)) {
by = By.name(value);
}
if
(type.equals(
"xpath"
)) {
by = By.xpath(value);
}
if
(type.equals(
"className"
)) {
by = By.className(value);
}
if
(type.equals(
"linkText"
)) {
by = By.linkText(value);
}
return
by;
}
private
WebElement watiForElement(
final
By by) {
WebElement element =
null
;
int
waitTime = Integer.parseInt(Config.getConfig(
"waitTime"
));
try
{
element =
new
WebDriverWait(driver, waitTime)
.until(
new
ExpectedCondition<WebElement>() {
public
WebElement apply(WebDriver d) {
return
d.findElement(by);
}
});
}
catch
(Exception e) {
System.out.println(by.toString() +
" is not exist until "
+ waitTime);
}
return
element;
}
private
boolean
waitElementToBeDisplayed(
final
WebElement element) {
boolean
wait =
false
;
if
(element ==
null
)
return
wait;
try
{
wait =
new
WebDriverWait(driver, Integer.parseInt(Config
.getConfig(
"waitTime"
)))
.until(
new
ExpectedCondition<Boolean>() {
public
Boolean apply(WebDriver d) {
return
element.isDisplayed();
}
});
}
catch
(Exception e) {
System.out.println(element.toString() +
" is not displayed"
);
}
return
wait;
}
public
boolean
waitElementToBeNonDisplayed(
final
WebElement element) {
boolean
wait =
false
;
if
(element ==
null
)
return
wait;
try
{
wait =
new
WebDriverWait(driver, Integer.parseInt(Config
.getConfig(
"waitTime"
)))
.until(
new
ExpectedCondition<Boolean>() {
public
Boolean apply(WebDriver d) {
return
!element.isDisplayed();
}
});
}
catch
(Exception e) {
System.out.println(
"Locator ["
+ element.toString()
+
"] is also displayed"
);
}
return
wait;
}
private
WebElement getLocator(String key, String[] replace,
boolean
wait) {
WebElement element =
null
;
if
(ml.containsKey(key)) {
HashMap<String, String> m = ml.get(key);
String type = m.get(
"type"
);
String value = m.get(
"value"
);
if
(replace !=
null
)
value =
this
.getLocatorString(value, replace);
By by =
this
.getBy(type, value);
if
(wait) {
element =
this
.watiForElement(by);
boolean
flag =
this
.waitElementToBeDisplayed(element);
if
(!flag)
element =
null
;
}
else
{
try
{
element = driver.findElement(by);
}
catch
(Exception e) {
element =
null
;
}
}
}
else
System.out.println(
"Locator "
+ key +
" is not exist in "
+ yamlFile
+
".yaml"
);
return
element;
}
public
WebElement getElement(String key) {
return
this
.getLocator(key,
null
,
true
);
}
public
WebElement getElementNoWait(String key) {
return
this
.getLocator(key,
null
,
false
);
}
public
WebElement getElement(String key, String[] replace) {
return
this
.getLocator(key, replace,
true
);
}
public
WebElement getElementNoWait(String key, String[] replace) {
return
this
.getLocator(key, replace,
false
);
}
}