UI自动化之循环遍历元素+多窗口处理

昨天接到一个UI自动化的需求,因为海外环境的平台代码都需要同步更新成跟国内环境的平台代码一致,平台代码的修改可能会影响到所有的表单(其实是已经出现了问题了,有的流程的表单打不开),所以需要点检所有模块下的文档。

而一个环境下,多的有2000+流程,海外环境还有好几个,手工点检的话就。。。

所以用自动化来点检势在必行了。下面上代码


    /**
* 点检所有模块下的旧单和新建文档
*
* @throws InterruptedException
*/
public static void checkBigModule(String configUrl) throws InterruptedException {
try {
// 通过mvn命令行执行指定的领域,moduleNumber会传值进来
moduleNumber = Integer.parseInt(System.getProperty("moduleNumber"));
} catch (Exception e) {
logger.info("未初始化moduleNumber,默认为0");
}
if (moduleNumber == 0) {
// 为0表示没有指定领域,默认所有领域
checkAllBigModuleForm(configUrl);
} else {
// 不为0表示指定了领域,记录点检的领域
checkBigModuleForm(moduleNumber, configUrl);
checkBigModule = (String) bigModuleNumberAndName.get(moduleNumber + "");
}
// 记录点检结果
logger.info("点检领域:" + checkBigModule);
logger.info("旧单点检失败数量:" + checkOldFormFailed);
logger.info("新建文档点检失败数量:" + checkNewDocFailed);
logger.info("旧单点检失败流程:" + formatList(checkOldFormFailedList));
logger.info("新建文档点检失败流程:" + formatList(checkNewDocFailedList));
}

/**
* 点检指定领域模块表单
*
* @param moduleNumber
* @throws InterruptedException
*/
private static void checkBigModuleForm(int moduleNumber, String configUrl) throws InterruptedException {
String moduleName = (String) BaseTest.bigModuleNumberAndName.get(moduleNumber + "");
logger.info("点检领域【" + moduleName + "】下所有模块表单");
// 遍历点击所有领域,确保领域按顺序加载出来
try {
List<WebElement> flowBigModule_s = UILibraryUtils.getElementsByKeywordWhenPresent("所有流程点检", "所有领域");
for (WebElement flowBigModule : flowBigModule_s) {
flowBigModule.click();
Thread.sleep(500);
}
} catch (Exception e) {
logger.info("领域定位失败!");
return;
}
// 点击领域
WebDriverWaitUtils.getElementWhenPresent(By.cssSelector("#flowBigModule>li:nth-child(" + moduleNumber + ")"), 5).click();
// 取到领域下所有父模块:#flowSecondList>ul:nth-child(i)>li
List<WebElement> flowSecondList_s = null;
try {
flowSecondList_s = WebDriverWaitUtils.getElementsWhenPresent(By.cssSelector("#flowSecondList>ul:nth-child(" + moduleNumber + ")>li"), 5);
} catch (Exception e) {
logger.error("领域【" + moduleName + "】下的父模块定位失败!");
Assert.assertEquals(true, false);
}
int j = 0; // 父模块下的子模块ul也是递增定位
for (WebElement flowSecondList : flowSecondList_s) {
j++;
String flowSecondListText;
try {
// 点击每一个父模块
flowSecondListText = flowSecondList.getText();
flowSecondList.click();
logger.info("*****切换父模块【" + flowSecondListText + "】*****");
} catch (Exception e) {
logger.info("父模块操作失败!");
continue;
}
Thread.sleep(500);
// 点检每个子模块下的文档
checkBigModuleForm(moduleNumber, j, flowSecondListText, moduleName, configUrl);
}

}

