前言


最初目的,为了在整体测试平台框架下,测试人员可便捷维护用例,无需启动服务、编译程序,提供一套Excel填写测试用例并能快速执行的解决方案。后续扩展了不同类型的测试用例,本文则对Web类型的测试用例Excel方式执行测试逻辑进行介绍。
开发过程中,本想着网上CV大法搞下来修修改改,结果发现没一个清晰完整的分享,于是,结合自身理解,以及测试框架设计,形成了目前的一个版本,同时分享给测试小伙伴,希望能对各位测试伙伴能有所帮助。

其他说明

  • 开发语言Java,采用SpringBoot开发框架(版本2.1.18.RELEASE)(文章尾部会贴出各依赖版本POM文件),测试底层调用Selenium框架方法,以及测试注解引用TestNG测试框架,本文所述功能仅为整体测试框架中一个小功能点,所以分享并没有全部贴出(不是作者不想,作者自研,完全有权限分享,只是内容太多,没整理好,就暂时不发出来)
  • 所有代码均有注释,代码基本不再细致说明,以及一些额外相关的代码没贴出来(主要是放这里显得杂乱,有机会单写文章说明整体架构逻辑及源码,有兴趣留言交流)
  • 本文主要从作者主观逻辑、代码进行介绍,可能部分方法、部分逻辑并未介绍到位,作者写文纯属锻炼下文档(好久没写文档了),不完善的地方请各位玩家理解,欢迎留言咨询,后续看源码通过什么形式发出来
  • 文章作者:@随心自然fqc , 转载时,请注明来源,注明作者,这是对文章作者的尊重,也是对知识的尊重。

整体图解


先看看整体逻辑,看完整体代码逻辑后,建议再回头看看这张图 ><

主要内容

1、测试用例调用方式
import cn.nhdc.cloud.modules.casemanage.model.vo.TestCaseVo;
import cn.nhdc.cloud.testscripts.listener.ExtentTestNGIReporterListener;
import cn.nhdc.cloud.testscripts.testcase.base.WebTestBase;
import org.testng.annotations.Listeners;
import org.testng.annotations.Optional;
import org.testng.annotations.Test;import java.util.List;/*** Web类型 Excel执行方式 自动化测试demo <br>* Method 1 :  {@link #testExcelExcute}  正向逻辑执行  <br>* Method 2 : {@link #testExcelInvokeExcute}  反射逻辑执行  <br>** ps: 以上2种方式 原理不同 但执行效果类似 具体选择哪种取决于调用者意愿** @author Fan QingChuan*/
@Test(description = "Excel方式自动化测试示例-正向逻辑&反射执行逻辑")
@Listeners(value = ExtentTestNGIReporterListener.class)
public class ExcelWebTestDemo extends WebTestBase {@Test(description = "正向逻辑: 操作方法封装在 WebTestBase 中, 测试用例中可直接调用  解析Excel -> caseSteps 根据遍历操作编码switch执行各项操作")void testExcelExcute(@Optional("C:\\Users\\allen\\Desktop\\自动化测试_Web类型测试用例_2022_07_07_185853_437.xlsx") String fileName,@Optional("testcase")String sheetName,@Optional("null")Integer headerRowNumber) {List<TestCaseVo> caseVoList = analysisExcelUiCase(fileName, sheetName, headerRowNumber);excuteExcelUiTest(caseVoList);}@Test(description = "反射逻辑: 操作封装在 WebCommon 通过 WebTestBase 中反射获取BasePage.class 并实例化 再根据操作编码+分类枚举 获取对应方法 并反射(invoke)执行测试")void testExcelInvokeExcute(@Optional("C:\\Users\\allen\\Desktop\\自动化测试_Web类型测试用例_2022_07_21_134019_690.xlsx") String fileName,@Optional("testcase")String sheetName,@Optional("null")Integer headerRowNumber)throws  IllegalAccessException, InstantiationException {List<TestCaseVo> caseVoList = analysisExcelUiCase(fileName, sheetName, headerRowNumber);invokeExcelUiTest(caseVoList);}}
2、测试用例对象Vo
import cn.nhdc.cloud.modules.casemanage.service.Add;
import cn.nhdc.cloud.modules.casemanage.service.Update;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;/*** * @author Fan QingChuan*/
@Data
public class TestCaseVo {@NotNull(message = "caseId(用例ID)不能为空", groups = {Update.class})private Long caseId;/*** 测试集ID*/private Long suiteId;/*** 团队code */@NotBlank(message = "teamCode 团队code不能为空", groups = {Add.class, Update.class})private String teamCode;/*** 测试用例编码*/@NotBlank(message = "caseCode 用例编号不能为空", groups = {Add.class, Update.class})private String caseCode;/*** 测试用例类型  1-接口测试 2-Web测试 3-安卓APP测试 4-IOS APP测试 9-其他混合测试*/@NotNull(message = "type 测试用例类型不能为空", groups = {Add.class, Update.class})private Integer type;/*** 用例顺序号*/private Integer sort;/*** 用例描述*/private String description;/*** 测试人员姓名*/@NotBlank(message = "tester 测试人员姓名不能为空", groups = {Add.class, Update.class})private String tester;/*** 测试步骤List*/@Validprivate List<UiCaseStepVo> caseSteps;@Datapublic static class UiCaseStepVo{@NotNull(message = "caseId 测试用例ID不能为空",groups = {Update.class})private Long caseId;@NotBlank(message = "caseCode 测试用例编码不能为空",groups = {Update.class})private String caseCode;@NotNull(message = "stepId 步骤ID不能为空",groups = {Update.class})private Long stepId;/*** 步骤顺序号*/@NotNull(message = "sort 步骤序号不能为空",groups = {Add.class, Update.class})private Integer sort;/*** 步骤描述*/@ExcelProperty("测试步骤描述")private String description;/*** 关键字(操作)编码*/@NotBlank(message = "actionKeyword 关键字(操作)编码不能为空",groups = {Add.class, Update.class})private String actionKeyword;/*** 元素定位方式(类型)*/private String elementLocateType;/*** 元素定位信息*/private String elementLocateValue;/*** 输入值*/private String parameter;}
}
  • 如代码所示,将测试用例各字段都考虑进去,如需扩展API测试,可在放进该对象
  • 字段上各校验注解,是用例管理CRUD接口中需要的,与本次分享毫无关系(例如 @NotNull(message = “caseId(用例ID)不能为空”, groups = {Update.class})),如有测试小白,可忽略,作者懒癌犯了,就没删除
