Skip to content

Commit

Permalink
BrowserStack Reports retention
Browse files Browse the repository at this point in the history
Create Persistent BrowserStack Reports
  • Loading branch information
kamal-kaur04 authored and francisf committed Jul 7, 2023
1 parent ea6547e commit 549c8af
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 22 deletions.
21 changes: 21 additions & 0 deletions src/main/java/com/browserstack/automate/ci/common/Tools.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() : "";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Session> browserStackSessions;
private final transient List<JSONObject> result;
private final Map<String, String> resultAggregation;
private transient List<JSONObject> result;
private Map<String, String> resultAggregation;
private final ProjectType projectType;
private final transient PrintStream logger;
private final String customProxy;
Expand All @@ -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,
Expand Down Expand Up @@ -109,6 +115,7 @@ public boolean generateBrowserStackReport() {
if (result.size() > 0) {
result.sort(new SessionsSortingComparator());
generateAggregationInfo();
writeBuildResultToFile(getBuild());
return true;
}
return false;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<JSONObject> 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<JSONObject> parseStoredBuildResult(Run<?, ?> build) {
List<JSONObject> 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<JSONObject> 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<JSONObject> getResult() {
public List<JSONObject> getBrowserStackResult() {
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<JSONObject> result;
private final Map<String, String> resultAggregation;
private final String errorConst = Constants.SessionStatus.ERROR;
private final String failedConst = Constants.SessionStatus.FAILED;

public BrowserStackResult(String buildName, String browserStackBuildBrowserUrl, List<JSONObject> resultList, Map<String, String> 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<JSONObject> getResult() {
return result;
}

public Map<String, String> 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;
}
}
Loading

0 comments on commit 549c8af

Please sign in to comment.