/**
* 点检所有领域下模块表单
*
* @throws InterruptedException
*/
private static void checkAllBigModuleForm(String configUrl) throws InterruptedException {
logger.info("点检所有领域下所有模块表单");
// 取到所有领域:#flowBigModule>li
List<WebElement> flowBigModule_s;
try {
flowBigModule_s = UILibraryUtils.getElementsByKeywordWhenPresent("所有流程点检", "所有领域");
} catch (Exception e) {
logger.info("领域定位失败!");
screenShotUtils.getScreenShot("所有领域定位失败");
return;
}
// 循环所有领域
int i = 0;// i递增遍历父模块ul标签
flowBigModule:
for (WebElement flowBigModule : flowBigModule_s) {
i++;
String flowBigModuleText;
try {
flowBigModuleText = flowBigModule.getText();
flowBigModule.click();
logger.info("**********切换到领域【" + flowBigModuleText + "】**********");
} catch (Exception e) {
logger.info("领域操作失败!");
continue;
}
// 领域下的父模块ul是递增定位,取到所有父模块:#flowSecondList>ul:nth-child(i)>li
List<WebElement> flowSecondList_s;
try {
flowSecondList_s = WebDriverWaitUtils.getElementsWhenPresent(By.cssSelector("#flowSecondList>ul:nth-child(" + i + ")>li"), 5);
} catch (Exception e) {
logger.info("领域【" + flowBigModuleText + "】下的父模块定位失败!");
screenShotUtils.getScreenShot(flowBigModuleText + i);
continue flowBigModule;
}
int j = 0; // j递增定位子模块ul标签
for (WebElement flowSecondList : flowSecondList_s) {
j++;
String flowSecondListText;
try {
// 点击每一个父模块
flowSecondListText = flowSecondList.getText();
flowSecondList.click();
logger.info("*****点击切换到父模块【" + flowSecondListText + "】*****");
} catch (Exception e) {
logger.info("领域【" + flowBigModuleText + "】下父模块操作失败!");
screenShotUtils.getScreenShot(flowBigModuleText + j);
continue;
}
Thread.sleep(100);
// 点检每个子模块下的文档
checkBigModuleForm(i, j, flowSecondListText, flowBigModuleText, configUrl);
}
}
}

/**
* 点检表单
*
* @param i
* @param j
* @param flowSecondListText
* @throws InterruptedException
*/
private static void checkBigModuleForm(int i, int j, String flowSecondListText, String bigModuleName, String configUrl) throws InterruptedException {
List<WebElement> childModuleElements;
try {
childModuleElements = WebDriverWaitUtils.getElementsWhenPresent(By.cssSelector("#flowList>div:nth-child(" + (i + 1) + ")>ul:nth-child(" + j + ")>li>dl>dd"), 5);
// allFormCount += childModuleElements.size();
} catch (Exception e) {
logger.info("父模块【" + flowSecondListText + "】下的子模块定位失败!");
screenShotUtils.getScreenShot(flowSecondListText + "子模块定位失败");
return;
}
// 遍历所有子模块
String firstHandle = driver.getWindowHandle();
String secondHandle;
childModuleEle_for:
for (WebElement childModuleElement : childModuleElements) {
String childModuleElementText = "";
try {
// 点击子模块
childModuleElementText = childModuleElement.getText();
logger.info("-----切换子模块【" + childModuleElementText + "】-----");
childModuleElement.click();
} catch (Exception e) {
logger.info("父模块【" + flowSecondListText + "】下子模块【" + childModuleElementText + "】操作失败");
screenShotUtils.getScreenShot(flowSecondListText + "-子模块操作失败");
Thread.sleep(100);
continue childModuleEle_for;
}
Thread.sleep(300);
// 进入到子模块窗口,有的模块会跳到其他系统,判断url是否包含:域名+/PortalNew/DocView
Set<String> handles_1 = driver.getWindowHandles(); //所有窗口集合
for (String handle_1 : handles_1) {
// 切换到非当前窗口(即新打开的窗口)
if (!handle_1.equals(firstHandle)) {
// 将当前窗口句柄赋给handle1
secondHandle = handle_1;
driver.switchTo().window(secondHandle);
Thread.sleep(1000);
boolean isBpmForm = driver.getCurrentUrl().contains(configUrl + "/PortalNew/DocView");
if (!isBpmForm) {
// 不包含关闭窗口,continue子模块的循环
notBPMModule++;
logger.info("该模块不是bpm模块");
driver.close();
driver.switchTo().window(firstHandle);
continue childModuleEle_for;
}
// 包含说明是bpm的流程,点击所有文档
clickAllDoc();
// 点检旧单
if (checkOldForm(flowSecondListText, bigModuleName, configUrl, firstHandle, secondHandle, childModuleElementText)) {
// 不包含指定url,失败记录,关闭窗口,continue子模块的循环
logger.info("子模块【" + bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "】下旧单点检失败!");
screenShotUtils.getScreenShot(bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "旧单点检失败");
checkOldFormFailed++;
checkOldFormFailedList.add(childModuleElementText);
}
// 旧单点检结束,点检新建文档
if (checkNewDoc(flowSecondListText, bigModuleName, configUrl, firstHandle, secondHandle, childModuleElementText, formName)) {
// 不包含关闭窗口,continue子模块的循环
logger.error("子模块【" + childModuleElementText + "】下新建文档点检失败!");
screenShotUtils.getScreenShot(bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "新建文档点检失败");
checkNewDocFailed++;
checkNewDocFailedList.add(childModuleElementText);
driver.close();
driver.switchTo().window(secondHandle);
}
                 // 点检完成关闭窗口
     driver.close();
     driver.switchTo().window(firstHandle);
     Thread.sleep(500);
                }
}
}
}

