提交 4234e666 authored 作者: 黄承天's avatar 黄承天

内部逻辑优化 增加对URL是否可访问的检查

上级 9a89229b
package com.zjty.autotest.pojo.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.openqa.selenium.WebElement;
/**
* <p>Description : autotest
* <p>Date : 2020/4/9 14:48
* <p>@author : C
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ElementEntry {
private ElementFeature feature;
private WebElement element;
private WebElement frame;
}
......@@ -5,6 +5,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
/**
* @author C
......@@ -13,8 +14,7 @@ import org.openqa.selenium.Point;
@AllArgsConstructor
@NoArgsConstructor
public class ElementFeature {
private Point location;
private Dimension size;
private String tag;
private String text;
private Rectangle rect;
}
......@@ -8,25 +8,29 @@ import com.zjty.autotest.common.action.LabelType;
import com.zjty.autotest.pojo.report.ElementDetail;
import com.zjty.autotest.pojo.report.Measure;
import com.zjty.autotest.pojo.report.Report;
import com.zjty.autotest.pojo.test.ActResult;
import com.zjty.autotest.pojo.test.ElementFeature;
import com.zjty.autotest.pojo.test.Input;
import com.zjty.autotest.pojo.test.Project;
import com.zjty.autotest.pojo.test.*;
import com.zjty.autotest.util.FileUtil;
import com.zjty.autotest.util.WebDriverUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.jsoup.nodes.Element;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.interactions.MoveTargetOutOfBoundsException;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Sleeper;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import sun.net.www.protocol.file.FileURLConnection;
import sun.net.www.protocol.ftp.FtpURLConnection;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Duration;
import java.util.*;
import java.util.NoSuchElementException;
import java.util.function.Predicate;
import java.util.stream.Collectors;
......@@ -41,7 +45,7 @@ import static java.util.Objects.nonNull;
@Service
public class SeleniumExecutor {
@Value("${selenium.executor.wait-after-click}")
@Value("${selenium.executor.wait-time}")
private Long waitTime;
@Value("${screenshot-path}")
......@@ -57,11 +61,14 @@ public class SeleniumExecutor {
private Set<ElementFeature> currentHistoryFeatures = Sets.newHashSet();
private Queue<String> windowQueue = Queues.newLinkedBlockingDeque();
private Queue<String> urlQueue = Queues.newLinkedBlockingQueue();
private Queue<String> urlQueue = Queues.newLinkedBlockingDeque();
private Queue<ElementEntry> elementQueue = Queues.newLinkedBlockingQueue();
private WebDriverWait webDriverWait;
private Actions actions;
private Map<ElementFeature, WebElement> elements = Maps.newHashMap();
/**
* 当前进行的窗口页
*/
......@@ -72,21 +79,16 @@ public class SeleniumExecutor {
*/
private String currentUrl;
/**
* 当前测试url的源码
*/
private String currentSource;
public Report execute(Project project) {
driver = WebDriverUtil.getWebDriver(project.getBrowser());
driver.manage().window().maximize();
webDriverWait = new WebDriverWait(driver, waitTime);
actions = new Actions(driver);
inputs = project.getInputs();
List<Measure> measures = Lists.newArrayList();
String os = "";
String os = Strings.EMPTY;
currentUrl = project.getUrl();
currentWindow = driver.getWindowHandle();
while (nonNull(currentWindow)) {
driver.switchTo().window(currentWindow);
while (nonNull(currentUrl)) {
Measure measure = testUrl(currentUrl);
if (nonNull(measure)) {
......@@ -94,10 +96,6 @@ public class SeleniumExecutor {
}
currentUrl = urlQueue.poll();
}
currentWindow = windowQueue.poll();
driver.close();
checkWindow();
}
driver.quit();
return new Report(
null,
......@@ -112,19 +110,18 @@ public class SeleniumExecutor {
private Measure testUrl(String currentUrl) {
Measure measure = new Measure();
measure.setUrl(currentUrl);
measure.setResponseTime(-1);
//如果该网页是未曾进入过的网页则加入历史记录并进行遍历测试
if (!historyUrls.contains(currentUrl)) {
historyUrls.add(driver.getCurrentUrl());
historyUrls.add(currentUrl);
log.info("当前URL:{} ", currentUrl);
boolean connectAble = checkConnect(currentUrl);
if (connectAble) {
//打开网页 记录响应时间
long startTime = System.currentTimeMillis();
driver.get(currentUrl);
currentSource = driver.getPageSource();
long endTime = System.currentTimeMillis();
Long responseTime = endTime - startTime;
sleep();
log.info("当前URL:{} 响应时间 {} ms 开始进行遍历...", currentUrl, responseTime);
long responseTime = load(currentUrl);
measure.setResponseTime((int) responseTime);
log.info("响应时间 {} ms 开始进行遍历...", responseTime);
log.info("剩余URL队列:{}", urlQueue);
log.info("正在获取当前网页所有元素...");
StringBuilder message = new StringBuilder();
Set<String> messages = Sets.newHashSet();
String error = checkJsError();
......@@ -132,185 +129,174 @@ public class SeleniumExecutor {
messages.add(error);
}
//获取当前网页的所有元素并放入元素队列
elements.clear();
elements = getAllElements(driver);
log.info("获取完毕...共{}个元素...", elements.size());
log.info("获取完毕...共{}个元素...", elementQueue.size());
//当前页面的历史元素记录重置
currentHistoryFeatures.clear();
//开始遍历操作元素队列中的元素 返回各个元素测试信息
List<ElementDetail> elementDetails = Lists.newArrayList();
traversal(elementDetails);
elementDetails = elementDetails.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
List<ElementDetail> elementDetails = traversal();
measure.setElementDetails(elementDetails);
//如果全部元素通过则是通过
Boolean success = elementDetails.stream()
.filter(Objects::nonNull)
.allMatch(ElementDetail::getSuccess);
//如果出错 则提供截图
String screenshot = null;
//如果未通过 则提供截图
if (!success) {
String screenshotName = alterUrlName(currentUrl) + ".png";
screenshot(screenshotName);
screenshot = String.format("/%s", screenshotName);
measure.setScreenshot(String.format("/%s", screenshotName));
}
measure.setSuccess(success);
//总合信息
messages.addAll(elementDetails.stream()
.filter(Objects::nonNull)
.map(ElementDetail::getMessage)
.filter(Objects::nonNull)
.filter(Strings::isNotEmpty)
.collect(Collectors.toList()));
for (String msg : messages) {
message.append(msg);
message.append(";");
}
measure.setMessage(message.toString());
log.info("遍历完毕...");
measure = new Measure(
currentUrl,
success,
responseTime.intValue(),
message.toString(),
screenshot,
elementDetails
);
} else {
measure.setMessage("无法访问的URL;");
measure.setElementDetails(Lists.newArrayList());
}
}
return measure;
}
private void traversal(List<ElementDetail> elementDetails) {
for (ElementFeature feature : elements.keySet()) {
WebElement element = elements.get(feature);
if (!currentHistoryFeatures.contains(feature) && isInputAble(element)) {
ActResult act = act(feature, element, INPUT);
ElementDetail elementDetail = act.getElementDetail();
private List<ElementDetail> traversal() {
List<ElementDetail> elementDetails = Lists.newArrayList();
while (!elementQueue.isEmpty()) {
ElementEntry elementEntry = elementQueue.poll();
ElementFeature feature = elementEntry.getFeature();
if (!currentHistoryFeatures.contains(feature)) {
if (isInput(feature)) {
ElementDetail elementDetail = act(elementEntry, INPUT);
if (!isSkipped(elementDetail)) {
elementDetails.add(elementDetail);
}
}
for (ElementFeature feature : elements.keySet()) {
WebElement element = elements.get(feature);
if (!currentHistoryFeatures.contains(feature) && isClickAble(element)) {
ActResult act = act(feature, element, CLICK);
ElementDetail elementDetail = act.getElementDetail();
} else {
ElementDetail elementDetail = act(elementEntry, CLICK);
if (!isSkipped(elementDetail)) {
elementDetails.add(elementDetail);
if (act.getStaleness()) {
reload();
traversal(elementDetails);
return;
}
}
}
}
return elementDetails;
}
private ActResult act(ElementFeature feature, WebElement element, String actType) {
String type = String.format("<%s>%s", element.getTagName(), element.getText());
String message = null;
private ElementDetail act(ElementEntry elementEntry, String actType) {
WebElement element = elementEntry.getElement();
ElementFeature feature = elementEntry.getFeature();
String type = String.format("<%s>%s", feature.getTag(), feature.getText());
String message = Strings.EMPTY;
long responseTime = -1L;
boolean success = false;
boolean staleness = false;
try {
if (isEnabled(element)) {
log.info("元素 ----- tag:[{}] ------ text:[{}] ---- feature:[{}] ------ notStale:{}", element.getTagName(), element.getText(), feature, !ExpectedConditions.stalenessOf(element).apply(driver));
if (nonNull(elementEntry.getFrame())) {
driver.switchTo().frame(elementEntry.getFrame());
}
if (isEnabled(element) && isDisPlayed(element)) {
log.info("元素 ----- feature:[{}]", feature);
long startTime = System.currentTimeMillis();
if (Objects.equals(actType, INPUT)) {
log.info("Input操作");
log.info("input操作");
if (!inputs.isEmpty()) {
Input input = inputs.remove(0);
element.sendKeys(input.getValue());
}
success = true;
} else if (Objects.equals(actType, CLICK)) {
log.info("Click操作");
element.click();
sleep();
log.info("click操作");
actions.moveToElement(element).click().perform();
success = true;
}
long endTime = System.currentTimeMillis();
//响应时间
responseTime = endTime - startTime;
//检查窗口切换
checkWindow();
//检查页面变化 有变化则重新读取URL和元素
boolean urlChanged = checkPage();
boolean elementStaled = ExpectedConditions.stalenessOf(element).apply(driver);
boolean pageChanged = urlChanged || elementStaled;
if (pageChanged) {
log.warn("page changed.reload...");
load(currentUrl);
log.info("reload complete.");
} else {
//页面无变化
//检查警告窗
Alert alert = ExpectedConditions.alertIsPresent().apply(driver);
String jsMsg = checkJsError();
Boolean locationMatch = checkLocation(element);
if (!locationMatch) {
message = "布局错误";
success = false;
log.error("error:布局错误");
}
if (nonNull(alert)) {
message = "error:出现警告窗:" + alert.getText();
message = "出现警告窗:" + alert.getText();
alert.accept();
success = false;
log.error("error:出现警告窗:{}", alert.getText());
} else if (nonNull(jsMsg)) {
log.error("出现警告窗:{}", alert.getText());
} else {
//通过读取浏览器日志来检查js异常
String jsMsg = checkJsError();
if (nonNull(jsMsg)) {
message = jsMsg;
success = false;
log.error("error:出现js异常:{}", jsMsg);
log.error("出现js异常:{}", jsMsg);
}
}
}
staleness = checkPage();
}
} catch (ElementNotInteractableException e) {
log.error("error:可操作范围之外的元素");
return new ActResult(false, null);
} catch (StaleElementReferenceException e) {
log.error("元素过期");
return new ActResult(false, null);
} catch (TimeoutException e) {
message = "页面超时";
success = false;
log.error("error:页面超时");
} catch (NoSuchElementException e) {
message = "依赖缺失";
message = "元素超时";
success = false;
log.error("error:依赖缺失");
} catch (WebDriverException e) {
message = "无法访问的地址";
log.error("元素超时");
} catch (MoveTargetOutOfBoundsException e) {
message = "元素布局错误";
success = false;
log.error("error:无法访问的地址");
log.error("元素布局错误");
} catch (Exception e) {
message = "出现预料之外的错误";
success = false;
e.printStackTrace();
log.error("error:" + e);
}
if (nonNull(elementEntry.getFrame())) {
driver.switchTo().defaultContent();
}
historyFeatures.add(feature);
currentHistoryFeatures.add(feature);
return new ActResult(
staleness,
new ElementDetail(
type,
(int) responseTime,
success,
message
)
);
return new ElementDetail(type, (int) responseTime, success, message);
}
private Boolean checkPage(){
boolean staleness = false;
private Boolean isSkipped(ElementDetail elementDetail) {
return Objects.equals(elementDetail.getResponseTime(), -1) && Objects.equals(elementDetail.getMessage(), Strings.EMPTY);
}
private Boolean checkPage() {
boolean urlChanged = checkURl();
boolean sourceChanged = checkSource();
boolean pageChanged = false;
if (urlChanged) {
boolean newPage = !historyUrls.contains(driver.getCurrentUrl())
&& !Sets.newHashSet(urlQueue).contains(driver.getCurrentUrl())
&& sourceChanged;
log.info("URL changed.");
pageChanged = true;
String practiseCurrentUrl = driver.getCurrentUrl();
boolean newPage = !historyUrls.contains(practiseCurrentUrl)
&& !Sets.newHashSet(urlQueue).contains(practiseCurrentUrl);
if (newPage) {
log.info("检测到新url:{} 加入队列 ", driver.getCurrentUrl());
log.info("检测到新URL:{} 加入队列 ", driver.getCurrentUrl());
urlQueue.add(driver.getCurrentUrl());
urlQueue = queueDuplicateRemoval(urlQueue);
staleness = true;
}
reload();
}
return staleness;
return pageChanged;
}
private Boolean checkURl() {
String practiseUrl = driver.getCurrentUrl();
String practiseUrl = webDriverWait.until(WebDriver::getCurrentUrl);
return !Objects.equals(currentUrl, practiseUrl);
}
private Boolean checkSource() {
String practise = driver.getPageSource();
return !Objects.equals(practise, currentSource);
}
private Queue<String> queueDuplicateRemoval(Queue<String> queue) {
HashSet<String> set = Sets.newHashSet(queue);
return Queues.newLinkedBlockingDeque(set);
......@@ -333,16 +319,36 @@ public class SeleniumExecutor {
}
private void checkWindow() {
if (!windowQueue.isEmpty()) {
windowQueue = Queues.newLinkedBlockingQueue(driver.getWindowHandles());
Set<String> practise = webDriverWait.until(WebDriver::getWindowHandles);
boolean windowChanged = practise.size() >= 2;
if (windowChanged) {
for (String newWindow : practise) {
if (!Objects.equals(newWindow, currentWindow)) {
driver.switchTo().window(newWindow);
log.info("here comes a new window.");
String practiseCurrentUrl = driver.getCurrentUrl();
boolean newPage = !historyUrls.contains(practiseCurrentUrl)
&& !Sets.newHashSet(urlQueue).contains(practiseCurrentUrl);
if (newPage) {
log.info("检测到新URL:{} 加入队列 ", driver.getCurrentUrl());
urlQueue.add(driver.getCurrentUrl());
urlQueue = queueDuplicateRemoval(urlQueue);
}
driver.close();
driver.switchTo().window(currentWindow);
break;
}
}
}
}
private void reload() {
private Long load(String currentUrl) {
long startTime = System.currentTimeMillis();
driver.get(currentUrl);
sleep();
currentSource = driver.getPageSource();
elements = getAllElements(driver);
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
getAllElements();
return responseTime;
}
@SuppressWarnings("unused")
......@@ -354,59 +360,53 @@ public class SeleniumExecutor {
}
}
private Map<ElementFeature, WebElement> getAllElements(WebDriver driver) {
List<WebElement> webElements = Lists.newArrayList();
driver.findElements(By.xpath("*"))
.forEach(element -> getSubElements(element, webElements));
Map<ElementFeature, WebElement> result = Maps.newHashMap();
private void getAllElements() {
elementQueue.clear();
// List<WebElement> frames = webDriverWait.until(webDriver -> webDriver.findElements(By.tagName("iframe")));
// List<WebElement> webElements = webDriverWait.until(webDriver -> driver.findElements(By.xpath("//*")).stream().filter(element -> element.findElements(By.xpath("*")).isEmpty()).collect(Collectors.toList()));
List<WebElement> webElements = webDriverWait.until(webDriver -> driver.findElements(By.xpath("//*")));
for (WebElement webElement : webElements) {
result.put(toFeature(webElement), webElement);
ElementFeature feature = toFeature(webElement);
ElementEntry entry = new ElementEntry(feature, webElement, null);
elementQueue.add(entry);
}
return result;
// if (!frames.isEmpty()) {
// for (WebElement frame : frames) {
// List<WebElement> webElementsInFrame = webDriverWait.until(webDriver -> frame.findElements(By.xpath("//*")).stream().filter(element -> element.findElements(By.xpath("*")).isEmpty()).collect(Collectors.toList()));
// Queue<ElementEntry> queue = Queues.newLinkedBlockingQueue();
// for (WebElement webElement : webElementsInFrame) {
// ElementFeature feature = toFeature(webElement);
// ElementEntry entry = new ElementEntry(feature, webElement, frame);
// queue.add(entry);
// }
// }
// }
}
private void getSubElements(WebElement element, List<WebElement> elements) {
List<WebElement> results = element.findElements(By.xpath("*"));
if (results.isEmpty()) {
elements.add(element);
} else {
results.forEach(webElement -> getSubElements(webElement, elements));
}
}
private Boolean isInputAble(WebElement element) {
return isEnabled(element) && Objects.equals(element.getTagName(), LabelType.INPUT);
}
@SuppressWarnings("unused")
private Boolean isClickAble(WebElement element) {
try {
return nonNull(ExpectedConditions.elementToBeClickable(element).apply(driver));
} catch (Exception e) {
return false;
}
private Boolean isInput(ElementFeature feature) {
return Objects.equals(feature.getTag(), LabelType.INPUT);
}
private Boolean isEnabled(WebElement element) {
try {
return nonNull(ExpectedConditions.elementToBeClickable(element).apply(driver));
} catch (Exception e) {
return false;
return element.isEnabled();
}
private Boolean isDisPlayed(WebElement element) {
return element.isDisplayed();
}
private String alterUrlName(String fileName) {
// /\?*:"<>|
return fileName.replace("/", "")
.replace("\\", "")
.replace("#", "")
.replace("?", "")
.replace("*", "")
.replace(":", "")
.replace("<", "")
.replace(".", "")
.replace(">", "")
.replace("|", "");
return fileName.replace("/", Strings.EMPTY)
.replace("\\", Strings.EMPTY)
.replace("#", Strings.EMPTY)
.replace("?", Strings.EMPTY)
.replace("*", Strings.EMPTY)
.replace(":", Strings.EMPTY)
.replace("<", Strings.EMPTY)
.replace(".", Strings.EMPTY)
.replace(">", Strings.EMPTY)
.replace("|", Strings.EMPTY);
}
private void screenshot(String name) {
......@@ -438,27 +438,13 @@ public class SeleniumExecutor {
return result;
}
private Boolean checkLocation(WebElement element) {
try {
Dimension size = driver.manage().window().getSize();
Point elementLocation = element.getLocation();
boolean xMatch = (elementLocation.x) >= 0 && (elementLocation.x <= size.width);
boolean yMatch = (elementLocation.y) >= 0 && (elementLocation.y <= size.height);
return xMatch && yMatch;
} catch (StaleElementReferenceException e) {
log.error("判断位置时出现元素过期");
return false;
} catch (Exception e) {
log.error("判断位置时出现错误:" + e);
return false;
}
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
private String checkJsError() {
try {
return analyseLogError(driver.manage().logs().get(LogType.BROWSER).getAll().stream().findFirst().orElse(null));
List<LogEntry> entries = webDriverWait.until(webDriver -> webDriver.manage().logs().get(LogType.BROWSER).getAll());
return analyseLogError(entries.stream().findFirst().orElse(null));
} catch (Exception e) {
log.error("出现异常:");
e.printStackTrace();
return null;
}
}
......@@ -469,6 +455,7 @@ public class SeleniumExecutor {
String errorConnection = "Failed to load resource: net::ERR_CONNECTION_RESET";
String resourceFailure = "Failed to load resource: ";
String referenceError = "Uncaught ReferenceError: ";
if (nonNull(logEntry) && nonNull(logEntry.getMessage())) {
if (logEntry.getMessage().contains(typeError)) {
return "JS类型错误";
} else if (logEntry.getMessage().contains(fileNotFound)) {
......@@ -478,18 +465,57 @@ public class SeleniumExecutor {
} else if (logEntry.getMessage().contains(resourceFailure)) {
return "加载依赖错误";
} else if (logEntry.getMessage().contains(referenceError)) {
return "没有依赖";
return "依赖缺失";
}
}
return null;
}
private ElementFeature toFeature(WebElement element) {
return new ElementFeature(
element.getLocation(),
element.getSize(),
element.getTagName(),
element.getText()
element.getText(),
element.getRect()
);
}
private Boolean checkConnect(String url) {
boolean connect = false;
String httpPrefix = "http";
String filePrefix = "file";
String ftpPrefix = "ftp";
if (url.startsWith(httpPrefix)) {
try {
URL u = new URL(url);
HttpURLConnection uConnection = (HttpURLConnection) u.openConnection();
uConnection.connect();
connect = true;
uConnection.disconnect();
} catch (Exception e) {
connect = false;
}
} else if (url.startsWith(filePrefix)) {
try {
URL u = new URL(url);
FileURLConnection uConnection = (FileURLConnection) u.openConnection();
uConnection.connect();
connect = true;
uConnection.close();
} catch (Exception e) {
connect = false;
}
} else if (url.startsWith(ftpPrefix)) {
try {
URL u = new URL(url);
FtpURLConnection uConnection = (FtpURLConnection) u.openConnection();
uConnection.connect();
connect = true;
uConnection.close();
} catch (Exception e) {
connect = false;
}
}
return connect;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论