3、解析Excel >> 测试用例

这个步骤就是将Excel填写好的测试用例,解析成我们刚才看到的一个个TestCaseVo对象,即List<TestCaseVo> , 用的EasyExcel工具解析

import cn.hutool.core.util.ObjectUtil;
import cn.nhdc.cloud.common.utils.HttpUtils;
import cn.nhdc.cloud.modules.casemanage.model.vo.ExcelCaseStepVo;
import cn.nhdc.cloud.modules.casemanage.model.vo.TestCaseVo;
import cn.nhdc.cloud.testscripts.config.ReportLog;
import cn.nhdc.cloud.testscripts.testcase.base.TestBase;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xiaoleilu.hutool.bean.BeanUtil;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** * @author Fan QingChuan*/
public interface TestCommon{/*** 解析Excel UI测试用例 <br>* 解析逻辑: 按测试用例编码(caseCode)进行分组,将所有步骤封装到对应用例编码下 <br>* 与测试用例-测试步骤数据结构对应, {@link TestCaseVo.UiCaseStepVo} 一对多关系** @param filePath 文件绝对路径* @param sheetName 需解析的sheetName 默认Sheet1* @param headerRowNumber 解析case的标题行数 默认1行* @return a List of {@link TestCaseVo}*/default List<TestCaseVo> analysisExcelUiCase(String filePath, String sheetName, Integer headerRowNumber){List<ExcelCaseStepVo> caseStepVos = EasyExcel.read(filePath).head(ExcelCaseStepVo.class).sheet(sheetName != null ? sheetName : "Sheet1").headRowNumber(ObjectUtil.isNotEmpty(headerRowNumber) && headerRowNumber > 0 ? headerRowNumber : 1).doReadSync();reportLog.info(" ======== >> 解析到[{}]条数据",caseStepVos.size());List<String> caseCodeList = caseStepVos.stream().map(ExcelCaseStepVo::getCaseCode).distinct().collect(Collectors.toList());List<TestCaseVo> caseVoList = new ArrayList<>(caseCodeList.size());reportLog.info(" ======== >> 重新组装成[{}]条测试用例",caseCodeList.size());caseCodeList.forEach(caseCode -> {TestCaseVo testCaseVo = new TestCaseVo();testCaseVo.setCaseCode(caseCode);List<TestCaseVo.UiCaseStepVo> caseSteps = caseStepVos.stream().filter(o -> caseCode.equals(o.getCaseCode())).map(excelCaseStepVo -> {TestCaseVo.UiCaseStepVo uiCaseStepVo = new TestCaseVo.UiCaseStepVo();BeanUtil.copyProperties(excelCaseStepVo,uiCaseStepVo);return uiCaseStepVo;}).collect(Collectors.toList());testCaseVo.setCaseSteps(caseSteps);caseVoList.add(testCaseVo);});return caseVoList;}default void threadSleep(String seconds) {if (seconds == null || Long.parseLong(seconds) < 1 ) {throw new IllegalArgumentException("threadSleep 未设置休眠秒数 即输入值(秒值)不能为空 且 值需大于0");}long second = Long.parseLong(seconds);try {Thread.sleep(second * 1000);} catch (InterruptedException e) {e.printStackTrace();}}
}
4、执行测试逻辑

