前言:
我的博客從來都是言簡意賅,直接步入正題。
元素對象管理:
元素對象(以下稱為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文件:
import 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對象。添加如下代碼
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對象.
import 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對象,代碼如下:
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; }
於是乎getElement方法里面就可以改為
public 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的元素對象,增加一個方法。
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; }
如此一來,getElement方法又可以改進一下了。
public 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; }
- 既然有等待元素對象顯示的,那么反之就有等待元素對象消失的方法。
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; }
- 看上去一切很美好了,but....如果我們要驗證一個元素對象不出現在頁面上,就會出現問題了,於是增加一個方法
public 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的方法體很接近,於是我們來重構下這部分的代碼,先增加一個方法,存放相同的方法體
private 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方法進行修改
public WebElement getElement(String key) { return this.getLocator(key, true); } public WebElement getElementNoWait(String key) { return this.getLocator(key, false); }
- 到現在為止,已經可以滿足絕大部分的需求了,完全可以使用了,下面的任務就是來點錦上添花了,舉個例子,在yaml文件中,允許使用參數,比如
baidu_input: type: id value: "%s%s" baidu_button: type: id value: "%s"
在這里面的參數用%s來表示,於是在腳本中,我們調用getElement與getElementNoWait方法時需要我們把value給傳進去,我們再來處理下這部分,增加一個方法。
private String getLocatorString(String locatorString, String[] ss) { for (String s : ss) { locatorString = locatorString.replaceFirst("%s", s); } return locatorString; }
在上面的方法中,我們可以看到,對於value值,我們是通過一個數組循環的去替代里面的%s,再把該方法結合到getLocator方法中去。
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; }
可以看到getLocator方法的參數變了,於是要重新的更改getElement與getElementNoWait方法,同時重載這兩個方法。
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); }
- 驚喜?更大的還在后面。再舉個例子:
baidu_input: type: xpath value: "//div[@id='%productId%']//div" baidu_button: type: xpath value: "//div[@id='%productId%']//input[@name='button']"
類似於上面這種,整個里面都含有productId, 於是我們可以通過一個方法,調用這個方法后,里面的都會被替換掉,該方法如下。
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); } }
- 再比如,有一些元素對象是每個頁面都會出現的,是公共的,這些公共的locator只是有時候要用到,大部分時候都不用,所以,我們把這些公共的放在一個特定的文件里,在需要的時候通過外部加載來使用這些公共的locator,增加一個變量與方法。
private 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(); } }
- 到此為止,整個元素對象管理就結束了,這只是提供一個思路,大家如果有耐心從上到下的給按着來寫一遍,應該會了解不少。最后來個總結性的代碼。
public 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); } }
- 敬請期待下期干貨。。。。。。