/**
* 点检旧单
*
* @param flowSecondListText
* @param bigModuleName
* @param configUrl
* @param firstHandle
* @param secondHandle
* @param childModuleElementText
* @return
*/
private static boolean checkOldForm(String flowSecondListText, String bigModuleName, String configUrl, String firstHandle, String secondHandle, String childModuleElementText) {
checkOldFormCount++;
// 取到第一个表单的标题
String thirdHandle;
WebElement firstForm;
String firstFormText;
boolean isBpmForm2;
try {
formName = UILibraryUtils.getElementByKeywordWhenPresent("模块页面", "流程名称").getText();
logger.info("流程名称:" + formName);
firstForm = UILibraryUtils.getElementByKeywordWhenPresent("所有流程点检", "第一个单");
firstFormText = firstForm.getText();
firstForm.click();
} catch (Exception e) {
logger.info("该流程【" + formName + "】下没有旧单可以查看");
checkOldFormSkip++;
return false;
}
// 切换到文档页面
Set<String> handle2s = driver.getWindowHandles();
for (String handle_2 : handle2s) {
if (!handle_2.equals(firstHandle) && !handle_2.equals(secondHandle)) {
// 将当前窗口句柄赋给handle1
thirdHandle = handle_2;
// 切换到新打开的窗口
driver.switchTo().window(thirdHandle);
isBpmForm2 = driver.getCurrentUrl().contains(configUrl);
// 判断页面跳转是否正确
if (!isBpmForm2) {
driver.close();
driver.switchTo().window(secondHandle);
return true;
}
// 拿到表单文档主题,表单主题有三种样式
String formTitleText;
try {
formTitleText = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "文档主题1").getText();
} catch (Exception e) {
try {
formTitleText = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "文档主题2").getAttribute("value");
} catch (Exception e2) {
try {
formTitleText = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "文档主题3").getText();
} catch (Exception e3) {
logger.info("子模块【" + bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "】下文档主题获取失败");
screenShotUtils.getScreenShot(bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "文档主题获取失败");
driver.close();
driver.switchTo().window(secondHandle);
return true;
}
}
}
// 断言并关闭第三个窗口
boolean result = assertFormName(firstFormText, formTitleText, childModuleElementText, bigModuleName, flowSecondListText);
driver.close();
driver.switchTo().window(secondHandle);
if (result) {
checkOldFormSuccess++;
} else {
checkOldFormFailed++;
}
}
}
return false;
}

/**
* 点检新建文档
*
* @param flowSecondListText
* @param bigModuleName
* @param configUrl
* @param firstHandle
* @param secondHandle
* @param childModuleElementText
* @param formName
* @return
* @throws InterruptedException
*/
private static boolean checkNewDoc(String flowSecondListText, String bigModuleName, String configUrl, String firstHandle, String secondHandle, String childModuleElementText, String formName) throws InterruptedException {
checkNewDocCount++;
int beforeClickHandleCounts = driver.getWindowHandles().size();
// 没有新建文档按钮的处理
WebElement newDocButton;
try {
newDocButton = UILibraryUtils.getElementByKeywordWhenPresent("模块页面", "新建文档按钮");
if (!Objects.equals(newDocButton.getText(), "新建文档")) {
throw new RuntimeException();
}
newDocButton.click();
Thread.sleep(1000);
} catch (Exception e) {
checkNewDocSkip++;
logger.info("该流程【" + bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "】没有新建文档按钮");
screenShotUtils.getScreenShot(bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "没有新建文档按钮");
return false;
}
// 有新建文档,单个入口or多个入口
Set<String> handle3s = driver.getWindowHandles();// 再次获取所有打开的窗口
if (beforeClickHandleCounts < handle3s.size()) {//点击前的窗口数量小于点击后的窗口数量,说明是单个新建文档入口
if (clickNewDoc(flowSecondListText, bigModuleName, configUrl, firstHandle, secondHandle, childModuleElementText, formName, handle3s))
return true;
} else {
// 有多个新建入口
List<WebElement> newDocWebElements = UILibraryUtils.getElementsByKeywordWhenPresent("模块页面", "新建文档入口集");
// 遍历新建文档多个入口
for (int k = 1; k <= newDocWebElements.size(); k++) {
WebElement newDocWebElement = WebDriverWaitUtils.getElementWhenPresent(By.xpath("//*[@id='btnWrap']/div[2]/ul/li[" + k + "]/a"), 5);
// 拿到当前新建文档的流程名称
String newDocWebElementText = newDocWebElement.getText().replace("新建", "");
newDocWebElement.click();
Thread.sleep(1000);
Set<String> handle4s = driver.getWindowHandles();
if (clickNewDoc(flowSecondListText, bigModuleName, configUrl, firstHandle, secondHandle, childModuleElementText, newDocWebElementText, handle4s))
return true;
newDocButton.click();
}
}
return false;
}

