我无法针对我的情况在Google或stackoverflow上找到解决方案,但被卡住了。
对于自动化测试,我使用InteliJ(作为IDE),java,Selenium,Appium和TestNG。
我在网站上执行了一些操作来初始化手机,然后自动在手机上执行了操作。
测试失败时的屏幕截图将捕获网站和手机屏幕。
我只需要捕获与失败的测试操作有关的屏幕。
请查看代码:
public abstract class BaseTest implements ITest, V3RestApi, V2Api {
private boolean isMobileAppLaunched = false;
@AfterMethod
public void afterMainMethod(ITestResult result) {
try {
if (result.getStatus() == ITestResult.FAILURE) {
captureScreenshot(result);
}
driver.quit();
if (isMobileAppLaunched) {
this.closeAppiumSession();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void captureScreenshot(ITestResult result) {
try {
String screenshotName;
File screenshot;
screenshotName = Utilities.getFileName(result.getName());
screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
this.attachScreenShotWithReport(screenshotName, screenshot, result);
if (isMobileAppLaunched) {
screenshotName = Utilities.getFileName(result.getName());
screenshot = ((TakesScreenshot) appiumDriver).getScreenshotAs(OutputType.FILE);
this.attachScreenShotWithReport(screenshotName, screenshot, result);
}
} catch (Exception e) {
logger.warn("Screenshot could not be captured for " + result.getName());
}
}
public void launchMobileApplication(MobileType mobileApplicationType) throws Exception {
this.isMobileAppLaunched = true;
}
}
public class AndroidTestCase extends BaseTest {
@Test(description = "Test description"})
public void testCaseOnAndroid() throws Exception {
reportLog("Login into the application as User Name");
//login action to website;
reportLog("Click on Hamburger Menu");
//click action on the website;
reportLog("Activate to recognize the mobile"));
//action on site to recognize the mobile;
reportLog("Mobile: Launch Mobile Application");
//launch the mobile;
reportLog("Mobile: Login into the Mobile application as User Name");
//action to login;
reportLog("Mobile: Click on tab");
//action on Mobile;
}
}
[假设reportLog
方法始终与测试方法本身在同一线程中被调用(例如testCaseOnAndroid
),我们可以构建一个缓存来保存给定线程的最后一次尝试操作(实质上,给定测试用例的最后一次尝试操作)。如果测试用例失败并调用了afterTestCase
,我们可以检查缓存并获取当前线程的最后一次尝试操作(通常,在测试线程本身的同一线程中调用用@AfterMethod
注释的方法)现在,我们可以决定要调用捕获浏览器窗口屏幕快照的驱动程序还是捕获模拟设备屏幕的屏幕快照的驱动程序:
public abstract class BaseTest {
/**
* Defines the type of a reported action.
*/
public enum ReportedActionType {
MOBILE,
WEB
}
private final ConcurrentHashMap<Thread, ReportedActionType> lastReportedActionCache = new ConcurrentHashMap<>();
@AfterMethod
public void afterTestCase(final ITestResult testResult) {
final Thread currentThread = currentThread();
final String testCaseName = testResult.getName();
if (testResult.getStatus() == FAILURE) {
final ReportedActionType lastReportedActionType = this.lastReportedActionCache.get(currentThread); // could be 'null'
try {
if (lastReportedActionType == MOBILE) {
captureEmulatedMobileDevice();
// todo: quit the mobile driver (close Appium session)
} else {
captureBrowserWindow();
// todo: quit the web driver (Selenium)
}
} catch (final Exception exception) {
exception.printStackTrace();
}
}
// irrespective of the state of the test result (success or failure), we need to make sure that we
// remove the cached information, otherwise the cache can get really
// large and this could lead to out of memory problems (we could potentially consider
// using a more sophisticated cache implementation of a 3rd-party library
// that supports time-based eviction, so that even if we forget to remove the
// cached information manually, it gets removed automatically after a fixed amount of time - e.g., 5-10 seconds)
this.lastReportedActionCache.remove(currentThread);
}
private void captureEmulatedMobileDevice() {
// todo: call the appropriate driver to capture a screenshot of the emulated device
}
private void captureBrowserWindow() {
// todo: call the appropriate driver to capture a screenshot of the browser window
}
public void reportLog(final String message) {
// log the message (to console, to a file, etc.)
// the assumption is that the actions within a test case are executed within the same
// thread the test case itself is executed in; as long as this assumption holds, we can cache
// the needed information and fetch it later to perform the needed checks
this.lastReportedActionCache.put(currentThread(),
getActionType(message));
}
private ReportedActionType getActionType(final String reportLogMessage) {
return reportLogMessage.toLowerCase()
.trim()
.startsWith("mobile:") ? MOBILE : WEB;
}
}
如果您碰巧具有非常自定义的测试环境设置,并且上述假设不成立,则意味着afterTestCase
在与测试方法不同的线程中调用...那么,我们就在一个泡菜中因为我们可以做的第二件事是尝试猜测测试方法的名称,并将其用作映射中的键,而不是使用当前线程(顺便说一下,请随意使用当前线程的ID作为地图而不是当前的Thread
对象本身):
public abstract class BaseTest {
/**
* Defines the type of a reported action.
*/
public enum ActionType {
MOBILE,
WEB
}
/**
* The name of the method in the stack trace after which we should stop guessing. Methods called after this method
* are irrelevant for the guessing algorithm.
*/
private static final String STOP_GUESSING_METHOD_NAME = "invoke0";
private final ConcurrentHashMap<String, ActionType> lastReportedActionCache = new ConcurrentHashMap<>();
@AfterMethod
public void afterTestCase(final ITestResult testResult) {
final String testCaseName = testResult.getName();
if (testResult.getStatus() == FAILURE) {
final ActionType lastReportedActionType = this.lastReportedActionCache.get(testCaseName); // could be 'null'
try {
if (lastReportedActionType == MOBILE) {
captureEmulatedMobileDevice();
// todo: quit the mobile driver (close Appium session)
} else {
captureBrowserWindow();
// todo: quit the web driver (Selenium)
}
} catch (final Exception exception) {
exception.printStackTrace();
}
}
// irrespective of the state of the test result (success or failure), we need to make sure that we
// remove the cached information, otherwise the cache can get really
// large and this could lead to out of memory problems (we could potentially consider
// using a more sophisticated cache implementation of a 3rd-party library
// that supports time-based eviction, so that even if we forget to remove the
// cached information manually, it gets removed automatically after a fixed amount of time - e.g., 5-10 seconds)
this.lastReportedActionCache.remove(testCaseName);
}
private void captureEmulatedMobileDevice() {
// todo: call the appropriate driver to capture a screenshot of the emulated device
}
private void captureBrowserWindow() {
// todo: call the appropriate driver to capture a screenshot of the browser window
}
public void reportLog(final String message) {
// log the message (to console, to a file, etc.)
// attempt to guess the test case name and store the performed action type; if we can't
// guess the test case name, then we don't store anything and in case an exception occurs, we simply
// treat as if it was a "WEB" action later in the code (cause that's the best we can do)
guessTestCaseName().ifPresent(testCaseName -> this.lastReportedActionCache.put(testCaseName,
guessActionType(message)));
}
private Optional<String> guessTestCaseName() {
final StackTraceElement[] stackTrace = currentThread().getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
final StackTraceElement stackTraceElement = stackTrace[i];
// the assumption is that the method invoked right before "invoke0" was the test method itself
if (STOP_GUESSING_METHOD_NAME.equals(stackTraceElement.getMethodName())) {
final String testCaseName = stackTrace[i - 1].getMethodName();
return of(testCaseName);
}
}
// we could not guess the test case name
return empty();
}
private ActionType guessActionType(final String reportLogMessage) {
return reportLogMessage.toLowerCase()
.trim()
.startsWith("mobile:") ? MOBILE : WEB;
}
}
上面的“猜测测试用例方法的名称”方法,有一些明显的缺点,而且很hacky。它取决于太多的假设,甚至取决于JVM和TestNG的内部工作原理(是的,我在找您... invoke0
)...在以后的版本中可能会发生更改。
话虽这么说,但这些是我能想到的最少侵入性的解决方案(黑客)。一个更合适的解决方案很可能需要更改成百上千的测试(这很可能是不希望的)。理想情况下,应该更恰当地对测试用例步骤(动作)进行建模,并且不仅在我们的想象中以reportLog
方法调用分隔的“事物”存在。 Java毕竟是一种OOP语言。