我们得到了List<TestCaseVo> caseVoList,下一步自然就是执行测试了,如果你是测试小白,那作者希望你能仔细读读以下代码。
其中关键点:

  • 区分 excuteExcelUiTest 与 invokeExcelUiTest 的调用逻辑区别
  • 不同关键字操作方法区别(以及对应在测试活动中的区别)

通过此类 你将收获到一丝丝灵感,方便你进行自己的创作

import cn.hutool.core.util.ObjectUtil;
import cn.nhdc.cloud.modules.casemanage.model.vo.TestCaseVo;
import cn.nhdc.cloud.testscripts.config.ReportLog;
import cn.nhdc.cloud.testscripts.enums.WebActionTypeEnum;
import cn.nhdc.cloud.testscripts.page.base.BasePage;
import cn.nhdc.cloud.testscripts.testcase.base.common.WebCommon;
import org.openqa.selenium.WebDriver;
import org.springframework.web.multipart.MultipartFile;
import org.testng.Assert;
import org.testng.ITestResult;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;/*** * @author Fan QingChuan*/
public abstract class WebTestBase implements WebCommon {public static final int TIME_OUT = 10;public static final int SLEEP_TIME = 300;private static final ReportLog reportLog = new ReportLog(WebTestBase.class);protected WebDriver baseWebDriver;/*** 执行测试(Excel UI自动化测试-正常逻辑)<br>* 正常逻辑: 按测试用例循环遍历测试,在一个测试用例中,先将用例下所有测试步骤按步骤序号进行排序(升序), <br>* 再按升序进行测试, 每个步骤按操作编码进行识别调用对应操作方法进行测试,* 调用方式: switch(actionType) case ...actionMethod 将所有操作类型分为4种调用方式 详情请看{@link WebActionTypeEnum}** @param caseVoList 解析后的测试用例List  {@link TestCaseVo}*/public void excuteExcelUiTest(List<TestCaseVo> caseVoList) {caseVoList.forEach(caseVo -> {List<TestCaseVo.UiCaseStepVo> caseSteps = caseVo.getCaseSteps().stream().sorted(Comparator.comparingInt(TestCaseVo.UiCaseStepVo::getSort)).collect(Collectors.toList());caseSteps.forEach(step -> {reportLog.info("测试用例编号[{}] 步骤序号:[{}],测试步骤描述 [{}],操作编码:[{}],元素定位方式[{}],定位值[{}],输入参数值[{}]",step.getCaseCode(),step.getSort(),step.getDescription(),step.getActionKeyword(),step.getElementLocateType(),step.getElementLocateValue(),step.getParameter());WebActionTypeEnum webActionTypeEnum = WebActionTypeEnum.getTargetType(step.getActionKeyword());Assert.assertNotNull(webActionTypeEnum,"操作编码不正确或不存在!");switch (webActionTypeEnum) {//type-0case ClosePage:closePage();break;//此处省略类似代码(其他操作关键字方式)//type-1case OpenUrl:if (step.getParameter() == null) {throw new IllegalArgumentException("openUrl 输入值(网址)不能为空  步骤序号:" +step.getSort() +"用例编号:"+caseVo.getCaseCode());}openUrl(step.getParameter());break;//此处省略类似代码(其他操作关键字方式)//type-2case Click:if (step.getElementLocateType() == null || step.getElementLocateValue() == null) {throw new IllegalArgumentException("click 元素定位不能为空  步骤序号:" +step.getSort() +"用例编号:"+caseVo.getCaseCode());}click(step.getElementLocateType(), step.getElementLocateValue());break;//此处省略类似代码(其他操作关键字方式)//type-3case ClickCustomWait:if (step.getElementLocateType() == null || step.getElementLocateValue() == null || step.getParameter() == null) {throw new IllegalArgumentException("clickCustomWait 元素定位与输入值均不能为空  步骤序号:" +step.getSort() +"用例编号:"+caseVo.getCaseCode());}String[] parameters = step.getParameter().split(":");if (parameters.length > 2) {throw new IllegalArgumentException("clickCustomWait 等待配置输入值不合法 格式:超时时间(单位秒):检查频率(单位毫秒) 例如:3:500 表示等待3秒,每500毫秒检查一次  步骤序号:" +step.getSort() +"用例编号:"+caseVo.getCaseCode());}clickCustomWait(Long.valueOf(parameters[0]),Long.valueOf(parameters[1]),step.getElementLocateType(),step.getElementLocateValue());break;//此处省略类似代码(其他操作关键字方式)//type-4case InitBaseWebBrowser:initBaseBrowser(step.getParameter());break;case ThreadSleep:threadSleep(step.getParameter());break;default:throw new IllegalArgumentException("操作编码不合法! 或 不存在该操作 如有必要请联系我增加 > <");}});});}protected void closePage() {closePage(baseWebDriver);}protected void openUrl(String url) {openUrl(baseWebDriver,url);}protected void click(String elementLocateType, String elementLocateValue) {click(baseWebDriver,elementLocateType,elementLocateValue);}protected void clickCustomWait(long outTimeSeconds, long sleep, String elementLocateType, String elementLocateValue) {clickCustomWait(baseWebDriver,outTimeSeconds,sleep,elementLocateType,elementLocateValue);}//此处省略类似代码(其他操作)/*** 初始化基类baseWebDriver* @param browserInfo*/protected void initBaseBrowser(String browserInfo) {//buildWebBrowser生产新的driverbaseWebDriver = buildWebBrowser(browserInfo);}/*** 执行测试(Excel UI自动化测试-反射逻辑)<br>* 反射逻辑: 按测试用例循环遍历测试,在一个测试用例中,先将用例下所有测试步骤按步骤序号进行排序(升序), <br>* 再按升序进行测试, 每个步骤按操作编码进行识别反射调用对应操作方法进行测试, <br>* 调用方式: switch(actionType) case ...invoke(actionMethod)  将所有操作类型分为4种调用方式 详情请看{@link WebActionTypeEnum} <br>* 与正向逻辑的区别在于 反射逻辑需实例化对应基类- {@link BasePage} 并在每个测试步骤传入上下文的 WebDriver,* 另需注意buildWebBrowser(初始化打开浏览器)操作属特殊情况,不能传入WebDriver,因为在正常情况下,buildWebBrowser前WebDriver为null没初始化,* 当然就不存在上文WebDriver** @param caseVoList 解析后的测试用例List  {@link TestCaseVo}* @throws InstantiationException* @throws IllegalAccessException*/public void invokeExcelUiTest(List<TestCaseVo> caseVoList) throws InstantiationException, IllegalAccessException {Class<?> clazz = null;try {clazz = Class.forName("cn.nhdc.cloud.testscripts.page.base.BasePage");} catch (ClassNotFoundException e) {e.printStackTrace();}assert clazz != null;BasePage instance = (BasePage) clazz.newInstance();Class<?> finalClazz = clazz;caseVoList.forEach(caseVo -> {List<TestCaseVo.UiCaseStepVo> caseSteps = caseVo.getCaseSteps().stream().sorted(Comparator.comparingInt(TestCaseVo.UiCaseStepVo::getSort)).collect(Collectors.toList());caseSteps.forEach(step -> {try {reportLog.info("测试用例编号[{}] 步骤序号:[{}],测试步骤描述 [{}],操作编码:[{}],元素定位方式[{}],定位值[{}],输入参数值[{}]",step.getCaseCode(),step.getSort(),step.getDescription(),step.getActionKeyword(),step.getElementLocateType(),step.getElementLocateValue(),step.getParameter());if (step.getActionKeyword().equals(WebActionTypeEnum.InitBaseWebBrowser.getActionKeyword())) {Method method = finalClazz.getMethod(step.getActionKeyword(),String.class);this.baseWebDriver = (WebDriver) method.invoke(instance,step.getParameter());}else {invokeMethod(finalClazz, instance, this.baseWebDriver, step.getActionKeyword(), step.getElementLocateType(), step.getElementLocateValue(), step.getParameter());}} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}});});}private void invokeMethod(Class<?> clazz, Object instance, WebDriver driver, String actionKeyword, String elementLocateType, String elementLocateValue, String parameter) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {WebActionTypeEnum webActionTypeEnum = WebActionTypeEnum.getTargetType(actionKeyword);if (ObjectUtil.isNull(webActionTypeEnum)) {throw new IllegalStateException("操作编码 actionKeyword 不合法");}Method method;switch (webActionTypeEnum.getType()) {case 0:method = clazz.getMethod(actionKeyword,WebDriver.class);method.invoke(instance,driver);break;case 1:method = clazz.getMethod(actionKeyword,WebDriver.class,String.class);method.invoke(instance,driver,parameter);break;case 2:method = clazz.getMethod(actionKeyword,WebDriver.class,String.class,String.class);method.invoke(instance,driver,elementLocateType,elementLocateValue);break;case 3:method = clazz.getMethod(actionKeyword,WebDriver.class,String.class,String.class,String.class);method.invoke(instance,driver,elementLocateType,elementLocateValue,parameter);break;case 4:method = clazz.getMethod(actionKeyword,String.class);method.invoke(instance,parameter);break;default:break;}}
}

也许你会好奇 ->

* 反射逻辑-invokeExcelUiTest中,调用反射执行invokeMethod(...),这些具体操作方法在哪里?
* 正向逻辑-excuteExcelUiTest中,调用WebTestBase.closePage() ,而closePage()中又调用了个closePage(baseWebDriver)?  WTF?

答案就是

  • 对于公共操作,作者都将其进行了集中抽取并分层管理,类似最开始图解中 WebCommon -> TestCommon ,包括作者没贴出来的AndroidCommon -> AppCommon -> WebCommon -> TestCommon
    每层通用的均各自层级实现,调用者只需要implements 对应抽象类即可调用。

PS: 是不是你就懂了前文 TestCommon中为什么有个方法 threadSleep(String seconds)了,除了可提供给用户Excel操作关键字,也可以视为所有测试活动都可以用这个方法(线程休眠)

那么回到主题,WebCommon长什么样子呢? 请往下继续

import cn.nhdc.cloud.common.utils.DateUtils;
import cn.nhdc.cloud.testscripts.config.Assertion;
import cn.nhdc.cloud.testscripts.config.WebDriverFactory;
import cn.nhdc.cloud.testscripts.testcase.base.WebTestBase;
import cn.nhdc.common.util.CollectionUtils;
import io.appium.java_client.AppiumBy;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.ITestResult;import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;/*** * @author Fan QingChuan*/
public interface WebCommon extends TestCommon {int WEB_COMMON_TIME_OUT = WebTestBase.TIME_OUT;int WEB_COMMON_SLEEP_TIME = WebTestBase.SLEEP_TIME;default void closePage(WebDriver driver) {if (driver != null) {driver.close();}else {throw new RuntimeException("不存在运行的浏览器  closePage失败! ");}}default void openUrl(WebDriver driver,String url) {driver.get(url);}default void click(WebDriver driver,String elementLocateType, String elementLocateValue) {WebElement element_click = new WebDriverWait(driver, Duration.ofSeconds(WEB_COMMON_TIME_OUT), Duration.ofMillis(WEB_COMMON_SLEEP_TIME)).until(ExpectedConditions.elementToBeClickable(getBy(elementLocateType, elementLocateValue)));element_click.click();}/*** 点击元素-自定义等待** @param driver* @param outTimeSeconds  最大等待时间 单位秒* @param sleep 检查频率 单位 毫秒* @param elementLocateType 定位方式* @param elementLocateValue 定位信息*/default void clickCustomWait(WebDriver driver,long outTimeSeconds, long sleep, String elementLocateType, String elementLocateValue) {WebElement element_click = new WebDriverWait(driver, Duration.ofSeconds(outTimeSeconds), Duration.ofMillis(sleep)).until(ExpectedConditions.elementToBeClickable(getBy(elementLocateType, elementLocateValue)));element_click.click();}//此处省略类似代码(其他操作关键字具体行为)/*** 初始化浏览器并返回给调用者* @param browserInfo* @return*/default WebDriver buildWebBrowser(String browserInfo) {if (browserInfo == null) {throw new IllegalArgumentException("buildBrowser 输入值不能为空 格式 浏览器名称:版本号 例如 chrome:102");}String[] browser = browserInfo.split(":");if (browser.length > 2) {throw new IllegalArgumentException("buildBrowser 浏览器名称及版本号格式不合法 示例: 类型:版本号 chrome:102");}//实例化新的driverreturn WebDriverFactory.initDriver(browser[0],browser[1]);}

也许聪明的你已经看出来了,所谓关键字操作,被作者分成了5类:

* 类型-0  无需定位 无需输入参数
* 类型-1  无需定位 需输入参数
* 类型-2  需定位 无需输入参数
* 类型-3  需定位 需输入参数
* 类型-4  特殊类型

需要特别说明的,类型-4中 有一个特殊的操作 -> 初始化浏览器(WebActionTypeEnum.InitBaseWebBrowser -> buildWebBrowser)
它的特别之处体现在反射方法invokeExcelUiTest中,我们来看

if (step.getActionKeyword().equals(WebActionTypeEnum.InitBaseWebBrowser.getActionKeyword())) {Method method = finalClazz.getMethod(step.getActionKeyword(),String.class);this.baseWebDriver = (WebDriver) method.invoke(instance,step.getParameter()); //这里反射执行方法后,返回了一个对象,被我们强转成了(WebDriver),猜猜它有什么用?
}

明显的是,我们Web测试过程中依赖浏览器的运行,即第一步必须初始化浏览器,在我们代码中,这个浏览器驱动(WebDriver)对象应交由后续其他关键字操作使用,即我们后续每一步操作都将该WebDriver传入方法执行即可:

else {invokeMethod(finalClazz, instance, this.baseWebDriver, step.getActionKeyword(), step.getElementLocateType(), step.getElementLocateValue(), step.getParameter());
}

既然操作被分成了5类,那这5类操作都有哪些呢? 下一步我们来看这5种类型定义把

4、操作类型(关键字)枚举
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.openqa.selenium.Keys;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;/*** * @author Fan QingChuan*/@Getter
@AllArgsConstructor
public enum WebActionTypeEnum {//类型-0  无需定位 无需输入参数ClosePage("关闭页面","closePage",0,"关闭当前所在页面窗口"),CloseBrowser("关闭浏览器浏览器","closeBrowser",0,"关闭整个浏览器(所有页面)"),PageBack("页面返回","pageBack",0,"页面返回"),PageForward("页面前进","pageForward",0,"页面前进"),PageRefresh("刷新页面","pageRefresh",0,"页面刷新"),//类型-1  无需定位 需输入参数OpenUrl("打开网络地址","openUrl",1,"打开网址(输入值-网址)"),NavigateToUrl("跳转网络地址","navigateToUrl",1,"(同一个页面窗口下)跳转至网址  (输入值-网址)"),NavigateToWindows("跳转窗口至目标窗口","navigateToWindows",1,"跳转窗口至目标窗口(输入值-页面标题)"),Pause("暂停N秒","pause",1,"暂停N秒(输入值-秒值 支持小数)"),OpenUrlBlank("Blank方式(新开页面)打开网址","openUrlBlank",1,"Blank方式(新开页面)打开网址 (输入值-网址)"),KeyBoard("输入键盘","keyBoard",1,"输入键盘(输入值 org.openqa.selenium.Keys枚举中的键盘值)  例如"+ Arrays.stream(Keys.values()).map(o -> o.name()).collect(Collectors.toList())),Javascript("执行js脚本","javascript",1,"执行js脚本(输入值-js脚本)"),//类型-2  需定位 无需输入参数Click("点击","click",2,"点击(元素)"),ClickNegatively("消极等待点击","clickNegatively",2,"消极等待点击(元素)"),RightClick("右键单击元素","rightClick",2,"右键单击元素(元素)"),MoveToElement("移动至元素","moveToElement",2,"焦点移动至(元素)"),ClickAndHold("点击并保持按住","clickAndHold",2,"点击并保持按下状态(元素)"),DoubleClick("双击","doubleClick",2,"双击(元素)"),Release("释放元素","release",2,"释放(元素)"),NavigateToFrame("跳转至Frame","navigateToFrame",2,"跳转Frame(元素)"),//类型-3  需定位 需输入参数ClickCustomWait("自定义等待点击","clickCustomWait",3,"自定义等待点击(元素)+ 输入值(时间配置 格式: 等待时间(单位秒):检查频率(单毫秒) 例如: 5:300 等待5秒每300毫秒检查一次)"),ClickNegativelyCustomWait("自定义消极等待点击","clickNegativelyCustomWait",3,"自定义消极等待点击(元素)+ 输入值(时间配置 格式: 等待时间(单位秒):检查频率(单毫秒) 例如: 10:500 等待10秒每500毫秒检查一次)"),InputText("输入文本","inputText",3,"输入文本(元素 + 输入值)"),AssertElementText("断言类型-元素文本","assertElementText",3,"断言元素上文本是否符合预期 (元素 + 输入值-预期文本)"),DragAndDropToPoint("拖拽某元素至目标坐标位置并释放","dragAndDropToPoint",3,"拖拽某元素至目标坐标位置并释放 (元素 +输入值-格式: x坐标:y坐标 例如:300:400 )"),DragAndDropToElement("拖拽某元素至目标元素位置并释放","dragAndDropToElement",3,"拖拽某元素至目标元素位置并释放 (元素 + 输入值 定位方式|定位值   例如: xpath|//*[@id=\"pane-third\"]"),ClickInElementsByText("点击组元素中文本符合预期的元素","clickInElementsByText",3,"点击组元素中文本符合预期的元素-(元素)"),//类型-4  特殊类型 不需要WebDriver(其他类型需要WebDriver) 不需定位 传入需输入参数ThreadSleep("线程休眠","threadSleep",4,"线程休眠(输入值 秒值-支持小数)"),InitBaseWebBrowser("实例化(打开)浏览器","buildWebBrowser",4,"实例化(打开)浏览器(输入值-浏览器类型:版本号)"),;private String name;private String actionKeyword;private Integer type;private String description;public static WebActionTypeEnum getTargetType(String actionKeyword) {for (WebActionTypeEnum value : WebActionTypeEnum.values()) {if (value.getActionKeyword().equalsIgnoreCase(actionKeyword)) {return value;}}return null;}public static List<String> getAllKeyword() {return Arrays.stream(WebActionTypeEnum.values()).map(WebActionTypeEnum::getActionKeyword).collect(Collectors.toList());}public static List<String> getAllDescription() {return Arrays.stream(WebActionTypeEnum.values()).map(WebActionTypeEnum::getDescription).collect(Collectors.toList());}
}

解释下字段吧(虽然有注释,估计还是不明白为什么要这么多字段)
* name 操作简称,主要后续如果需要取名字值时,用这个简称比较合适
* actionKeyword 操作关键编码,核心字段,操作方法、用例关键字都将与该字段一一完全对应,全局通用
* type 操作类型,根据需要进行定义
* description 描述,当然这个描述,就比较细致了(Excel测试用例中会展示),尤其开发者可以开发一些特殊的连续操作,此时就算看简称、操作编码也不太容易懂,随即该字段可提供更细致的解释

5、BasePage? 干嘛用的

接下来,就是大部分测试小伙伴都知道的一种代码模型 => PO设计模型,作者这里准备了Web页面的基类,供喜欢PO写法的小伙伴进行使用,当然此处直接用于反射逻辑中了。

import cn.nhdc.cloud.testscripts.config.ReportLog;
import cn.nhdc.cloud.testscripts.testcase.base.common.TestCommon;
import cn.nhdc.cloud.testscripts.testcase.base.common.WebCommon;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.pagefactory.AjaxElementLocatorFactory;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;import java.net.URL;
import java.time.Duration;/*** * @author Fan QingChuan*/
public class BasePage implements TestCommon, WebCommon {private static final ReportLog reportLog = new ReportLog(BasePage.class);public WebDriver driver;public BasePage() {}public BasePage(WebDriver driver) {this.driver = driver;PageFactory.initElements(new AjaxElementLocatorFactory(driver, WebCommon.WEB_COMMON_TIME_OUT), this);}public BasePage(WebDriver driver, URL url) {this.driver = driver;PageFactory.initElements(new AjaxElementLocatorFactory(driver, WebCommon.WEB_COMMON_TIME_OUT), this);this.driver.get(url.toString());}public BasePage(WebDriver driver, final String title) {this.driver = driver;WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(WebCommon.WEB_COMMON_TIME_OUT));try {boolean flag = wait.until((ExpectedCondition<Boolean>) arg0 -> arg0.getTitle().equals(title));} catch (TimeoutException te) {throw new IllegalStateException("当前不是预期页面,当前页面title是:" + driver.getTitle());}PageFactory.initElements(new AjaxElementLocatorFactory(driver, WebCommon.WEB_COMMON_TIME_OUT), this);}}
6、其他相关
  • 本文相关依赖及版本:
        <?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><properties><selenium.version>4.1.4</selenium.version><junit5.version>5.9.0-M1</junit5.version><testng.version>7.4.0</testng.version><appium.version>8.0.0</appium.version><slf4j.version>1.7.36</slf4j.version><logback.version>1.2.11</logback.version></properties><dependencies><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>${testng.version}</version></dependency><dependency><groupId>com.xiaoleilu</groupId><artifactId>hutool-all</artifactId><version>3.3.2</version></dependency><dependency><groupId>com.relevantcodes</groupId><artifactId>extentreports</artifactId><version>2.41.2</version></dependency><dependency><groupId>com.vimalselvam</groupId><artifactId>testng-extentsreport</artifactId><version>1.3.1</version></dependency><dependency><groupId>com.aventstack</groupId><artifactId>extentreports</artifactId><version>3.0.6</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.11</version></dependency><!-- https://mvnrepository.com/artifact/io.appium/java-client --><dependency><groupId>io.appium</groupId><artifactId>java-client</artifactId><version>${appium.version}</version></dependency><!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>${selenium.version}</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>${logback.version}</version><scope>runtime</scope></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version><scope>runtime</scope></dependency></dependencies></project>
  • 测试用例Excel:
    格式由其他方法进行定义,可自动导出不同测试类型的测试用例,以及参数均通过下拉选值及复制进行快速编写测试用例,(其他文章单独说明)

  • Excel关键字描述:

  • 运行日志:

  • 测试报告:

一种很轻松的Excel关键字方式进行网页Web自动化测试(Java+Selenium+TestNG+Excel)相关推荐

  1. 一种很轻松的Excel关键字方式进行Android端APP自动化测试(Java+Appium+TestNG+Excel)

    说明 本次分享Android端APP自动化测试Excel执行逻辑,整体逻辑与Web端类似,没看过的读者可看这里 =========>> 一种很轻松的Excel关键字方式进行网页Web自动化 ...

  2. excel文本方式区学习笔记

    excel文本方式区学习笔记 import java.io.FileInputStream; import java.io.InputStream; import org.apache.poi.hss ...

  3. JAVA classpath设置方式启动详解:java class名、java -jar

    java classpath设置方式启动详解:java className.java -jar 文章目录 java classpath设置方式启动详解:java className.java -jar ...

  4. wps表格保存html,网页中的数据怎么导入excel表格数据-怎么把网页数据导入到wps表格中...

    如何采集网页中的表格数据到Excel中 1.打开excel表格 2.打开菜单"数据"->入外部数据"->" Web 查询",在" ...

  5. Java实现从Excel文件转换成XML文件(一)

    实现思路:         直接从Excel文件装换成XML文件是可以实现的,这里我采用一个中间装换,也就是先实现excel文件内容放入实现设计好的access数据库文件中,然后再从access中读取 ...

  6. Excel导入多个HTML,网页表格批量导入到excel表格数据-如何将多个网页上的数据批量导入EXCEL...

    怎么将网页中的表格数据导入Excel 看看这个程序 htt==ps://it365.gitlab.io/zh-cn/table-to-excel/?d59568 上面的链点问题,你先复制了,粘浏览器地 ...

  7. html怎么复制到Excel表格里,html表格转excel【如何将网页中的表格快速复制到EXCEL中】...

    在HTML5中如何读取Excel中的数据并且在火狐浏览器中显示啊 js 调用winAPI  html5 没这 具体能容自己修 Untitled Page function importXLS(file ...

  8. excel波士顿矩阵怎么做_Excel2013采用散点图气泡图Powerview三种方案轻松制做波士顿矩阵.docx...

    Excel2013采用散点图气泡图Powerview三种方案轻松制做波士顿矩阵 Excel2013采用散点图气泡图Powerview三种方案轻松制作波士顿矩阵 波士顿矩阵是图形化分析非常好的工具.找遍 ...

  9. 怎么把Excel转换成PDF格式?这三种方法轻松完成转换

    如何将excel表格转换成PDF格式呢?大家在日常的办公过程中也会经常使用这两种文件格式,对文件的格式转换需求我们也是屡见不鲜,其中就有把excel表格转换成PDF格式的需求,遇到这种情况该怎么转换呢 ...

  10. [Java中实现Excel表导入导出]基于easy-poi和EasyExcel两种方式实现

    第一种:基于easy-poi实现Excel导入导出 1.导出Excel表格 第一步:在pom文件中导入依赖 <!--基于easy-poi实现Excel导入导出--><dependen ...

最新文章

  1. 计算机书籍-前谷歌工程师撰写-机器学习实战
  2. 影像组学视频学习笔记(37)-机器学习模型判断脑卒中发病时间(文献报告)、Li‘s have a solution and plan.
  3. office2007的界面
  4. ViewPage+Fragment的使用例子
  5. 一天搞定CSS: 浮动(float)与inline-block的区别--11
  6. 爱奇艺PPS如何登陆账号
  7. windows smb更改端口_SMB协议(使用说明+过程详解+抓包分析)
  8. centos查询是否有安装软件包
  9. 【操作系统】—线程概念和多线程模型
  10. 必须要会的Linux命令,省时省力
  11. Java学习笔记之设计模式(5)装饰者模式
  12. CCNA实验:实验一:思科设备基本配置
  13. NAC配置与管理实战——2
  14. 科学计算机使用的软件,科学计算器软件有哪些?2020好用的科学计算器推荐
  15. 【C语言编程练习】20050. 计算存款利息
  16. HTML实现图片360度循环旋转
  17. 计算机休眠唤醒后 网络受限,Windows 10中的睡眠/唤醒/休眠状态后,Wi-Fi网络消失...
  18. 关于RHCE考试一些事情
  19. 工业无线传感器网络的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  20. 微信企业号用户验证php,身份验证

热门文章

  1. linux设置法语键盘布局,法语键盘布局图
  2. 希望我能帮助你:关于软件开发的建议
  3. “鲲鹏”展翅 宝德“自强”
  4. Excel 2010 VBA 入门 108 个人所得税计算函数
  5. dnf超时空漩涡副本路线流程图_dnf怎么打超时空漩涡 dnf超时空漩涡打法攻略
  6. MongoDB课堂笔记
  7. VML实例-拖动效果
  8. MySQL数据库的完美卸载
  9. S32k144SDK版本——FTM_IC输入捕获
  10. 网站搭建:从零搭建个人网站教程(1)