一种很轻松的Excel关键字方式进行网页Web自动化测试(Java+Selenium+TestNG+Excel)
前言
最初目的,為了在整體測試平臺框架下,測試人員可便捷維護(hù)用例,無需啟動服務(wù)、編譯程序,提供一套Excel填寫測試用例并能快速執(zhí)行的解決方案。后續(xù)擴(kuò)展了不同類型的測試用例,本文則對Web類型的測試用例Excel方式執(zhí)行測試邏輯進(jìn)行介紹。
開發(fā)過程中,本想著網(wǎng)上CV大法搞下來修修改改,結(jié)果發(fā)現(xiàn)沒一個清晰完整的分享,于是,結(jié)合自身理解,以及測試框架設(shè)計,形成了目前的一個版本,同時分享給測試小伙伴,希望能對各位測試伙伴能有所幫助。
其他說明
- 開發(fā)語言Java,采用SpringBoot開發(fā)框架(版本2.1.18.RELEASE)(文章尾部會貼出各依賴版本POM文件),測試底層調(diào)用Selenium框架方法,以及測試注解引用TestNG測試框架,本文所述功能僅為整體測試框架中一個小功能點,所以分享并沒有全部貼出(不是作者不想,作者自研,完全有權(quán)限分享,只是內(nèi)容太多,沒整理好,就暫時不發(fā)出來)
- 所有代碼均有注釋,代碼基本不再細(xì)致說明,以及一些額外相關(guān)的代碼沒貼出來(主要是放這里顯得雜亂,有機(jī)會單寫文章說明整體架構(gòu)邏輯及源碼,有興趣留言交流)
- 本文主要從作者主觀邏輯、代碼進(jìn)行介紹,可能部分方法、部分邏輯并未介紹到位,作者寫文純屬鍛煉下文檔(好久沒寫文檔了),不完善的地方請各位玩家理解,歡迎留言咨詢,后續(xù)看源碼通過什么形式發(fā)出來
- 文章作者:@隨心自然fqc , 轉(zhuǎn)載時,請注明來源,注明作者,這是對文章作者的尊重,也是對知識的尊重。
整體圖解
先看看整體邏輯,看完整體代碼邏輯后,建議再回頭看看這張圖 ><
主要內(nèi)容
1、測試用例調(diào)用方式
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執(zhí)行方式 自動化測試demo <br>* Method 1 : {@link #testExcelExcute} 正向邏輯執(zhí)行 <br>* Method 2 : {@link #testExcelInvokeExcute} 反射邏輯執(zhí)行 <br>** ps: 以上2種方式 原理不同 但執(zhí)行效果類似 具體選擇哪種取決于調(diào)用者意愿** @author Fan QingChuan*/ @Test(description = "Excel方式自動化測試示例-正向邏輯&反射執(zhí)行邏輯") @Listeners(value = ExtentTestNGIReporterListener.class) public class ExcelWebTestDemo extends WebTestBase {@Test(description = "正向邏輯: 操作方法封裝在 WebTestBase 中, 測試用例中可直接調(diào)用 解析Excel -> caseSteps 根據(jù)遍歷操作編碼switch執(zhí)行各項操作")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 并實例化 再根據(jù)操作編碼+分類枚舉 獲取對應(yīng)方法 并反射(invoke)執(zhí)行測試")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;/*** 團(tuán)隊code */@NotBlank(message = "teamCode 團(tuán)隊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;/*** 關(guān)鍵字(操作)編碼*/@NotBlank(message = "actionKeyword 關(guān)鍵字(操作)編碼不能為空",groups = {Add.class, Update.class})private String actionKeyword;/*** 元素定位方式(類型)*/private String elementLocateType;/*** 元素定位信息*/private String elementLocateValue;/*** 輸入值*/private String parameter;} }- 如代碼所示,將測試用例各字段都考慮進(jìn)去,如需擴(kuò)展API測試,可在放進(jìn)該對象
- 字段上各校驗注解,是用例管理CRUD接口中需要的,與本次分享毫無關(guān)系(例如 @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)進(jìn)行分組,將所有步驟封裝到對應(yīng)用例編碼下 <br>* 與測試用例-測試步驟數(shù)據(jù)結(jié)構(gòu)對應(yīng), {@link TestCaseVo.UiCaseStepVo} 一對多關(guān)系** @param filePath 文件絕對路徑* @param sheetName 需解析的sheetName 默認(rèn)Sheet1* @param headerRowNumber 解析case的標(biāo)題行數(shù) 默認(rèn)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(" ======== >> 解析到[{}]條數(shù)據(jù)",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 未設(shè)置休眠秒數(shù) 即輸入值(秒值)不能為空 且 值需大于0");}long second = Long.parseLong(seconds);try {Thread.sleep(second * 1000);} catch (InterruptedException e) {e.printStackTrace();}} }4、執(zhí)行測試邏輯
我們得到了List<TestCaseVo> caseVoList,下一步自然就是執(zhí)行測試了,如果你是測試小白,那作者希望你能仔細(xì)讀讀以下代碼。
其中關(guān)鍵點:
- 區(qū)分 excuteExcelUiTest 與 invokeExcelUiTest 的調(diào)用邏輯區(qū)別
- 不同關(guān)鍵字操作方法區(qū)別(以及對應(yīng)在測試活動中的區(qū)別)
通過此類 你將收獲到一絲絲靈感,方便你進(jìn)行自己的創(chuàng)作
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;/*** 執(zhí)行測試(Excel UI自動化測試-正常邏輯)<br>* 正常邏輯: 按測試用例循環(huán)遍歷測試,在一個測試用例中,先將用例下所有測試步驟按步驟序號進(jìn)行排序(升序), <br>* 再按升序進(jìn)行測試, 每個步驟按操作編碼進(jìn)行識別調(diào)用對應(yīng)操作方法進(jìn)行測試,* 調(diào)用方式: switch(actionType) case ...actionMethod 將所有操作類型分為4種調(diào)用方式 詳情請看{@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("測試用例編號[{}] 步驟序號:[{}],測試步驟描述 [{}],操作編碼:[{}],元素定位方式[{}],定位值[{}],輸入?yún)?shù)值[{}]",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;//此處省略類似代碼(其他操作關(guān)鍵字方式)//type-1case OpenUrl:if (step.getParameter() == null) {throw new IllegalArgumentException("openUrl 輸入值(網(wǎng)址)不能為空 步驟序號:" +step.getSort() +"用例編號:"+caseVo.getCaseCode());}openUrl(step.getParameter());break;//此處省略類似代碼(其他操作關(guān)鍵字方式)//type-2case Click:if (step.getElementLocateType() == null || step.getElementLocateValue() == null) {throw new IllegalArgumentException("click 元素定位不能為空 步驟序號:" +step.getSort() +"用例編號:"+caseVo.getCaseCode());}click(step.getElementLocateType(), step.getElementLocateValue());break;//此處省略類似代碼(其他操作關(guān)鍵字方式)//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;//此處省略類似代碼(其他操作關(guān)鍵字方式)//type-4case InitBaseWebBrowser:initBaseBrowser(step.getParameter());break;case ThreadSleep:threadSleep(step.getParameter());break;default:throw new IllegalArgumentException("操作編碼不合法! 或 不存在該操作 如有必要請聯(lián)系我增加 > <");}});});}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生產(chǎn)新的driverbaseWebDriver = buildWebBrowser(browserInfo);}/*** 執(zhí)行測試(Excel UI自動化測試-反射邏輯)<br>* 反射邏輯: 按測試用例循環(huán)遍歷測試,在一個測試用例中,先將用例下所有測試步驟按步驟序號進(jìn)行排序(升序), <br>* 再按升序進(jìn)行測試, 每個步驟按操作編碼進(jìn)行識別反射調(diào)用對應(yīng)操作方法進(jìn)行測試, <br>* 調(diào)用方式: switch(actionType) case ...invoke(actionMethod) 將所有操作類型分為4種調(diào)用方式 詳情請看{@link WebActionTypeEnum} <br>* 與正向邏輯的區(qū)別在于 反射邏輯需實例化對應(yīng)基類- {@link BasePage} 并在每個測試步驟傳入上下文的 WebDriver,* 另需注意buildWebBrowser(初始化打開瀏覽器)操作屬特殊情況,不能傳入WebDriver,因為在正常情況下,buildWebBrowser前WebDriver為null沒初始化,* 當(dāng)然就不存在上文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("測試用例編號[{}] 步驟序號:[{}],測試步驟描述 [{}],操作編碼:[{}],元素定位方式[{}],定位值[{}],輸入?yún)?shù)值[{}]",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中,調(diào)用反射執(zhí)行invokeMethod(...),這些具體操作方法在哪里? * 正向邏輯-excuteExcelUiTest中,調(diào)用WebTestBase.closePage() ,而closePage()中又調(diào)用了個closePage(baseWebDriver)? WTF?答案就是
- 對于公共操作,作者都將其進(jìn)行了集中抽取并分層管理,類似最開始圖解中 WebCommon -> TestCommon ,包括作者沒貼出來的AndroidCommon -> AppCommon -> WebCommon -> TestCommon
每層通用的均各自層級實現(xiàn),調(diào)用者只需要implements 對應(yīng)抽象類即可調(diào)用。
PS: 是不是你就懂了前文 TestCommon中為什么有個方法 threadSleep(String seconds)了,除了可提供給用戶Excel操作關(guān)鍵字,也可以視為所有測試活動都可以用這個方法(線程休眠)
那么回到主題,WebCommon長什么樣子呢? 請往下繼續(xù)
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("不存在運(yùn)行的瀏覽器 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();}//此處省略類似代碼(其他操作關(guān)鍵字具體行為)/*** 初始化瀏覽器并返回給調(diào)用者* @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]);}也許聰明的你已經(jīng)看出來了,所謂關(guān)鍵字操作,被作者分成了5類:
* 類型-0 無需定位 無需輸入?yún)?shù) * 類型-1 無需定位 需輸入?yún)?shù) * 類型-2 需定位 無需輸入?yún)?shù) * 類型-3 需定位 需輸入?yún)?shù) * 類型-4 特殊類型需要特別說明的,類型-4中 有一個特殊的操作 -> 初始化瀏覽器(WebActionTypeEnum.InitBaseWebBrowser -> buildWebBrowser)
它的特別之處體現(xiàn)在反射方法invokeExcelUiTest中,我們來看
明顯的是,我們Web測試過程中依賴瀏覽器的運(yùn)行,即第一步必須初始化瀏覽器,在我們代碼中,這個瀏覽器驅(qū)動(WebDriver)對象應(yīng)交由后續(xù)其他關(guān)鍵字操作使用,即我們后續(xù)每一步操作都將該WebDriver傳入方法執(zhí)行即可:
else {invokeMethod(finalClazz, instance, this.baseWebDriver, step.getActionKeyword(), step.getElementLocateType(), step.getElementLocateValue(), step.getParameter()); }既然操作被分成了5類,那這5類操作都有哪些呢? 下一步我們來看這5種類型定義把
4、操作類型(關(guān)鍵字)枚舉
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 無需定位 無需輸入?yún)?shù)ClosePage("關(guān)閉頁面","closePage",0,"關(guān)閉當(dāng)前所在頁面窗口"),CloseBrowser("關(guān)閉瀏覽器瀏覽器","closeBrowser",0,"關(guān)閉整個瀏覽器(所有頁面)"),PageBack("頁面返回","pageBack",0,"頁面返回"),PageForward("頁面前進(jìn)","pageForward",0,"頁面前進(jìn)"),PageRefresh("刷新頁面","pageRefresh",0,"頁面刷新"),//類型-1 無需定位 需輸入?yún)?shù)OpenUrl("打開網(wǎng)絡(luò)地址","openUrl",1,"打開網(wǎng)址(輸入值-網(wǎng)址)"),NavigateToUrl("跳轉(zhuǎn)網(wǎng)絡(luò)地址","navigateToUrl",1,"(同一個頁面窗口下)跳轉(zhuǎn)至網(wǎng)址 (輸入值-網(wǎng)址)"),NavigateToWindows("跳轉(zhuǎn)窗口至目標(biāo)窗口","navigateToWindows",1,"跳轉(zhuǎn)窗口至目標(biāo)窗口(輸入值-頁面標(biāo)題)"),Pause("暫停N秒","pause",1,"暫停N秒(輸入值-秒值 支持小數(shù))"),OpenUrlBlank("Blank方式(新開頁面)打開網(wǎng)址","openUrlBlank",1,"Blank方式(新開頁面)打開網(wǎng)址 (輸入值-網(wǎng)址)"),KeyBoard("輸入鍵盤","keyBoard",1,"輸入鍵盤(輸入值 org.openqa.selenium.Keys枚舉中的鍵盤值) 例如"+ Arrays.stream(Keys.values()).map(o -> o.name()).collect(Collectors.toList())),Javascript("執(zhí)行js腳本","javascript",1,"執(zhí)行js腳本(輸入值-js腳本)"),//類型-2 需定位 無需輸入?yún)?shù)Click("點擊","click",2,"點擊(元素)"),ClickNegatively("消極等待點擊","clickNegatively",2,"消極等待點擊(元素)"),RightClick("右鍵單擊元素","rightClick",2,"右鍵單擊元素(元素)"),MoveToElement("移動至元素","moveToElement",2,"焦點移動至(元素)"),ClickAndHold("點擊并保持按住","clickAndHold",2,"點擊并保持按下狀態(tài)(元素)"),DoubleClick("雙擊","doubleClick",2,"雙擊(元素)"),Release("釋放元素","release",2,"釋放(元素)"),NavigateToFrame("跳轉(zhuǎn)至Frame","navigateToFrame",2,"跳轉(zhuǎn)Frame(元素)"),//類型-3 需定位 需輸入?yún)?shù)ClickCustomWait("自定義等待點擊","clickCustomWait",3,"自定義等待點擊(元素)+ 輸入值(時間配置 格式: 等待時間(單位秒):檢查頻率(單毫秒) 例如: 5:300 等待5秒每300毫秒檢查一次)"),ClickNegativelyCustomWait("自定義消極等待點擊","clickNegativelyCustomWait",3,"自定義消極等待點擊(元素)+ 輸入值(時間配置 格式: 等待時間(單位秒):檢查頻率(單毫秒) 例如: 10:500 等待10秒每500毫秒檢查一次)"),InputText("輸入文本","inputText",3,"輸入文本(元素 + 輸入值)"),AssertElementText("斷言類型-元素文本","assertElementText",3,"斷言元素上文本是否符合預(yù)期 (元素 + 輸入值-預(yù)期文本)"),DragAndDropToPoint("拖拽某元素至目標(biāo)坐標(biāo)位置并釋放","dragAndDropToPoint",3,"拖拽某元素至目標(biāo)坐標(biāo)位置并釋放 (元素 +輸入值-格式: x坐標(biāo):y坐標(biāo) 例如:300:400 )"),DragAndDropToElement("拖拽某元素至目標(biāo)元素位置并釋放","dragAndDropToElement",3,"拖拽某元素至目標(biāo)元素位置并釋放 (元素 + 輸入值 定位方式|定位值 例如: xpath|//*[@id=\"pane-third\"]"),ClickInElementsByText("點擊組元素中文本符合預(yù)期的元素","clickInElementsByText",3,"點擊組元素中文本符合預(yù)期的元素-(元素)"),//類型-4 特殊類型 不需要WebDriver(其他類型需要WebDriver) 不需定位 傳入需輸入?yún)?shù)ThreadSleep("線程休眠","threadSleep",4,"線程休眠(輸入值 秒值-支持小數(shù))"),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 操作簡稱,主要后續(xù)如果需要取名字值時,用這個簡稱比較合適
* actionKeyword 操作關(guān)鍵編碼,核心字段,操作方法、用例關(guān)鍵字都將與該字段一一完全對應(yīng),全局通用
* type 操作類型,根據(jù)需要進(jìn)行定義
* description 描述,當(dāng)然這個描述,就比較細(xì)致了(Excel測試用例中會展示),尤其開發(fā)者可以開發(fā)一些特殊的連續(xù)操作,此時就算看簡稱、操作編碼也不太容易懂,隨即該字段可提供更細(xì)致的解釋
5、BasePage? 干嘛用的
接下來,就是大部分測試小伙伴都知道的一種代碼模型 => PO設(shè)計模型,作者這里準(zhǔn)備了Web頁面的基類,供喜歡PO寫法的小伙伴進(jìn)行使用,當(dāng)然此處直接用于反射邏輯中了。
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("當(dāng)前不是預(yù)期頁面,當(dāng)前頁面title是:" + driver.getTitle());}PageFactory.initElements(new AjaxElementLocatorFactory(driver, WebCommon.WEB_COMMON_TIME_OUT), this);}}6、其他相關(guān)
- 本文相關(guān)依賴及版本:
-
測試用例Excel:
格式由其他方法進(jìn)行定義,可自動導(dǎo)出不同測試類型的測試用例,以及參數(shù)均通過下拉選值及復(fù)制進(jìn)行快速編寫測試用例,(其他文章單獨(dú)說明)
-
Excel關(guān)鍵字描述:
-
運(yùn)行日志:
-
測試報告:
總結(jié)
以上是生活随笔為你收集整理的一种很轻松的Excel关键字方式进行网页Web自动化测试(Java+Selenium+TestNG+Excel)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多系统启动菜单的修复EasyBoot
- 下一篇: 流媒体直播点播系统方案设计