private static boolean clickNewDoc(String flowSecondListText, String bigModuleName, String configUrl, String firstHandle, String secondHandle, String childModuleElementText, String newDocWebElementText, Set<String> handle_s) {
String thirdHandle;
for (String handle : handle_s) {
if (!handle.equals(firstHandle) && !handle.equals(secondHandle)) {
// 将当前窗口句柄赋给handle1
thirdHandle = handle;
// 切换到新打开的窗口
driver.switchTo().window(thirdHandle);
boolean isBpmForm3 = driver.getCurrentUrl().contains(configUrl);
if (!isBpmForm3) {
return true;
}
// 获取文档页面的流程名称
String docPageModuleName;
try {
docPageModuleName = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "流程名称1").getText();
String docPageModuleName2 = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "流程名称11").getText();
String docPageModuleName3 = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "流程名称111").getText();
docPageModuleName = docPageModuleName + docPageModuleName2 + docPageModuleName3;
} catch (Exception e1) {
try {
docPageModuleName = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "流程名称2").getText();
String docPageModuleName2 = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "流程名称22").getText();
String docPageModuleName3 = UILibraryUtils.getElementByKeywordWhenPresent("文档页面", "流程名称222").getText();
docPageModuleName = docPageModuleName + docPageModuleName2 + docPageModuleName3;
} catch (Exception e2) {
logger.info("【" + childModuleElementText + "】文档页面的流程名称获取失败");
screenShotUtils.getScreenShot(bigModuleName + "-" + flowSecondListText + "-" + childModuleElementText + "流程名称获取失败");
return true;
}
}
// 断言并关闭第三个窗口
boolean result = assertFormName(newDocWebElementText, docPageModuleName, childModuleElementText, bigModuleName, flowSecondListText);
if (result) {
checkNewDocSuccess++;
} else {
checkNewDocFailed++;
}
driver.close();
driver.switchTo().window(secondHandle);
}
}
return false;
}

/**
* 点检所有文档动作
*/
public static void clickAllDoc() throws InterruptedException {
// 获取所有的左侧菜单
List<WebElement> webElementList = UILibraryUtils.getElementsByKeywordWhenPresent("模块页面", "左侧菜单");
// 判断title==所有文档
for (WebElement webElement : webElementList) {
String title = webElement.getAttribute("title");
if ("所有文档" == title) {
webElement.click();
Thread.sleep(2000);
return;
}
}
}

/**
* 断言文档名/模块名是否相同
*
* @param formName
* @param docPageModuleName
* @param childModuleEleText
* @param bigModuleName
* @param flowSecondListText
*/
private static Boolean assertFormName(String formName, String docPageModuleName, String childModuleEleText, String bigModuleName, String flowSecondListText) {
if (docPageModuleName.replace("(", "(").replace(")", ")").contains(formName.replace("(", "(").replace(")", ")"))) {
logger.info("点检成功:期望【" + formName + "】,实际【" + docPageModuleName + "】");
return true;
} else {
// 失败记录
logger.info("点检失败:期望【" + formName + "】,实际【" + docPageModuleName + "】");
checkOldFormFailedList.add(bigModuleName + "-" + flowSecondListText + "-" + childModuleEleText);
return false;
}
}

做的过程中碰到几个问题:

 1,各个领域-父模块-子模块的定位,可以看到是需要用循环递增的方式来定位每一个层对应的模块的,这里就需要理清哪层标签是变化的,递增nth-child(i)即可;

 2,存在多种异常场景,比如进入模块的不是所有文档视图、没有旧单、多个新建文档入口,还有跳转到其他系统的,或者其它系统抛单过来的,没有新建文档按钮的,都需要加以处理;

 3,因为这个点检不同于其他的点检,需要跑三个小时,所以对于每一个可能出现的异常都出要做处理,还有窗口的关闭和切换也要处理好,不能影响到后面的循环点检;

 4,还有一个就是这个不同于以往的用例,是一个用例循环跑,不能再在失败时截图了,而是要在人为判断失败的地方时就进行截图。

展示下点检结果

 跳过的是其他流程挂在我们系统的链接;

 失败的模块查看日志基本都是流程表单里的流程名称和新建文档那里的命名不规范导致断言失败,已经提交给BA确认了;剩下几个看日志记录没获取到数据,看失败截图是页面白屏,应该是网络问题没有加载出来页面导致的,人为验证下是OK的)

 

posted @ 2021-04-01 11:04  lynnk1ng  阅读(808)  评论(0编辑  收藏  举报