以前博客中已經將筆者實現的框架進行過簡單介紹,在使用過程當中,對如下幾點提出優化:css
1.頁面URL和頁面的定位信息保存不一樣的配置文件中-----------整合到一個配置文件中,相應的配置文件解析作出調整html
2.將項目部署到Jenkins以後,出現Chrome驅動啓動失敗的問題(經過Jenkins運行時,會去找Chrome的chrome.exe)-----------修改driver的配置文件並對其解析類進行一些修改java
3.在一個測試case中,對元素進行定位,每次都要傳入多個參數(頁面配置信息文件地址、頁面名稱、元素描述信息),會使得代碼可讀性不夠高----------自定義一個TargetPage註解,將頁面配置信息的文件地址、頁面描述保存在其中。mysql
4.拋棄比較醜的reportng報告,使用Allure2生成測試報告web
5.不一樣的環境鏈接不一樣的數據庫,爲了解決能夠讀取不一樣數據庫問題,修改了數據庫配置文件,使用添加前綴的方式來指定數據庫(數據量比較大,參數比較多時,筆者傾向於將測試數據放到數據庫中。而且在計劃使用SpringMVC實現一套自動化測試工具,這也是爲其作準備)。sql
爲了使你們能更清晰的理解該套框架,下面將UML圖提供出來:chrome
下面上源碼:數據庫
1.pom.xmlapache
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.claire.jing</groupId> <artifactId>UIAutoTest0928</artifactId> <version>1.0-SNAPSHOT</version> <properties> <aspectj.version>1.8.10</aspectj.version> </properties> <dependencies> <dependency> <groupId>io.qameta.allure</groupId> <artifactId>allure-testng</artifactId> <version>2.6.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.testng/testng --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.14.3</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/dom4j/dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.10</version> <configuration> <!--設置參數命令行--> <argLine> -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" </argLine> <systemPropertyVariables> <!--是否忽略html,解釋見下圖。與以後在reportNg報告上顯示截圖相關。--> <org.uncommons.reportng.escape-output>false</org.uncommons.reportng.escape-output> </systemPropertyVariables> <!--測試失敗後,是否忽略並繼續測試--> <testFailureIgnore>true</testFailureIgnore> <argLine> -Dfile.encoding=UTF-8 </argLine> <suiteXmlFiles> <!--表明的是要執行的測試套件名稱--> <suiteXmlFile>src/test/resources/testNG.xml</suiteXmlFile> </suiteXmlFiles> </configuration> </plugin> </plugins> <finalName>activiti-study</finalName> <resources> <resource> <directory>${basedir}</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build> </project>
2.driver配置文件以及解析文件的util數組
<?xml version="1.0" encoding="UTF-8"?> <drivers index="0"> <!--Chrome瀏覽器的配置--> <driver name="org.openqa.selenium.chrome.ChromeDriver" index="0"> <properties> <property> <name>webdriver.chrome.driver</name> <value>{resourcespath}chromedriver.exe</value> </property> </properties> <options> <option> <name>C:/Users/Samps/AppData/Local/Google/Chrome/Application/chrome.exe</name> </option> </options> </driver> <!--爲3.xselenium提供的配置文件,是須要driver的--> <driver name="org.openqa.selenium.firefox.FirefoxDriver" index="1"> <properties> <property> <name>webdriver.gecko.driver</name> <value>{resourcespath}geckodriver.exe</value> </property> <property> <name>webdriver.firefox.bin</name> <value>C:\Program Files\Mozilla Firefox\firefox.exe</value> </property> </properties> </driver> <!--爲selenium2.x提供的配置文件,內部繼承了firfox的驅動,不須要設置驅動地址--> <driver name="org.openqa.selenium.firefox.FirefoxDriver" index="2"> <properties> <property> <name>webdriver.firefox.bin</name> <value>C:\Program Files\Mozilla Firefox\firefox.exe</value> </property> </properties> </driver> <!--IE瀏覽器的配置--> <driver name="org.openqa.selenium.ie.InternetExplorerDriver" index="3"> <properties> <property> <name>webdriver.ie.driver</name> <value>{resourcespath}IEDriverServer.exe</value> </property> </properties> <capabilities> <capability> <name>InternetExplorerDriver.IGNORE_ZOOM_SETTING</name> </capability> <capability> <name>InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS</name> </capability> </capabilities> </driver> </drivers>
package com.claire.jing.utils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.DesiredCapabilities; import java.io.InputStream; import java.util.List; public class GetDriverUtil { /** * example * @param args */ public static void main(String[] args) { WebDriver driver = getDriver(); driver.quit(); } /** * 啓動瀏覽器,獲取到瀏覽器驅動。 * 支持瀏覽器類型爲:火狐,谷歌,IE。 * 類型設置在drivercon.xml文件中。修改根節點index值便可啓動對應的瀏覽器 * @return */ public static WebDriver getDriver() { SAXReader reader = new SAXReader(); InputStream in = GetDriverUtil.class.getResourceAsStream("/drivercon.xml"); Document document = null; try { document = reader.read(in); } catch (DocumentException e) { e.printStackTrace(); } Element rootElement = document.getRootElement();//獲取到根節點 String targetIndex = rootElement.attributeValue("index");//獲取到根節點中的driver List<Element> drivers = rootElement.elements("driver");//獲取到全部的driver節點 String className = null; Element targetDriver = null; for (Element driver : drivers) { String index = driver.attributeValue("index"); if (index.equals(targetIndex)){ className =driver.attributeValue("name");//獲取到目標瀏覽器驅動的類全名 targetDriver = driver; break; } } Class<?> driverClass = null;//反射獲取到驅動的類引用 try { driverClass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } Element properties = targetDriver.element("properties");//獲取到目標驅動的properties節點 List<Element> allProperties = properties.elements("property");//獲取到目標驅動的全部property節點,即:要設置的系統屬性 //遍歷並設置全部的系統屬性 for (Element property : allProperties) { String propertyName = property.element("name").getText(); String path = property.element("value").getText(); String propertyValue = pathFormat(path); System.setProperty(propertyName,propertyValue); } Element capabilities = targetDriver.element("capabilities");//獲取到目標驅動的capabilities if (capabilities != null){ List<Element> allCapabilities = capabilities.elements("capability");//獲取到目標驅動的全部capability節點,即:要設置IE瀏覽器的能力 for (Element capability : allCapabilities) { String capabilieyToSet = capability.element("name").getText(); DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); desiredCapabilities.setCapability(capabilieyToSet,true); } } Element optionsElement = targetDriver.element("options");//獲取到目標驅動下的options ChromeOptions chromeOptions =null; if (optionsElement != null){ List<Element> allOptions = optionsElement.elements("option"); for (Element option : allOptions) { String binaryPath = option.element("name").getText(); System.out.println("chrome的binary地址爲"+binaryPath); chromeOptions = new ChromeOptions(); chromeOptions.setBinary(binaryPath); } System.out.println("建立的是Chrome的驅動哦"); return new ChromeDriver(chromeOptions); } WebDriver driver = null; try { driver = (WebDriver) driverClass.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return driver; } /** * 格式化driver文件的位置 * * @param path * 傳入的瀏覽器路徑設置值 * 當帶有佔位符resourcespath時,解析爲"src/test/resources/" * 沒有佔位符時,原樣返回 * @return * 返回格式化後的driver路徑 */ public static String pathFormat(String path){ if (path.contains("{resourcespath}")){ path = "src/test/resources/"+path.substring(15); System.out.println("格式化以後的路徑爲"+path); return path; } return path; } }
3.BaseTester
package com.claire.jing.base; import com.claire.jing.annocations.TargetPage; import com.claire.jing.annocations.TargetTestData; import com.claire.jing.elementLocator.ElementInfo; import com.claire.jing.utils.ElementLocatorUtil; import com.claire.jing.utils.ExcelDataproviderUtil; import com.claire.jing.utils.GetDriverUtil; import com.claire.jing.utils.MysqlDataproviderUtil; import org.apache.log4j.Logger; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.Assert; import org.testng.ITestNGMethod; import org.testng.ITestResult; import org.testng.Reporter; import org.testng.annotations.AfterSuite; import org.testng.annotations.BeforeSuite; import org.testng.annotations.DataProvider; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Iterator; import java.util.List; import java.util.Set; public class BaseTester { public static WebDriver driver; public static Logger logger = Logger.getLogger(BaseTester.class); /*-----------------------------------測試先後準備工做--------------------------------------------------------*/ @BeforeSuite public void beforeSuit() { driver = GetDriverUtil.getDriver();//測試開始以前,啓動瀏覽器 } @AfterSuite public void afterSuit() { driver.quit();//測試結束以後,退出瀏覽器驅動,並關閉全部相關瀏覽器窗口 } /*----------------------------------數據提供者----------------------------------------------------*/ @DataProvider public Iterator<Object[]> testData(Method method) { TargetTestData annotation = method.getAnnotation(TargetTestData.class); String simpleName = this.getClass().getSimpleName(); String substring = simpleName.substring(0, simpleName.indexOf("_")); // System.out.println(substring); if (annotation == null) { annotation = (TargetTestData) Proxy.newProxyInstance(BaseTester.class.getClassLoader(), new Class[]{TargetTestData.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("sourceType".equals(method.getName())) return "excel";//默認去excel讀取數據 if ("sourcePath".equals(method.getName())) return "/testData/" + substring + ".xlsx";//若是沒有寫讀取地址,則默認到類名前綴的文件中去找 if ("sourceSheetOrSql".equals(method.getName())) return "Sheet1"; if ("mysqlPrefix".equals(method.getName())) return ".test"; return null; } }); } String sourceType = annotation.sourceType(); if ("excel".equals(sourceType)) { return new ExcelDataproviderUtil(annotation.sourcePath(), annotation.sourceSheetOrSql()); } if ("mysql".equals(sourceType)) { return new MysqlDataproviderUtil(annotation.sourceSheetOrSql(), annotation.mysqlPrefix()); } return null; } /*-----------------------------------------智能等待定位元素--------------------------------------------------------*/ /** * 智能定位元素 * * @param path 要讀取的配置文件地址 * @param pageName 頁面名稱 * @param desc 元素描述 * @param timeOut 超時時間,單位爲S * @return 返回定位到的元素 */ public WebElement intelligentWaitgetWebElement(String path, String pageName, String desc, int timeOut) { //System.out.println("path----->"+path+"pagename----->"+pageName+timeOut); ElementInfo locator = ElementLocatorUtil.getLocator(path, pageName, desc);//獲取到定位器 //定位器內容 String byStr = locator.getBy(); String value = locator.getValue(); //經過反射,獲取到By類的字節碼文件(By類的引用) Class<By> byClass = By.class; Method declaredMethod = null;//根據方法名獲取到方法By.id(),方法名就是id try { declaredMethod = byClass.getDeclaredMethod(byStr, String.class); By by = (By) declaredMethod.invoke(null, value);//調用null對象(即By類的靜態方法)declaredMethod,參數爲value WebDriverWait wait = new WebDriverWait(driver, timeOut);//建立等待對對象 WebElement until = wait.until(new ExpectedCondition<WebElement>() {//等待--直到內部類返回一個element public WebElement apply(WebDriver input) { WebElement element = input.findElement(by); return element; } }); return until; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 當沒有傳入path,pageName時,經過註解去獲取path和pageName, * 而後再去調用重載方法IntelligentWaitgetWebElement(String path, String pageName, String desc, int timeOut) * 智能等待時間默認爲3秒 * * @param desc * @return */ public WebElement intelligentWaitgetWebElement(String desc) { GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke(); String path = getPathAndPagename.getPath(); String pageName = getPathAndPagename.getPageName(); return intelligentWaitgetWebElement(path, pageName, desc, 3); } /** * 返回元素列表 * * @param path 要讀取的配置文件地址 * @param pageName 頁面名稱 * @param desc 元素描述 * @param timeOut 超時時間,單位爲S * @return 返回定位到的元素列表 */ public List<WebElement> intelligentWaitgetWebElements(String path, String pageName, String desc, int timeOut) { //System.out.println("path----->"+path+"pagename----->"+pageName+timeOut); ElementInfo locator = ElementLocatorUtil.getLocator(path, pageName, desc);//獲取到定位器 //定位器內容 String byStr = locator.getBy(); String value = locator.getValue(); //經過反射,獲取到By類的字節碼文件(By類的引用) Class<By> byClass = By.class; Method declaredMethod = null;//根據方法名獲取到方法By.id(),方法名就是id try { declaredMethod = byClass.getDeclaredMethod(byStr, String.class); By by = (By) declaredMethod.invoke(null, value);//調用null對象(即By類的靜態方法)declaredMethod,參數爲value WebDriverWait wait = new WebDriverWait(driver, timeOut);//建立等待對對象 List<WebElement> until = wait.until(new ExpectedCondition<List<WebElement>>() {//等待--直到內部類返回一個element public List<WebElement> apply(WebDriver input) { List<WebElement> elements = input.findElements(by); return elements; } }); return until; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 返回元素列表 * 當沒有傳入path,pageName時,經過註解去獲取path和pageName, * 而後再去調用重載方法IntelligentWaitgetWebElements(String path, String pageName, String desc, int timeOut) * 智能等待時間默認爲3秒 * * @param desc * @return */ public List<WebElement> intelligentWaitgetWebElements(String desc) { GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke(); String path = getPathAndPagename.getPath(); String pageName = getPathAndPagename.getPageName(); return intelligentWaitgetWebElements(path, pageName, desc, 3); } /** * 內部類,用於獲取到當前運行方法上的TargetPage註解 */ private class GetPathAndPagename { private String path; private String pageName; public String getPath() { return path; } public String getPageName() { return pageName; } public GetPathAndPagename invoke() { ITestNGMethod method = Reporter.getCurrentTestResult().getMethod(); Method method1 = method.getMethod(); String name = method.getMethodName(); // System.out.println("當前運行方法爲" +method1.getName()); //this.getClass().getDeclaredMethod(name,) TargetPage annotation = method1.getAnnotation(TargetPage.class); if (annotation == null) { annotation = (TargetPage) Proxy.newProxyInstance(BaseTester.class.getClassLoader(), new Class[]{TargetPage.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("path".equals(method.getName())) return "/frontPage/frontPage.xml"; if ("pageName".equals(method.getName())) return name; return null; } }); } path = annotation.path(); pageName = annotation.pageName(); return this; } } /*------------------------------------普一般用方法封裝-------------------------------------------*/ /** * 打開瀏覽器頁面 */ public void go() { GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke(); String path = getPathAndPagename.getPath(); String pageName = getPathAndPagename.getPageName(); String url = ElementLocatorUtil.getUrl(path, pageName); driver.get(url); } /** * 打開瀏覽器指定頁面 * * @param path 讀取的配置文件地址 * @param pageName 頁面名稱 */ public void go(String path, String pageName) { String url = ElementLocatorUtil.getUrl(path, pageName); driver.get(url); } /** * 打開瀏覽器指定頁面 * * @param pageName 頁面名稱 */ public void go(String pageName) { GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke(); String path = getPathAndPagename.getPath(); String url = ElementLocatorUtil.getUrl(path, pageName); driver.get(url); } /** * 打開指定url * @param url */ public void goByUrl(String url){ driver.get(url); } /** * 向指定元素輸入內容 * * @param desc 元素關鍵字 * @param inputKeys 輸入的內容 */ public void inputKeys(String desc, String inputKeys) { intelligentWaitgetWebElement(desc).sendKeys(inputKeys); } /** * 元素點擊 * * @param desc 元素關鍵字 */ public void click(String desc) { intelligentWaitgetWebElement(desc).click(); } /** * 點擊指定元素 * @param element */ public void click(WebElement element){ element.click(); } public void intelligentClick(WebElement element,int timeOut){ WebDriverWait wait = new WebDriverWait(driver,timeOut); wait.until(ExpectedConditions.elementToBeClickable(element)).click(); } /** * 獲取到元素文本 * * @param desc * @return */ public String getText(String desc) { return intelligentWaitgetWebElement(desc).getText(); } /** * 獲取元素文本 * @param path * @param pagename * @param desc * @return */ public String getText(String path, String pagename, String desc) { return intelligentWaitgetWebElement(path, pagename, desc, 3).getText(); } /** * 獲取到指定元素的指定屬性值 * @param element * 元素 * @param attrName * 屬性名 * @return * 屬性值 */ public String getAttrVal(WebElement element,String attrName){ return element.getAttribute(attrName); } /** * 得到當前窗口title * @return */ public String getTitle(){ return driver.getTitle(); } /** * 得到當前窗口url * @return */ public String getCurrentUrl(){ return driver.getCurrentUrl(); } /** * 返回以前頁面 */ public void back(){ driver.navigate().back(); } /** * 得到當前全部窗口句柄 * @return */ public Set<String> getWindowHandles(){ return driver.getWindowHandles(); } /** * 得到當前窗口句柄 * @return */ public String getWindowHandle(){ return driver.getWindowHandle(); } /** * 進入指定句柄的窗口 * @param windowhandle */ public void goInNewWindow(String windowhandle){ driver.switchTo().window(windowhandle); } public void selectBytext(String desc,String text){ if (text != ""){ WebElement webElement = intelligentWaitgetWebElement(desc); Select select =new Select(webElement); select.selectByVisibleText(text); } } /*----------------------------------------斷言--------------------------------------------------*/ /** * 斷言預期字符串與實際字符串相等 * * @param expected * @param actural */ public void assertTextEqual(String expected, String actural) { Assert.assertEquals(expected, actural); } /** * 斷言實際文本包含預期文本 * * @param expected * @param actural */ public void assertTextContain(String expected, String actural) { try { Assert.assertTrue(actural.contains(expected)); } catch (Exception e) { e.printStackTrace(); logger.error("斷言失敗!"); } } /** * 斷言非空 * * @param actural */ public void assertNotNull(String actural) { Assert.assertNotNull(actural); } public void assertTrue(Boolean flag){ Assert.assertTrue(flag); } }
4.用於封裝頁面信息的2個類
package com.claire.jing.elementLocator; /** * 該類模擬頁面元素定位器,任何元素經過By.by(value)的方式均可以得到,如By.id() */ public class ElementInfo { private String by; private String value; private String desc; public ElementInfo() { } public ElementInfo(String by, String value, String desc) { this.by = by; this.value = value; this.desc = desc; } public String getBy() { return by; } public void setBy(String by) { this.by = by; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "ElementInfo{" + "by='" + by + '\'' + ", value='" + value + '\'' + ", desc='" + desc + '\'' + '}'; } }
package com.claire.jing.elementLocator; import java.util.Arrays; import java.util.Map; public class PageInfo { private String pageName; private String pageUrl; private Map<String,ElementInfo> elementInfoMap; public PageInfo() { } public PageInfo(String pageName, String pageUrl, Map<String, ElementInfo> elementInfoMap) { this.pageName = pageName; this.pageUrl = pageUrl; this.elementInfoMap = elementInfoMap; } public String getPageName() { return pageName; } public void setPageName(String pageName) { this.pageName = pageName; } public String getPageUrl() { return pageUrl; } public void setPageUrl(String pageUrl) { this.pageUrl = pageUrl; } public Map<String, ElementInfo> getElementInfoMap() { return elementInfoMap; } public void setElementInfoMap(Map<String, ElementInfo> elementInfoMap) { this.elementInfoMap = elementInfoMap; } @Override public String toString() { return "PageInfo{" + "pageName='" + pageName + '\'' + ", pageUrl='" + pageUrl + '\'' + ", elementInfoMap=" + elementInfoMap + '}'; } }
5.頁面信息配置文件以及文件解析的util(實現關鍵字驅動---將頁面元素定位信息以及頁面Url寫到配置文件中,並讀取映射到指定pojo上。對位提供獲取url和獲取定位器的方法)
<?xml version="1.0" encoding="UTF-8"?> <!--登陸頁--> <pages> <page pageName="Login" url="http://118.184.217.39/Organizer/Login"> <locator by="id" value="UserInfo" desc="輸入用戶名"/> <locator by="id" value="Password" desc="輸入密碼"/> <locator by="id" value="button" desc="登陸按鈕"/> <locator by="cssSelector" value="span[data-valmsg-for='UserInfo']" desc="用戶名錯誤提示"/> <locator by="cssSelector" value="span[data-valmsg-for='Password']" desc="密碼錯誤提示"/> </page> <!--首頁--> <page pageName="HomePage" url="http://118.184.217.39/"> <locator by="cssSelector" value="a[href='/Organizer/Index']" desc="首頁歡迎您文本"/> <locator by="xpath" value="//div[@id='banner_list']/a" desc="首頁banners"/> <locator by="xpath" value="//td[@height='28']/a" desc="首頁全部競賽連接"/> </page> <!--競賽搜索頁--> <page pageName="CompetitionSearch" url="http://118.184.217.39/Competition/Index"> <locator by="id" value="ProvinceCode" desc="選擇省"/> <locator by="id" value="CityCode" desc="選擇市"/> <locator by="id" value="AreaCode" desc="選擇區"/> <locator by="id" value="search_text" desc="競賽搜索輸入框"/> <locator by="id" value="search_btn" desc="競賽搜索按鈕"/> <locator by="id" value="loadMoreBtn" desc="點擊加載更多"/> <locator by="cssSelector" value="div[class='fl search_comp_list']" desc="頁面全部競賽"/> <locator by="id" value="search_btn" desc="競賽搜索按鈕"/> </page> <!--報名頁--> <page pageName="ApplyCompetition" url="http://118.184.217.39/Project/Index/"> <locator by="cssSelector" value="[href='/Organizer/Login']" desc="登陸按鈕"/> <locator by="xpath" value="//*[@id='EnrollType']/label/input" desc="角色類型"/> <locator by="cssSelector" value="input[name='GroupType']" desc="選擇組別"/> <locator by="id" value="teamName" desc="團隊全稱"/> <locator by="id" value="leaderName" desc="領隊姓名"/> <locator by="id" value="leaderPhone" desc="領隊電話"/> <locator by="id" value="coachName" desc="教練姓名"/> <locator by="id" value="coachPhone" desc="教練電話"/> <locator by="id" value="coachName2" desc="教練姓名2"/> <locator by="id" value="coachPhone2" desc="教練電話2"/> <locator by="id" value="coachName3" desc="教練姓名3"/> <locator by="id" value="coachPhone3" desc="教練電話3"/> <locator by="id" value="coachName4" desc="教練姓名4"/> <locator by="id" value="coachPhone4" desc="教練電話4"/> <locator by="id" value="parentName" desc="家長姓名"/> <locator by="id" value="parentPhone" desc="家長電話"/> <locator by="id" value="email" desc="電子郵箱"/> <locator by="id" value="companyName" desc="單位名稱"/> <locator by="id" value="taxPayerNumber" desc="稅號"/> <locator by="id" value="row_name" desc="參賽人姓名"/> <locator by="id" value="row_sex" desc="參賽人性別"/> <locator by="id" value="row_idType" desc="參賽人證件類型"/> <locator by="id" value="row_idno" desc="參賽人證件號碼"/> <locator by="id" value="row_birthday" desc="參賽人生日"/> <locator by="id" value="row_add" desc="添加參賽人按鈕"/> <locator by="id" value="submit" desc="肯定報名按鈕"/> <locator by="id" value="search_btn" desc="競賽搜索按鈕"/> </page> </pages>
package com.claire.jing.utils; import com.claire.jing.elementLocator.ElementInfo; import com.claire.jing.elementLocator.PageInfo; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.util.HashMap; import java.util.List; import java.util.Map; public class ElementLocatorUtil { /** * example * @param args */ public static void main(String[] args) { String pageUrl = ElementLocatorUtil.getUrl("/frontPage/frontPage.xml", "Login"); System.out.println(pageUrl); ElementInfo locator = ElementLocatorUtil.getLocator("/frontPage/frontPage.xml", "Login", "輸入用戶名"); System.out.println(locator); } private static String path; private static Map<String, Map<String, PageInfo>> sureInitMap = new HashMap<String, Map<String, PageInfo>>(); /** * 構造函數,爲了能將path傳遞進來 * @param path */ public ElementLocatorUtil(String path) { this.path = path; } /** * 確保同一份文件只被解析一遍 */ private static void sureInit() { if (sureInitMap.get(path) == null) { readXml(); } } /** * 解析xml文件 * 思想是:將xml解析到JavaBean中 * Map<String, PageInfo> -------根據pageName 獲取到對應的 pageInfo對象 * Map<String, ElementInfo>------根據元素描述 獲取到對應的 ElementInfo對象(即:獲取到頁面全部元素的定位信息) */ private static void readXml() { SAXReader reader = new SAXReader(); Document document = null; try { document = reader.read(ElementLocatorUtil.class.getResourceAsStream(path)); } catch (DocumentException e) { e.printStackTrace(); } Element rootElement = document.getRootElement();//獲取到根節點pages List<Element> allPages = rootElement.elements("page");//獲取到全部的page子節點 Map<String, PageInfo> pageInfoMap = new HashMap<String, PageInfo>();//爲了能夠經過pageName獲取到頁面全部信息,建立該map PageInfo pageInfo = null;//建立一個PageInfo對象 for (Element page : allPages) { String pageName = page.attributeValue("pageName");//獲取到pageName String url = page.attributeValue("url");//獲取到url List<Element> allLocator = page.elements("locator");//獲取到page節點下的全部locator子節點 Map<String, ElementInfo> elementInfoMap = new HashMap<String, ElementInfo>();//爲了能夠經過元素的描述--獲取到對應元素,建立該map //List<ElementInfo> elementInfo = null;//建立一個ElementInfo對象 for (Element locator : allLocator) { String by = locator.attributeValue("by");//獲取到locator的by屬性值 String value = locator.attributeValue("value");//獲取到locator的value屬性值 String desc = locator.attributeValue("desc");//獲取到locator的desc屬性值 elementInfoMap.put(desc, new ElementInfo(by, value, desc));//經過元素描述,能夠直接得到ElementInfo } pageInfo = new PageInfo(pageName, url, elementInfoMap); pageInfoMap.put(pageName, pageInfo); } sureInitMap.put(path, pageInfoMap); } /** * 獲取到測試頁面的url * @param path * 要讀取的配置文件路徑(如:在resources下的路徑爲/frontPage(配置文件上一層包名)/frontPage.xml(配置文件名) * @param pageName * 頁面名稱 * @return * 返回頁面url */ public static String getUrl(String path, String pageName) { new ElementLocatorUtil(path); sureInit(); Map<String, PageInfo> map = sureInitMap.get(path); return map.get(pageName).getPageUrl(); } /** * 獲取到測試頁面指定元素的定位器(定位器即JavaBean,元素全部定位信息封裝在JavaBean中) * @param path * 要讀取的配置文件路徑(如:在resources下的路徑爲/frontPage(配置文件上一層包名)/frontPage.xml(配置文件名) * @param pageName * 頁面名稱 * @param desc * 元素描述 * @return * 返回定位器 */ public static ElementInfo getLocator(String path, String pageName, String desc) { new ElementLocatorUtil(path); sureInit(); Map<String, PageInfo> map = sureInitMap.get(path); PageInfo pageInfo = map.get(pageName); ElementInfo elementInfo = pageInfo.getElementInfoMap().get(desc); return elementInfo; } }
6.數據提供者(關於數據驅動--將測試數據與代碼徹底分離)
讀取Excel中的測試數據:
package com.claire.jing.utils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.ss.usermodel.*; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class ExcelDataproviderUtil implements Iterator<Object[]> { public static void main(String[] args) { ExcelDataproviderUtil excelDataproviderUtil = new ExcelDataproviderUtil("/testData/login.xlsx", "Sheet1"); while (excelDataproviderUtil.hasNext()){ Object[] next = excelDataproviderUtil.next(); System.out.println(Arrays.deepToString(next)); } } private String path; private String sheetName; String[] clomnName; Iterator<Row> rowIterator; Map<String, Workbook> workbookMap = new HashMap<>(); public ExcelDataproviderUtil(String path, String sheetName) { this.path = path; this.sheetName = sheetName; } private void sureInit() { if (workbookMap.get(path) == null) initReadExcel(); } public void initReadExcel() { InputStream inp = ExcelDataproviderUtil.class.getResourceAsStream(path); Workbook workbook = null; try { workbook = WorkbookFactory.create(inp); } catch (IOException e) { e.printStackTrace(); } catch (InvalidFormatException e) { e.printStackTrace(); } workbookMap.put(path, workbook); Sheet sheet = workbook.getSheet(this.sheetName); Row firstRow = sheet.getRow(0); short lastCellNum = firstRow.getLastCellNum(); clomnName = new String[lastCellNum]; //初始化列名數組 for (int i = 0; i < clomnName.length; i++) { clomnName[i] = firstRow.getCell(i).getStringCellValue(); } rowIterator = sheet.iterator(); rowIterator.next();//直接將行迭代器移動到數據行上面 } boolean flag = false;//默認假設沒有下一個 @Override public boolean hasNext() { sureInit(); if (flag) return true; //判斷,當有下一個的時候,將flag設置爲true if (rowIterator.hasNext()) { flag = true; return true; } else { return false; } } @Override public Object[] next() { sureInit(); flag = false; //讀取當前行數據 Row currentRow = rowIterator.next(); Map<String,String> currentRowData = new HashMap<>(); for (int i = 0; i < clomnName.length; i++) { Cell cell = currentRow.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); cell.setCellType(CellType.STRING); currentRowData.put(clomnName[i],cell.getStringCellValue()); } return new Object[]{currentRowData}; } @Override public void remove() { throw new UnsupportedOperationException("不支持刪除操做!"); } }
jdbc幫助類:
package com.claire.jing.utils.jdbc; import com.claire.jing.utils.PropertiesUtil; import java.sql.*; public class JdbcUtil { private static final String JDBC_DRIVER = "jdbc.driver"; private static final String JDBC_URL="jdbc.url"; private static final String JDBC_USERNAME="jdbc.username"; private static final String JDBC_PWD="jdbc.password"; private static final String PATH = "/mysql.properties"; //保證driver只加載一次 static { try { Class.forName(PropertiesUtil.getProVal(PATH,JDBC_DRIVER)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 獲取數據庫連接 * @return */ public static Connection getConn(String prefix){ String url = PropertiesUtil.getProVal(PATH, prefix+JDBC_URL); String username = PropertiesUtil.getProVal(PATH, prefix+JDBC_USERNAME); String pwd = PropertiesUtil.getProVal(PATH, prefix+JDBC_PWD); try { Connection connection = DriverManager.getConnection(url, username, pwd); return connection; } catch (SQLException e) { e.printStackTrace(); } return null; } public static void close(Connection conn, ResultSet resultSet, PreparedStatement statement){ if (conn !=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } if (resultSet!=null) try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } if (statement!=null) try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } }
數據庫讀取測試數據:
package com.claire.jing.utils; import com.claire.jing.utils.jdbc.JdbcUtil; import java.sql.*; import java.util.*; public class MysqlDataproviderUtil implements Iterator<Object[]> { public static void main(String[] args) { MysqlDataproviderUtil mysqlDataproviderUtil = new MysqlDataproviderUtil("SELECT * FROM `Organizers`", "test."); for (int i = 0; i < 3; i++) { if (mysqlDataproviderUtil.hasNext()){ Object[] next = mysqlDataproviderUtil.next(); System.out.println(Arrays.toString(next));} } } private String sql; private String prefix; Connection conn; PreparedStatement statement; private ResultSet resultSet; String[] columnName; Map<String, ResultSet> sureInitMap = new HashMap<>(); /** * 構造函數 * @param sql * @param prefix */ public MysqlDataproviderUtil(String sql,String prefix) { this.sql = sql; this.prefix = prefix; } /** * 確保一個sql不去執行多遍 */ private void sureInit() { if (sureInitMap.get(sql) == null) doSelect(); } /** * 執行查詢 */ private void doSelect() { //System.out.println("個人執行次數"); conn = JdbcUtil.getConn(prefix); try { statement = conn.prepareStatement(sql); resultSet = statement.executeQuery(); sureInitMap.put(sql, resultSet); } catch (SQLException e) { e.printStackTrace(); } ResultSetMetaData metaData = null; int columnCount = 0; try { metaData = resultSet.getMetaData(); columnCount = metaData.getColumnCount(); } catch (SQLException e) { e.printStackTrace(); } columnName = new String[columnCount]; for (int i = 0; i < columnName.length; i++) { try { columnName[i] = metaData.getColumnName(i + 1); } catch (SQLException e) { e.printStackTrace(); } } // System.out.println("執行結束,得到了resultSet"); } Boolean flag = false;//消除next帶來的反作用 @Override public boolean hasNext() { sureInit(); if (flag) { return true; } boolean temp = false; try { temp = resultSet.next(); } catch (SQLException e) { e.printStackTrace(); } if (temp) { flag = true; return true; } else { close();//當沒有下一行數據時,自動關閉數據庫各類鏈接。若是在next=true時,就再也不取數據了,建議手動調用close方法,去關閉數據庫鏈接。 return false; } } @Override public Object[] next() { sureInit(); flag = false; Map<String, String> testData = new HashMap<>(); for (int i = 0; i < columnName.length; i++) {//遍歷,並獲取到當前行的數據 try { String setString = resultSet.getString(i + 1); testData.put(columnName[i], setString); } catch (SQLException e) { e.printStackTrace(); } } return new Object[]{testData}; } @Override public void remove() { throw new UnsupportedOperationException("不支持刪除操做"); } /** * 對外提供一個關閉數據庫的方法,能夠手動關閉。 */ public void close(){ JdbcUtil.close(conn,resultSet,statement); } }
7.爲了實現失敗截圖的監聽器,在上一篇文章中已經介紹,這裏再也不贅述。
package com.claire.jing.MyListener; import com.claire.jing.base.BaseTester; import com.claire.jing.utils.FailTestScreenShotUtil; import io.qameta.allure.Attachment; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.testng.ITestResult; import org.testng.TestListenerAdapter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; public class TestFailListener extends TestListenerAdapter { @Override public void onTestFailure(ITestResult result) { takePhoto(); } @Attachment(value = "screen shot",type = "image/png") public byte[] takePhoto(){ byte[] screenshotAs = ((TakesScreenshot)BaseTester.driver).getScreenshotAs(OutputType.BYTES); return screenshotAs; } }
8.自定義註解,方便在測試用例執行時,指定測試數據以及要測試頁面
package com.claire.jing.annocations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /**
*要測試頁面的配置文件路徑以及頁面名稱
*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TargetPage { public String path() default ""; public String pageName() default "";
}
package com.claire.jing.annocations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
/**
*sourceType指定測試數據源的類型,當前支持Excel和mysql數據庫
*sourcePath指定測試數據的配置文件路徑
*sourceSheetOrSql 當測試數據從Excel讀取時,指定sheet名。若是爲mysql讀取時,指定sql語句
*mysqlPrefix 當測試數據從mysql讀取時,指定數據庫前綴
*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface TargetTestData { public String sourceType() default "excel"; public String sourcePath() default ""; public String sourceSheetOrSql() default ""; public String mysqlPrefix() default "test."; }
9.測試腳本(只粘貼一個競賽搜索頁面的測試)
package com.claire.jing.testCases.competitionsList; import com.claire.jing.MyListener.TestFailListener; import com.claire.jing.annocations.TargetPage; import com.claire.jing.annocations.TargetTestData; import com.claire.jing.base.BaseTester; import io.qameta.allure.Step; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.Select; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.List; import java.util.Map; @Listeners(TestFailListener.class) public class CompetitionsList_testCases extends BaseTester { private static final String PAGE_PATH = "/frontPage/frontPage.xml"; private static final String PAGE_NAME = "CompetitionSearch"; private static final String SOURCE_PATH = "/testData/competitionSearch.xlsx"; private static final String SOURCE_SHEET="competitionSearch"; @Test(dataProvider = "testData",description = "競賽搜索測試") @Step("輸入搜索關鍵字:{0},對應搜索條數預期結果爲:{1}") @TargetTestData(sourcePath = SOURCE_PATH,sourceSheetOrSql = SOURCE_SHEET) @TargetPage(path = PAGE_PATH,pageName = PAGE_NAME) public void competitionSearchTest(Map<String,String> testData) throws Exception{ go(PAGE_NAME);//打開競賽搜索頁面 Thread.sleep(2000); selectBytext("選擇省",testData.get("選擇省"));//選擇省 selectBytext("選擇市",testData.get("選擇市"));//選擇市 selectBytext("選擇區",testData.get("選擇區"));//選擇區 inputKeys("競賽搜索輸入框",testData.get("搜索關鍵字"));//輸入搜索關鍵字 click("競賽搜索按鈕"); int expectCounts =Integer.valueOf(testData.get("預期搜索到的競賽個數")); logger.info("預期競賽個數爲"+expectCounts); List<WebElement> competitionsList = intelligentWaitgetWebElements("頁面全部競賽"); int index =10; while (competitionsList.size() - index ==0){ click(intelligentWaitgetWebElement("點擊加載更多"));//點擊加載更多 Thread.sleep(1000); competitionsList = intelligentWaitgetWebElements("頁面全部競賽"); index += 10; } logger.info("實際搜索到的競賽有"+competitionsList.size()+"個"); assertTrue(expectCounts==competitionsList.size()); } }
下面是對應的測試數據:
後續Jenkins的配置已經在以前博客中整理過,有興趣能夠學習下哦。