昨天接到一個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的)