From 549c8af91c676601011ff324be197213c46ba54f Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur <38219887+kamal-kaur04@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:23:34 +0530 Subject: [PATCH] BrowserStack Reports retention Create Persistent BrowserStack Reports --- .../automate/ci/common/Tools.java | 21 +++ .../ci/common/constants/Constants.java | 4 + .../AbstractBrowserStackReportForBuild.java | 6 +- .../jenkins/BrowserStackReportForBuild.java | 146 ++++++++++++++++-- .../jenkins/BrowserStackReportPublisher.java | 9 ++ .../ci/jenkins/BrowserStackResult.java | 80 ++++++++++ .../index.jelly | 6 +- .../summary.jelly | 8 +- 8 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackResult.java diff --git a/src/main/java/com/browserstack/automate/ci/common/Tools.java b/src/main/java/com/browserstack/automate/ci/common/Tools.java index 2f3ee10..96707e3 100644 --- a/src/main/java/com/browserstack/automate/ci/common/Tools.java +++ b/src/main/java/com/browserstack/automate/ci/common/Tools.java @@ -2,6 +2,12 @@ import org.apache.commons.lang.RandomStringUtils; +import hudson.FilePath; +import hudson.model.Run; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -60,4 +66,19 @@ public static String durationToHumanReadable(long duration) { public static String getUniqueString(boolean letters, boolean numbers) { return RandomStringUtils.random(48, letters, numbers); } + + /** Gets the directory to store report files */ + public static FilePath getBrowserStackReportDir(Run build, String dirName) { + return new FilePath(new File(build.getRootDir(), dirName)); + } + + public static String getStackTraceAsString(Throwable throwable) { + try { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } catch(Throwable e) { + return throwable != null ? throwable.toString() : ""; + } + } } diff --git a/src/main/java/com/browserstack/automate/ci/common/constants/Constants.java b/src/main/java/com/browserstack/automate/ci/common/constants/Constants.java index c012e90..b51e4b5 100644 --- a/src/main/java/com/browserstack/automate/ci/common/constants/Constants.java +++ b/src/main/java/com/browserstack/automate/ci/common/constants/Constants.java @@ -9,6 +9,7 @@ public class Constants { public static final String BROWSERSTACK_REPORT_URL = "testReportBrowserStack"; public static final String BROWSERSTACK_CYPRESS_REPORT_URL = "testReportBrowserStackCypress"; public static final String BROWSERSTACK_REPORT_PIPELINE_FUNCTION = "browserStackReportPublisher"; + public static final String BROWSERSTACK_REPORT_PATH_PATTERN = "**/browserstack-artifacts/*"; public static final String JENKINS_CI_PLUGIN = "JenkinsCiPlugin"; // Product @@ -20,6 +21,9 @@ public class Constants { // Session related info public static final class SessionInfo { public static final String NAME = "name"; + public static final String BROWSERSTACK_BUILD_NAME = "buildName"; + public static final String BROWSERSTACK_BUILD_URL = "buildUrl"; + public static final String BROWSERSTACK_BUILD_DURATION = "buildDuration"; public static final String BROWSER = "browser"; public static final String OS = "os"; public static final String STATUS = "status"; diff --git a/src/main/java/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild.java b/src/main/java/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild.java index deaff45..4241a00 100644 --- a/src/main/java/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild.java +++ b/src/main/java/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild.java @@ -1,12 +1,12 @@ package com.browserstack.automate.ci.jenkins; import com.browserstack.automate.ci.common.constants.Constants; -import hudson.model.Action; import hudson.model.Run; - -public abstract class AbstractBrowserStackReportForBuild implements Action { +import hudson.tasks.test.AbstractTestResultAction; +public abstract class AbstractBrowserStackReportForBuild extends AbstractTestResultAction { private Run build; + @Override public String getIconFileName() { return Constants.BROWSERSTACK_LOGO; diff --git a/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportForBuild.java b/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportForBuild.java index fc56574..c251078 100644 --- a/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportForBuild.java +++ b/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportForBuild.java @@ -10,30 +10,35 @@ import com.browserstack.automate.model.Session; import com.browserstack.client.BrowserStackClient; import com.browserstack.client.exception.BrowserStackException; + +import hudson.FilePath; import hudson.model.Run; + +import org.apache.commons.io.IOUtils; +import org.json.JSONArray; import org.json.JSONObject; import javax.annotation.Nonnull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.text.ParseException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.stream.Collectors; -import static com.browserstack.automate.ci.common.logger.PluginLogger.log; import static com.browserstack.automate.ci.common.logger.PluginLogger.logError; public class BrowserStackReportForBuild extends AbstractBrowserStackReportForBuild { private final String buildName; private final transient List browserStackSessions; - private final transient List result; - private final Map resultAggregation; + private transient List result; + private Map resultAggregation; private final ProjectType projectType; private final transient PrintStream logger; private final String customProxy; @@ -44,6 +49,7 @@ public class BrowserStackReportForBuild extends AbstractBrowserStackReportForBui private final String failedConst = Constants.SessionStatus.FAILED; private transient Build browserStackBuild; private String browserStackBuildBrowserUrl; + private static final Logger LOGGER = Logger.getLogger(BrowserStackReportForBuild.class.getName()); public BrowserStackReportForBuild(final Run build, final ProjectType projectType, @@ -109,6 +115,7 @@ public boolean generateBrowserStackReport() { if (result.size() > 0) { result.sort(new SessionsSortingComparator()); generateAggregationInfo(); + writeBuildResultToFile(getBuild()); return true; } return false; @@ -155,6 +162,10 @@ private JSONObject convertSessionToJsonObject(Session session) { sessionJSON.put(Constants.SessionInfo.NAME, session.getName()); } + sessionJSON.put(Constants.SessionInfo.BROWSERSTACK_BUILD_NAME, buildName); + sessionJSON.put(Constants.SessionInfo.BROWSERSTACK_BUILD_URL, browserStackBuildBrowserUrl); + sessionJSON.put(Constants.SessionInfo.BROWSERSTACK_BUILD_DURATION, String.valueOf(browserStackBuild.getDuration())); + if (session.getDevice() == null || session.getDevice().isEmpty()) { sessionJSON.put(Constants.SessionInfo.BROWSER, session.getBrowser()); } else { @@ -206,10 +217,121 @@ private void generateAggregationInfo() { resultAggregation.put("totalSessions", String.valueOf(totalSessions)); resultAggregation.put("totalErrors", String.valueOf(totalErrors)); - resultAggregation.put("buildDuration", Tools.durationToHumanReadable(browserStackBuild.getDuration())); + if (browserStackBuild == null && result.get(0).get(Constants.SessionInfo.BROWSERSTACK_BUILD_DURATION) != null) { + resultAggregation.put("buildDuration", Tools.durationToHumanReadable(Long.parseLong(String.valueOf(result.get(0).get(Constants.SessionInfo.BROWSERSTACK_BUILD_DURATION))))); + } else { + resultAggregation.put("buildDuration", Tools.durationToHumanReadable(browserStackBuild.getDuration())); + } + } + + private String fetchBuildInfo(List resultList) { + String buildName = ""; + if (resultList.size() > 0) { + JSONObject resultObject = resultList.get(0); + browserStackBuildBrowserUrl = String.valueOf(resultObject.get(Constants.SessionInfo.BROWSERSTACK_BUILD_URL)); + buildName = String.valueOf(resultObject.get(Constants.SessionInfo.BROWSERSTACK_BUILD_NAME)); + } + return buildName; + } + + private void writeBuildResultToFile(Run build) { + LOGGER.info("Writing Build results to File"); + try { + FilePath bstackDir = Tools.getBrowserStackReportDir(build, "browserstack-reports"); + bstackDir.mkdirs(); + FilePath dstFile = bstackDir.child("buildResults.json"); + dstFile.write(getBrowserStackResult().toString(), null); + } catch (Exception e) { + LOGGER.warning(String.format("Write Build result to File Failed - %s", Tools.getStackTraceAsString(e))); + } + } + + private List parseStoredBuildResult(Run build) { + List bstackResultList = new ArrayList<>(); + try { + FilePath bstackDir = Tools.getBrowserStackReportDir(build, "browserstack-reports"); + FilePath[] paths = null; + + try { + paths = bstackDir.list("buildResults*.json"); + } catch (Exception e) { + // do nothing + } + + if (paths != null) { + for (FilePath path : paths) { + File file = new File(path.getRemote()); + + if (!file.isFile()) { + continue; // move to next file + } else { + } + + try { + InputStream inputStream = new FileInputStream(file); + String jsonArrayTxt = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + try { + JSONArray parsedResult = new JSONArray(jsonArrayTxt); + for (int i = 0; i < parsedResult.length(); i++) { + JSONObject jsonobject = parsedResult.getJSONObject(i); + bstackResultList.add(jsonobject); + } + } catch (Exception e) { + LOGGER.warning(String.format("Reading BrowserStack Results from file failed - %s", Tools.getStackTraceAsString(e))); + } + inputStream.close(); + return bstackResultList; + } catch (Exception e) { + LOGGER.warning(String.format("Converting BrowserStack Results to Text failed - %s", Tools.getStackTraceAsString(e))); + } + } + } + return bstackResultList; + } catch (Exception e) { + LOGGER.warning(String.format("Parse stored build result %s", Tools.getStackTraceAsString(e))); + return bstackResultList; + } + } + + @Override + public int getFailCount() { + return 0; + } + + @Override + public int getTotalCount() { + return 0; + } + + @Override + public BrowserStackResult getResult() { + BrowserStackResult bstackResult = new BrowserStackResult(buildName, browserStackBuildBrowserUrl, result, resultAggregation); + bstackResult.setRun(getBuild()); + if (result == null) { + List resultList = parseStoredBuildResult(super.run); + try { + if (resultList != null && resultList.size() > 0) { + LOGGER.fine(String.format("Parse successful %s", resultList)); + result = resultList; + resultAggregation = new HashMap<>(); + generateAggregationInfo(); + String browserstackbuildName = fetchBuildInfo(resultList); + bstackResult = new BrowserStackResult(browserstackbuildName, browserStackBuildBrowserUrl, resultList, resultAggregation); + bstackResult.setRun(super.run); + } + } catch (Exception e) { + LOGGER.warning(String.format("Fetching results failed - %s", Tools.getStackTraceAsString(e))); + } + } + try { + super.run.save(); + } catch (IOException e) { + // do nothing + } + return bstackResult; } - public List getResult() { + public List getBrowserStackResult() { return result; } diff --git a/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportPublisher.java b/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportPublisher.java index c0be23f..3b87fd2 100644 --- a/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportPublisher.java +++ b/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackReportPublisher.java @@ -11,6 +11,7 @@ import hudson.model.AbstractProject; import hudson.model.Run; import hudson.model.TaskListener; +import hudson.tasks.ArtifactArchiver; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; @@ -22,10 +23,12 @@ import java.io.IOException; import java.io.PrintStream; import java.util.Optional; +import java.util.logging.Logger; import static com.browserstack.automate.ci.common.logger.PluginLogger.log; public class BrowserStackReportPublisher extends Recorder implements SimpleBuildStep { + private static final Logger LOGGER = Logger.getLogger(BrowserStackReportPublisher.class.getName()); @DataBoundConstructor public BrowserStackReportPublisher() { @@ -65,6 +68,12 @@ public void perform(@Nonnull Run build, @Nonnull FilePath workspace, @Nonn String reportStatus = reportResult ? Constants.ReportStatus.SUCCESS : Constants.ReportStatus.FAILED; log(logger, "BrowserStack Report Status: " + reportStatus); + LOGGER.info(String.format("Archiving artifacts for pattern %s", Constants.BROWSERSTACK_REPORT_PATH_PATTERN)); + ArtifactArchiver artifactArchiver = new ArtifactArchiver(Constants.BROWSERSTACK_REPORT_PATH_PATTERN); + artifactArchiver.setAllowEmptyArchive(true); + artifactArchiver.perform(build, workspace, parentEnvs, launcher, listener); + LOGGER.info(String.format("Succesfully archived artifacts for pattern %s", artifactArchiver.getArtifacts())); + tracker.reportGenerationCompleted(reportStatus, product.name(), pipelineStatus, browserStackBuildName, bstackReportAction.getBrowserStackBuildID()); } diff --git a/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackResult.java b/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackResult.java new file mode 100644 index 0000000..5e37a07 --- /dev/null +++ b/src/main/java/com/browserstack/automate/ci/jenkins/BrowserStackResult.java @@ -0,0 +1,80 @@ +package com.browserstack.automate.ci.jenkins; + +import java.util.List; +import java.util.Map; + +import hudson.model.Run; +import org.json.JSONObject; + +import com.browserstack.automate.ci.common.constants.Constants; +import hudson.tasks.test.TestObject; +import hudson.tasks.test.TestResult; + +public class BrowserStackResult extends TestResult { + // owner of this build + protected Run build; + private String buildName; + private String browserStackBuildBrowserUrl; + private final transient List result; + private final Map resultAggregation; + private final String errorConst = Constants.SessionStatus.ERROR; + private final String failedConst = Constants.SessionStatus.FAILED; + + public BrowserStackResult(String buildName, String browserStackBuildBrowserUrl, List resultList, Map resultAggregation) { + this.buildName = buildName; + this.browserStackBuildBrowserUrl = browserStackBuildBrowserUrl; + this.result = resultList; + this.resultAggregation = resultAggregation; + } + + @Override + public TestResult findCorrespondingResult(String id) { + if (id.equals(getId())) { + return this; + } + return null; + } + + @Override + public String getDisplayName() { + return Constants.BROWSERSTACK_REPORT_DISPLAY_NAME; + } + + @Override + public Run getRun() { + return build; + } + + @Override + public TestObject getParent() { + return null; + } + + public List getResult() { + return result; + } + + public Map getResultAggregation() { + return resultAggregation; + } + + public String getErrorConst() { + return errorConst; + } + + public String getFailedConst() { + return failedConst; + } + + public void setRun(Run build) { + this.build = build; + } + + public String getBuildName() { + return buildName; + } + + public String getBrowserStackBuildBrowserUrl() { + return browserStackBuildBrowserUrl; + } +} diff --git a/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/index.jelly b/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/index.jelly index d21e66d..f52b045 100644 --- a/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/index.jelly +++ b/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/index.jelly @@ -87,7 +87,7 @@ - +

No BrowserStack Report Available

@@ -103,7 +103,7 @@
- +

@@ -122,7 +122,7 @@ Duration Created At - + ${i.name} diff --git a/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/summary.jelly b/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/summary.jelly index d93b2b9..bfac3d7 100644 --- a/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/summary.jelly +++ b/src/main/resources/com/browserstack/automate/ci/jenkins/AbstractBrowserStackReportForBuild/summary.jelly @@ -78,7 +78,7 @@ - +

No BrowserStack Report Available

@@ -93,16 +93,16 @@
- +
BUILD -

${it.buildName}${it.resultAggregation.buildDuration}

+

${it.buildName}${it.result.resultAggregation.buildDuration}

TOTAL SESSIONS -

${it.resultAggregation.totalSessions}${it.resultAggregation.totalErrors} ERRORS

+

${it.result.resultAggregation.totalSessions}${it.result.resultAggregation.totalErrors} ERRORS