最近在公司搭建了一套基於maven+selenium+java+testng+jenkins的自動化測試框架,免得以後重寫記錄下
工程目錄
pom.xml
<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>Realinsight4.0</groupId> <artifactId>Realinsight4.0</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Realinsight4.0</name> <dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.141.59</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-server</artifactId> <version>3.141.59</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.14.3</version> </dependency> <dependency> <groupId>org.uncommons</groupId> <artifactId>reportng</artifactId> <version>1.1.4</version> <exclusions> <exclusion> <groupId>org.testng</groupId> <artifactId>testng</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>3.0</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>com.aventstack</groupId> <artifactId>extentreports</artifactId> <version>3.1.2</version> </dependency> <dependency> <groupId>com.relevantcodes</groupId> <artifactId>extentreports</artifactId> <version>2.40.2</version> </dependency> <dependency> <groupId>com.vimalselvam</groupId> <artifactId>testng-extentsreport</artifactId> <version>1.3.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.17</version> <configuration> <suiteXmlFiles> <suiteXmlFile>testng.xml</suiteXmlFile> </suiteXmlFiles> </configuration> </plugin> <!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <properties> <property> <name>usedefaultlisteners</name> <value>false</value> </property> <property> <name>listener</name> <value>org.uncommons.reportng.HTMLReporter,org.uncommons.reportng.JUnitXMLReporter</value> </property> </properties> <workingDirectory></workingDirectory> </configuration> </plugin> --> </plugins> </build> </project>
testng.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Realinsight4.0"> <listeners> <listener class-name="utils.RetryListener" /> <listener class-name="utils.TestngListener" /> <listener class-name="utils.ExtentReporterListener" /> </listeners> <test name="登陸測試" verbose="10" preserve-order="true"> <classes> <class name="testcase.login.login"/> <class name="testcase.login.loginWithParas"/> <class name="testcase.login.createAccount"/> <class name="testcase.login.lockAccount"/> <class name="testcase.login.resetPwd"/> <class name="testcase.login.logout"/> </classes> </test> <test name="數據集測試" verbose="10" preserve-order="true"> <classes> <class name="testcase.boardsheet.UploadData"/> <class name="testcase.boardsheet.AggRegate"/> <class name="testcase.boardsheet.AppendData"/> <class name="testcase.boardsheet.CreatBySQL"/> <class name="testcase.boardsheet.AppendMerge"/> <class name="testcase.boardsheet.DimensionalityReduction"/> <class name="testcase.boardsheet.ReplaceData"/> </classes> </test> <test name="儀表板測試" verbose="10" preserve-order="true"> <classes> <class name="testcase.dashboard.CreateDashBoard"/> <class name="testcase.dashboard.CreateChart"/> <class name="testcase.dashboard.ChartOperation"/> <class name="testcase.dashboard.TextOperation"/> </classes> </test> <test name="刪除數據" verbose="10" preserve-order="true"> <classes> <class name="testcase.boardsheet.Deleteboardsheet"/> </classes> </test> </suite> <!-- Suite -->
log4j.properties
log4j.rootCategory=INFO, stdout , R log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender\u00A0 log4j.appender.Threshold = DEBUG\u00A0 log4j.appender.CONSOLE.Target = System.out\u00A0 log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout\u00A0 log4j.appender.CONSOLE.layout.ConversionPattern = [framework] % d - % c -%- 4r [ % t] %- 5p % c % x - % m % n\u00A0 --------------------- \u4F5C\u8005\uFF1AJXNUleo \u6765\u6E90\uFF1ACSDN \u539F\u6587\uFF1Ahttps://blog.csdn.net/m0_37874657/article/details/80536086 \u7248\u6743\u58F0\u660E\uFF1A\u672C\u6587\u4E3A\u535A\u4E3B\u539F\u521B\u6587\u7AE0\uFF0C\u8F6C\u8F7D\u8BF7\u9644\u4E0A\u535A\u6587\u94FE\u63A5\uFF01 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[YG] %p [%t] %C.%M(%L) | %m%n log4j.appender.R=org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File=${project}/WEB-INF/logs/app.log log4j.appender.R.layout=org.apache.log4j.PatternLayout 1log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n log4j.logger.com.neusoft=DEBUG log4j.logger.com.opensymphony.oscache=ERROR log4j.logger.net.sf.navigator=ERROR log4j.logger.org.apache.commons=ERROR log4j.logger.org.apache.struts=WARN log4j.logger.org.displaytag=ERROR log4j.logger.org.springframework=DEBUG log4j.logger.com.ibatis.db=WARN log4j.logger.org.apache.velocity=FATAL log4j.logger.com.canoo.webtest=WARN log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN log4j.logger.org.hibernate=DEBUG log4j.logger.org.logicalcobwebs=WARN log4j.rootCategory=INFO, stdout , R log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n log4j.appender.R=org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File=D:\\logs\\YG.log log4j.appender.R.layout=org.apache.log4j.PatternLayout 1log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n log4j.logger.com.neusoft=DEBUG log4j.logger.com.opensymphony.oscache=ERROR log4j.logger.net.sf.navigator=ERROR log4j.logger.org.apache.commons=ERROR log4j.logger.org.apache.struts=WARN log4j.logger.org.displaytag=ERROR log4j.logger.org.springframework=DEBUG log4j.logger.com.ibatis.db=WARN log4j.logger.org.apache.velocity=FATAL log4j.logger.com.canoo.webtest=WARN log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN log4j.logger.org.hibernate=DEBUG log4j.logger.org.logicalcobwebs=WARN
ExtentReporterListener.java
package utils; import com.aventstack.extentreports.ExtentReports; import com.aventstack.extentreports.ExtentTest; import com.aventstack.extentreports.ResourceCDN; import com.aventstack.extentreports.Status; import com.aventstack.extentreports.model.TestAttribute; import com.aventstack.extentreports.reporter.ExtentHtmlReporter; import com.aventstack.extentreports.reporter.configuration.ChartLocation; import com.aventstack.extentreports.reporter.configuration.Theme; import org.testng.*; import org.testng.xml.XmlSuite; import java.io.File; import java.util.*; public class ExtentReporterListener implements IReporter { //生成的路徑以及文件名 private static final String OUTPUT_FOLDER = "test-output/"; private static final String FILE_NAME = "index.html"; private ExtentReports extent; @Override public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) { init(); boolean createSuiteNode = false; if(suites.size()>1){ createSuiteNode=true; } for (ISuite suite : suites) { Map<String, ISuiteResult> result = suite.getResults(); //如果suite裏面沒有任何用例,直接跳過,不在報告裏生成 if(result.size()==0){ continue; } //統計suite下的成功、失敗、跳過的總用例數 int suiteFailSize=0; int suitePassSize=0; int suiteSkipSize=0; ExtentTest suiteTest=null; //存在多個suite的情況下,在報告中將同一個一個suite的測試結果歸爲一類,創建一級節點。 if(createSuiteNode){ suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName()); } boolean createSuiteResultNode = false; if(result.size()>1){ createSuiteResultNode=true; } for (ISuiteResult r : result.values()) { ExtentTest resultNode; ITestContext context = r.getTestContext(); if(createSuiteResultNode){ //沒有創建suite的情況下,將在SuiteResult的創建爲一級節點,否則創建爲suite的一個子節點。 if( null == suiteTest){ resultNode = extent.createTest(r.getTestContext().getName()); }else{ resultNode = suiteTest.createNode(r.getTestContext().getName()); } }else{ resultNode = suiteTest; } if(resultNode != null){ resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName()); if(resultNode.getModel().hasCategory()){ resultNode.assignCategory(r.getTestContext().getName()); }else{ resultNode.assignCategory(suite.getName(),r.getTestContext().getName()); } resultNode.getModel().setStartTime(r.getTestContext().getStartDate()); resultNode.getModel().setEndTime(r.getTestContext().getEndDate()); //統計SuiteResult下的數據 int passSize = r.getTestContext().getPassedTests().size(); int failSize = r.getTestContext().getFailedTests().size(); int skipSize = r.getTestContext().getSkippedTests().size(); suitePassSize += passSize; suiteFailSize += failSize; suiteSkipSize += skipSize; if(failSize>0){ resultNode.getModel().setStatus(Status.FAIL); } resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize)); } buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL); buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP); buildTestNodes(resultNode,context.getPassedTests(), Status.PASS); } if(suiteTest!= null){ suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize)); if(suiteFailSize>0){ suiteTest.getModel().setStatus(Status.FAIL); } } } // for (String s : Reporter.getOutput()) { // extent.setTestRunnerOutput(s); // } extent.flush(); } private void init() { //文件夾不存在的話進行創建 File reportDir= new File(OUTPUT_FOLDER); if(!reportDir.exists()&& !reportDir .isDirectory()){ reportDir.mkdir(); } ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME); // 設置靜態文件的DNS //怎麼樣解決cdn.rawgit.com訪問不了的情況 htmlReporter.config().setEncoding("gbk"); htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS); htmlReporter.config().setDocumentTitle("自動化測試報告"); htmlReporter.config().setReportName("自動化測試報告"); htmlReporter.config().setChartVisibilityOnOpen(true); htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP); htmlReporter.config().setTheme(Theme.STANDARD); htmlReporter.config().setCSS(".node.level-1 ul{ display:none;} .node.level-1.active ul{display:block;}"); extent = new ExtentReports(); extent.attachReporter(htmlReporter); extent.setReportUsesManualConfiguration(true); } private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) { //存在父節點時,獲取父節點的標籤 String[] categories=new String[0]; if(extenttest != null ){ List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll(); categories = new String[categoryList.size()]; for(int index=0;index<categoryList.size();index++){ categories[index] = categoryList.get(index).getName(); } } ExtentTest test; if (tests.size() > 0) { //調整用例排序,按時間排序 Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() { @Override public int compare(ITestResult o1, ITestResult o2) { return o1.getStartMillis()<o2.getStartMillis()?-1:1; } }); treeSet.addAll(tests.getAllResults()); for (ITestResult result : treeSet) { Object[] parameters = result.getParameters(); String name=""; //如果有參數,則使用參數的toString組合代替報告中的name for(Object param:parameters){ name+=param.toString(); } if(name.length()>0){ if(name.length()>50){ name= name.substring(0,49)+"..."; } }else{ name = result.getMethod().getMethodName(); } if(extenttest==null){ test = extent.createTest(name); }else{ //作爲子節點進行創建時,設置同父節點的標籤一致,便於報告檢索。 test = extenttest.createNode(name).assignCategory(categories); } //test.getModel().setDescription(description.toString()); //test = extent.createTest(result.getMethod().getMethodName()); for (String group : result.getMethod().getGroups()) test.assignCategory(group); List<String> outputList = Reporter.getOutput(result); for(String output:outputList){ //將用例的log輸出報告中 test.debug(output); } if (result.getThrowable() != null) { test.log(status, result.getThrowable()); } else { test.log(status, "Test " + status.toString().toLowerCase() + "ed"); } test.getModel().setStartTime(getTime(result.getStartMillis())); test.getModel().setEndTime(getTime(result.getEndMillis())); } } } private Date getTime(long millis) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(millis); return calendar.getTime(); } }
OverrideRetry.java
package utils; import org.testng.IRetryAnalyzer; import org.testng.ITestResult; public class OverrideRetry implements IRetryAnalyzer { private int count = 1; private int max_count = 3; @Override public boolean retry(ITestResult result) { System.out.println("執行用例:"+result.getName()+",第"+count+"次失敗"); if (count < max_count) { count++; return true; } return false; } }
ParasUtils.java
package utils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class ParasUtils { private String db_url; private String db_username; private String db_password; private String db_tableName; private String url; private String username; private String password; private String userData; private String downloadDir; private String uploadDir; private String chromePath; public void readParas(){ try { InputStream inStream = new FileInputStream(new File(System.getProperty("user.dir")+"\\datas\\Paras.properties")); Properties prop = new Properties(); prop.load(inStream); setDb_url(prop.getProperty("db_url")); setDb_username(prop.getProperty("db_username")); setDb_password(prop.getProperty("db_password")); setDb_tableName(prop.getProperty("db_tableName")); setUrl(prop.getProperty("url")); setUsername(prop.getProperty("username")); setPassword(prop.getProperty("password")); setUserData(prop.getProperty("user-data-dir")); setUploadDir(prop.getProperty("uploadDir")); setDownloadDir(prop.getProperty("downloadDir")); setChromePath(prop.getProperty("chromePath")); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String getUrl() { readParas(); return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { readParas(); return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { readParas(); return password; } public void setPassword(String password) { this.password = password; } public String getUserData() { readParas(); return userData; } public void setUserData(String userData) { this.userData = userData; } public String getDownloadDir() { readParas(); return downloadDir; } public void setDownloadDir(String downloadDir) { this.downloadDir = downloadDir; } public String getDb_username() { return db_username; } public void setDb_username(String db_username) { this.db_username = db_username; } public String getDb_url() { return db_url; } public void setDb_url(String db_url) { this.db_url = db_url; } public String getDb_password() { return db_password; } public void setDb_password(String db_password) { this.db_password = db_password; } public String getDb_tableName() { return db_tableName; } public void setDb_tableName(String db_tableName) { this.db_tableName = db_tableName; } public void setUp() { // TODO Auto-generated method stub } public String getUploadDir() { return uploadDir; } public void setUploadDir(String uploadDir) { this.uploadDir = uploadDir; } public String getChromePath() { return chromePath; } public void setChromePath(String chromePath) { this.chromePath = chromePath; } }
ReadCSV.java
package utils; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; publicView Codepackage utils; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class ReadCSV { public static Object [][] readCSV(String fileName) throws IOException { //讀取CSV文件的方法 List<Object[]> records = new ArrayList<Object[]>(); String record; BufferedReader file = new BufferedReader(new InputStreamReader(new FileInputStream(fileName),"UTF-8")); file.readLine(); while ((record=file.readLine())!=null){ String fields[] = record.split(","); records.add(fields); } file.close(); Object[][] results = new Object[records.size()][]; for (int i=0; i<records.size();i++){ results[i] = records.get(i); } return results; } }
RetryListener.java
package utils; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.testng.IAnnotationTransformer; import org.testng.IRetryAnalyzer; import org.testng.annotations.ITestAnnotation; public class RetryListener implements IAnnotationTransformer { public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { IRetryAnalyzer retry = annotation.getRetryAnalyzer(); if (retry == null) { annotation.setRetryAnalyzer(OverrideRetry.class); // 這個類名一定要和上方的對上 } } }
TestngListener.java
package utils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.testng.ITestContext; import org.testng.ITestNGMethod; import org.testng.ITestResult; import org.testng.TestListenerAdapter; import common.basic; public class TestngListener extends TestListenerAdapter { private static Logger logger = Logger.getLogger(TestngListener.class); @Override public void onTestFailure(ITestResult tr) { super.onTestFailure(tr); logger.info(tr.getName() + "Failure"); basic basic=(basic)tr.getInstance(); takeScreenshot(basic.driver); } @Override public void onTestSkipped(ITestResult tr) { super.onTestSkipped(tr); logger.info(tr.getName() + "Skipped"); basic basic=(basic)tr.getInstance(); takeScreenshot(basic.driver); } @Override public void onTestSuccess(ITestResult tr) { super.onTestSuccess(tr); logger.info(tr.getName() + "Success"); } @Override public void onTestStart(ITestResult tr) { super.onTestStart(tr);