Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BrowserStack Reports retention #71

Merged
merged 1 commit into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/*";
kamal-kaur04 marked this conversation as resolved.
Show resolved Hide resolved
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");
kamal-kaur04 marked this conversation as resolved.
Show resolved Hide resolved
} 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