首页 > 代码库 > 自动化测试-东航B2C网站测试框架搭建
自动化测试-东航B2C网站测试框架搭建
还是周末闲来无事做了一个我们公司电商产品-东航官网的自动化测试框架,发布到公司测试环境后获得好评。
现在把框架内容补齐做个阶段性的基线吧。
总体
1 现在框架运用到的技术: Selenium SpringMVC Hibernate Logback
2 主要接口的定位:以主流程的页面为接口,对其进行实现
3 异常处理机制:多种策略并存克服不稳定的测试环境
4 定时任务机制:采用Spring的提供的定时器进行cron配置
5 邮件系统:采用Spring和JavaX的邮件系统
6 测试数据持久化:入库测试数据库
7 易用性:提供友好的页面供点击
8 测试用例实现多线程:测试用例类均实现runnable接口,并调用线程池处理(暂时用处不大,只有CS模式时才有用)
主要的类
BaseSeleniumManager 继承 ExpectedCondition 实现 apply方法
1 启动浏览器
2 各种杂项操作
LaputaLogin,LaputaQueryFlight,LaputaSelectFlight....继承Login,QueryFlight,SelectFlight....
1 实现基本功能的类
LoginUtil,QueryFlightUtil,SelectFlightUtil...
1 是LaputaLogin,LaputaQueryFlight,LaputaSelectFlight....的小工具类
TestCaseService实现Runnable接口的抽象类
1 是所有的测试用例的存放类
XXXCase 继承TestCaseService类并实现Runnable接口的类
1 为单个测试用例的启动类
ThreadPoolService 线程池管理类
1 提供多个测试用例一起执行的方法,采用Executors.newCachedThreadPool();方式启动线程池
package org.travelsky.autotest.selenium.model;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Date;import java.util.List;import org.openqa.selenium.Alert;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.WebElement;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.firefox.FirefoxDriver;import org.openqa.selenium.htmlunit.HtmlUnitDriver;import org.openqa.selenium.ie.InternetExplorerDriver;import org.openqa.selenium.support.ui.ExpectedCondition;import org.openqa.selenium.support.ui.WebDriverWait;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.travelsky.autotest.selenium.config.BaseConst;import org.travelsky.autotest.selenium.config.StartUpType;import org.travelsky.autotest.selenium.exception.MyExecuteMethodOutOfTimesException;import org.travelsky.autotest.selenium.exception.MyInvokeException;import org.travelsky.autotest.selenium.exception.MySeleniumException;import org.travelsky.autotest.util.DateOpt;import org.travelsky.autotest.util.RegexUtil;public class BaseSeleniumManager implements ExpectedCondition<WebElement>, BaseSelenium { protected Logger log = LoggerFactory.getLogger(getClass()); private String xpath; public int failCout = BaseConst.FAIL_COUNT; private boolean acceptNextAlert = true; /** * 启动浏览器 */ public WebDriver startUpBrowse(StartUpType type) { WebDriver driver; switch (type) { case IE: System.setProperty("webdriver.ie.driver", "E:\\sts-bundle\\selenium-2.42.2\\IEDriverServer.exe"); driver = new InternetExplorerDriver(); break; case FireFox: System.setProperty("webdriver.firefox.bin", "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); driver = new FirefoxDriver(); break; case Chrome: System.setProperty("webdriver.chrome.bin", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"); driver = new ChromeDriver(); break; case HtmlUnit: driver = new HtmlUnitDriver(); break; default: throw new IllegalArgumentException("type is wrong"); } return driver; } /** * 关闭driver * * @param driver */ public void close(WebDriver driver) { if (null == driver) return; driver.quit(); } /** * you will not directly use this ,you should be use "checkCondition" method * implement ExpectedCondition */ @Override public WebElement apply(WebDriver driver) { return driver.findElement(By.xpath(this.xpath)); } /** * this method is synchronized to find and return element * * @param driver * @param xpath * locate element * @param timeOutSecond * after this time ,will be throw a exception * @return */ public WebElement checkCondition(WebDriver driver, String xpath) { this.xpath = xpath; WebDriverWait wait = new WebDriverWait(driver, BaseConst.OUT_TIME_SECOND); WebElement element = wait.until(this); return element; } /** * 自定义超时时间 * @param driver * @param xpath * @param timeout 超时时间 * @return */ public WebElement checkCondition(WebDriver driver, String xpath,int timeout) { this.xpath = xpath; WebDriverWait wait = new WebDriverWait(driver, timeout); WebElement element = wait.until(this); return element; } /** * repeat click element ,if click event have a exception will find the next * element in the list<WebElement> to click * * @param elements * @param i * start with the index = i element */ public Boolean clickWebElement(List<WebElement> elements, int i) { int size = elements.size(); if (i == size) { log.error("没有合适的按钮"); return false; } try { elements.get(i).click(); return true; } catch (Exception e) { ++i; this.clickWebElement(elements, i); } return true; } /** * 重新运行传入的方法,次数为failCout配置的数量 * 会抛出MySeleniumException和MyInvokeException异常 * @throws MyInvokeException */ public void runBack(Method m, Throwable e, Object... args) throws MyExecuteMethodOutOfTimesException, MyInvokeException { if (this.failCout < 0) { throw new MyExecuteMethodOutOfTimesException(this.getClass()+m.getName()+ " 方法执行超过次数", e); } this.failCout--; try { m.invoke(this, args); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) { throw new MyInvokeException(this.getClass().getName()+m.getName()+"方法回滚异常",e1); } } /** * 弹出框点确认 */ public String AcceptAlertAndGetItsText(WebDriver driver) { String alertText = ""; try { Alert alert = driver.switchTo().alert(); alertText = alert.getText(); if (acceptNextAlert) { alert.accept(); } else { alert.dismiss(); } return alertText; } catch (Exception e) { //不需要做处理 } finally { acceptNextAlert = true; } return alertText; } /** * 关闭弹出框 */ public String closeAlertAndGetItsText(WebDriver driver) { String alertText = ""; try { Alert alert = driver.switchTo().alert(); alertText = alert.getText(); alert.dismiss(); return alertText; } catch (Exception e) { //不需要做处理 } return alertText; } /** * 正则匹配替换URL的日期 * * @param url * 地址 * @param regex * 匹配策略"\\d{6}"表示匹配6位数字 * @param dateFormat * 日期格式20140404的格式为"yyyyMMdd" * @param addDay * 后移几天 * @return */ public String repalceUrl(String url, String regex, String dateFormat, int addDay) { String newUrl = url; List<String> matches = RegexUtil.matchRegex(newUrl, regex); for (String old : matches) { Date day = DateOpt.stringTypeToDateType(old, dateFormat); String reString = DateOpt.dateTypeToString(DateOpt.add(day, addDay), dateFormat); newUrl = newUrl.replace(old, reString); } return newUrl; } /** * * @param ele CHECKBOX元素 * */ public void selectCheckBox(WebElement ele, boolean selected) { if (ele.isSelected()&&!selected||!ele.isSelected()&&selected) { ele.click(); } } /** * 由于部分页面渲染速度慢,导致无法点击 * @param second */ public void delay(int second){ try { Thread.sleep(second*1000); } catch (InterruptedException e) { log.error("BaseSeleniumManager的delay()方法报错",e); } } /** * 检查页面的title * @param driver * @param expect * @return */ public boolean checkTitle(WebDriver driver,String expect){ return driver.getTitle().contains(expect); }}
package org.travelsky.autotest.selenium.service.manager;import java.util.ArrayList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.travelsky.autotest.selenium.service.TestCaseService;/** * * @author sunfan * */public class ThreadPoolService { static Logger log = LoggerFactory.getLogger("ThreadPoolService"); /** * * @param testcases * @throws InterruptedException */ public void executeTestCase( ArrayList<? extends Runnable> testcases) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); for (Runnable testCase : testcases) { executorService.execute(testCase); } } /** * 获取当前线程池中正在运行的线程数 * @param executorService * @return */ public int getThreadCount(ExecutorService executorService) { int threadCount = ((ThreadPoolExecutor) executorService) .getActiveCount(); log.info("当前正在运行的线程数:" + threadCount); return threadCount; }}
package org.travelsky.autotest.selenium.service;import java.util.List;import javax.security.auth.login.LoginException;import org.openqa.selenium.WebDriver;import org.travelsky.autotest.selenium.config.StartUpType;import org.travelsky.autotest.selenium.exception.MyBookException;import org.travelsky.autotest.selenium.exception.MyExecuteMethodOutOfTimesException;import org.travelsky.autotest.selenium.exception.MyFreePassengerException;import org.travelsky.autotest.selenium.exception.MyInvokeException;import org.travelsky.autotest.selenium.exception.MyLoginException;import org.travelsky.autotest.selenium.exception.MyPassengerException;import org.travelsky.autotest.selenium.exception.MyPaymentException;import org.travelsky.autotest.selenium.exception.MyQueryFlightException;import org.travelsky.autotest.selenium.exception.MySelectFlightException;import org.travelsky.autotest.selenium.model.BaseSeleniumManager;import org.travelsky.autotest.selenium.model.laputa.entity.QueryFlightInfo;import org.travelsky.autotest.selenium.model.laputa.entity.User;import org.travelsky.autotest.selenium.model.laputa.impl.LaputaDynamicAvation;import org.travelsky.autotest.selenium.model.laputa.impl.LaputaLowPrice;import org.travelsky.autotest.selenium.service.util.TestCaseServiceUtil;/** * It‘s a entrance to provide some method to run testcase * * @author sunfan * */public abstract class TestCaseService extends BaseSeleniumManager implements Runnable { TestCaseServiceUtil util = new TestCaseServiceUtil(); WebDriver driver; /** * @param type 启动浏览器的方式, StartUpType.IE,StartUpType.FireFox * @param orgCity 出发城市 * @param arrCity 到达城市 * @param depdate 单程时间 * @param rtDate 往返时间 (如果无往返可不填) * @param product 单程产品 "[productCd]", "BNGM" "COMMON_INTER_Y_DIS", "" * @param rtProduct 往返产品(注意,如果为单程航班这里必须为""或者NULL) * @param tripType 单程还是往返 * @param usr 登录用户 * @param pw 登录账号 * @param loginType 登录类型 填 ffp或者mobile * @throws MyQueryFlightException * @throws MyPassengerException * @throws MySelectFlightException * @throws LoginException * @throws MyBookException * @throws SecurityException * @throws NoSuchMethodException * @throws Exception */ public String bookCommonFlight(StartUpType type,User user,QueryFlightInfo queryFlightInfo,List<?> paxs,boolean insurance,String testcase) throws MyLoginException,MyQueryFlightException, MySelectFlightException,MyPassengerException, MyBookException { driver = util.loginStep(type, user); util.queryFlightStep(driver, queryFlightInfo); util.selectFlightStep(driver, queryFlightInfo); util.passengerStep(driver, paxs, insurance); String res = util.bookStep(driver, testcase, user); return res; } public String bookWsdFlight(StartUpType type,User user,QueryFlightInfo queryFlightInfo,List<?> paxs,List<?> freePaxs,boolean insurance,String testcase) throws MyLoginException,MyQueryFlightException, MySelectFlightException,MyPassengerException, MyBookException, MyFreePassengerException { driver = util.loginStep(type, user); util.queryFlightStep(driver, queryFlightInfo); util.selectFlightStep(driver, queryFlightInfo); util.passengerStep(driver, paxs, insurance); util.freePassengerStep(driver, freePaxs, insurance); String res = util.bookStep(driver, testcase, user); return res; } public String bookAndIssueCommonTicket(StartUpType type,User user,QueryFlightInfo queryFlightInfo,List<?> paxs,boolean insurance,String payType,String testcase) throws MyLoginException,MyQueryFlightException, MySelectFlightException,MyPassengerException, MyBookException, MyPaymentException{ String res = this.bookCommonFlight(type, user, queryFlightInfo, paxs, insurance, testcase); if(res.contains("error code:")) return res; return res + " "+util.pay(driver, payType,user); } public String bookAndIssueWsdTicket(StartUpType type,User user,QueryFlightInfo queryFlightInfo,List<?> paxs,List<?> freePaxs,boolean insurance,String payType,String testcase) throws MyLoginException,MyQueryFlightException, MySelectFlightException,MyPassengerException, MyBookException, MyPaymentException, MyFreePassengerException{ String res = this.bookWsdFlight(type, user, queryFlightInfo, paxs, freePaxs, insurance, testcase); if(res.contains("error code:")) return res; return res + " "+util.pay(driver, payType,user); } /** * 航班动态 * @param type * @param city1 * @param city2 * @return * @throws MyInvokeException * @throws MyExecuteMethodOutOfTimesException * @throws Exception */ public String dynamicFlight(StartUpType type,String city1 ,String city2) throws MyExecuteMethodOutOfTimesException, MyInvokeException { driver = super.startUpBrowse(type); String res = new LaputaDynamicAvation().queryDynamicFlight(driver, city1, city2); return util.addcheckResult(res, 17); } public String lowPrice(StartUpType type) throws MyExecuteMethodOutOfTimesException, MyInvokeException { driver = super.startUpBrowse(type); String res =new LaputaLowPrice().showLowPrice(driver); return util.addcheckResult(res, 17); } /** * 实现多线程 */ public abstract void run(); public abstract String execute(); public void closeCurrentDriver() { super.close(this.driver); }}
package org.travelsky.autotest.selenium.service.runable;import java.util.List;import org.travelsky.autotest.selenium.config.StartUpType;import org.travelsky.autotest.selenium.exception.MyBookException;import org.travelsky.autotest.selenium.exception.MyLoginException;import org.travelsky.autotest.selenium.exception.MyPassengerException;import org.travelsky.autotest.selenium.exception.MyPaymentException;import org.travelsky.autotest.selenium.exception.MyQueryFlightException;import org.travelsky.autotest.selenium.exception.MySelectFlightException;import org.travelsky.autotest.selenium.model.laputa.entity.QueryFlightInfo;import org.travelsky.autotest.selenium.model.laputa.entity.User;import org.travelsky.autotest.selenium.service.TestCaseService;public class BookAndIssueCommonTicketCase extends TestCaseService { StartUpType type; QueryFlightInfo queryFlightInfo; User user; String testcase; List<?> paxs; boolean insurance; String payType; public BookAndIssueCommonTicketCase(StartUpType type, User user, QueryFlightInfo queryFlightInfo, List<?> paxs, boolean insurance, String payType, String testcase) { this.type = type; this.queryFlightInfo = queryFlightInfo; this.user = user; this.paxs = paxs; this.testcase = testcase; this.insurance = insurance; this.payType = payType; } @Override public String execute() { String res = null; try { res = super.bookAndIssueCommonTicket(type, user, queryFlightInfo, paxs, insurance, payType, testcase); } catch (MyPaymentException e) { res = "支付或出票页面报错"; log.error(res, e); } catch (MyQueryFlightException e) { res = "查询航班页面报错"; log.error(res, e); } catch (MySelectFlightException e) { res = "选择航班页面报错"; log.error(res, e); } catch (MyPassengerException e) { res = "旅客资料页面报错"; log.error(res, e); } catch (MyBookException e) { res = "预定页面报错"; log.error(res, e); } catch (MyLoginException e) { res = "登录页面报错"; log.error(res, e); } finally { super.closeCurrentDriver(); } return res; } @Override public void run() { try { super.bookAndIssueCommonTicket(type, user, queryFlightInfo, paxs, insurance, payType, testcase); } catch (Exception e) { log.error(this.getClass().getName(), e); } finally { super.closeCurrentDriver(); } }}
package org.travelsky.autotest.selenium.model.laputa.impl;import java.lang.reflect.InvocationTargetException;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.travelsky.autotest.selenium.exception.MyExecuteMethodOutOfTimesException;import org.travelsky.autotest.selenium.exception.MyInvokeException;import org.travelsky.autotest.selenium.exception.MyQueryFlightException;import org.travelsky.autotest.selenium.exception.MySeleniumException;import org.travelsky.autotest.selenium.model.BaseSeleniumManager;import org.travelsky.autotest.selenium.model.laputa.QueryFlight;import org.travelsky.autotest.selenium.model.laputa.entity.QueryFlightInfo;import org.travelsky.autotest.selenium.model.laputa.util.QueryFlightUtil;/** * * @author sunfan * */public class LaputaQueryFlight extends BaseSeleniumManager implements QueryFlight { QueryFlightUtil queryFlightUtil = new QueryFlightUtil(1); /** * * @param driver * @param queryFlight * @param int input "1" = "http://easternmiles.ceair.com/flight2014/" or * input "2" = http://easternmiles.ceair.com/flight/"; * @throws MyExecuteMethodOutOfTimesException * @throws MyInvokeException * @throws MySeleniumException * @throws InterruptedException * @throws InstantiationException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchMethodException */ public WebDriver queryFlight(WebDriver driver, QueryFlightInfo queryFlight) throws MyExecuteMethodOutOfTimesException, MyInvokeException { try { delay(2); driver.switchTo().window(driver.getWindowHandle());// 前置当前窗口 queryFlightUtil.inputQueryInfo(driver, queryFlight);// 输入查询航班内容 queryFlightUtil.checkJF(driver, queryFlight);// 判断是否需要点击积分兑换按钮 delay(1); driver.findElement(By.id("btn_member_search")).click();// 点击查询按钮 delay(2); super.AcceptAlertAndGetItsText(driver);// 点击可能出现的弹出框 queryFlightUtil.confirmSuccess(driver);// 确认是否成功 } catch (Exception e) { try { super.runBack( this.getClass().getMethod("queryFlight", org.openqa.selenium.WebDriver.class, org.travelsky.autotest.selenium.model.laputa.entity.QueryFlightInfo.class), e, driver, queryFlight); } catch (NoSuchMethodException | SecurityException e1) { log.info("反射获取login()queryFlight", e1); } } return driver; }}
package org.travelsky.autotest.config.spring;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configuration@ComponentScan(basePackages="org.travelsky.autotest.*")@EnableWebMvc@EnableSchedulingpublic class WebConfig extends WebMvcConfigurerAdapter{ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resource/**").addResourceLocations("/resource/"); }}
package org.travelsky.autotest.config.spring;import java.util.EnumSet;import java.util.HashMap;import java.util.Map;import javax.servlet.DispatcherType;import javax.servlet.FilterRegistration;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration;import org.springframework.web.WebApplicationInitializer;import org.springframework.web.context.ContextLoaderListener;import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import org.springframework.web.filter.CharacterEncodingFilter;import org.springframework.web.servlet.DispatcherServlet;public class WebInit implements WebApplicationInitializer { @Override public void onStartup(ServletContext sc) throws ServletException { // Create the ‘root‘ Spring application context AnnotationConfigWebApplicationContext application = new AnnotationConfigWebApplicationContext(); application.scan("org.travelsky.autotest.config.spring"); // Manages the lifecycle of the root application context sc.addListener(new ContextLoaderListener(application)); //CharacterEncodingFilter CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); characterEncodingFilter.setForceEncoding(true); FilterRegistration filterRegistration = sc.addFilter("characterEncodingFilter", characterEncodingFilter); filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext(); ServletRegistration.Dynamic appServlet = sc.addServlet("appServlet", new DispatcherServlet(webContext)); appServlet.setLoadOnStartup(1); appServlet.addMapping("/"); }}