Skip to content

Commit 8b97c2a

Browse files
authored
Merge pull request #37 from SagarGaniga/cypress_report
Add Cypress Report Generator
2 parents 0a8795a + 203b28e commit 8b97c2a

File tree

7 files changed

+601
-0
lines changed

7 files changed

+601
-0
lines changed

src/main/java/com/browserstack/automate/ci/common/constants/Constants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
public class Constants {
66
public static final String BROWSERSTACK_REPORT_DISPLAY_NAME = "BrowserStack Test Report";
7+
public static final String BROWSERSTACK_CYPRESS_REPORT_DISPLAY_NAME = "BrowserStack Cypress Test Report";
78
public static final String BROWSERSTACK_LOGO = String.format("%s/plugin/browserstack-integration/images/logo.png", Jenkins.RESOURCE_PATH);
89
public static final String BROWSERSTACK_REPORT_URL = "testReportBrowserStack";
10+
public static final String BROWSERSTACK_CYPRESS_REPORT_URL = "testReportBrowserStackCypress";
911
public static final String BROWSERSTACK_REPORT_PIPELINE_FUNCTION = "browserStackReportPublisher";
1012
public static final String JENKINS_CI_PLUGIN = "JenkinsCiPlugin";
1113

@@ -40,5 +42,6 @@ public static final class SessionStatus {
4042
public static final String ERROR = "error";
4143
public static final String FAILED = "failed";
4244
public static final String UNMARKED = "unmarked";
45+
public static final String PASSED = "passed";
4346
}
4447
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import com.browserstack.automate.ci.common.constants.Constants;
4+
import hudson.model.Action;
5+
import hudson.model.Run;
6+
7+
public abstract class AbstractBrowserStackCypressReportForBuild implements Action {
8+
private Run<?, ?> build;
9+
10+
@Override
11+
public String getIconFileName() {
12+
return Constants.BROWSERSTACK_LOGO;
13+
}
14+
15+
@Override
16+
public String getDisplayName() {
17+
return Constants.BROWSERSTACK_CYPRESS_REPORT_DISPLAY_NAME;
18+
}
19+
20+
@Override
21+
public String getUrlName() {
22+
return Constants.BROWSERSTACK_CYPRESS_REPORT_URL;
23+
}
24+
25+
public Run<?, ?> getBuild() {
26+
return build;
27+
}
28+
29+
public void setBuild(Run<?, ?> build) {
30+
this.build = build;
31+
}
32+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import com.browserstack.automate.ci.common.constants.Constants;
4+
import com.browserstack.automate.ci.common.enums.ProjectType;
5+
import com.browserstack.automate.ci.common.tracking.PluginsTracker;
6+
import hudson.model.Run;
7+
import org.apache.commons.io.IOUtils;
8+
import org.json.JSONArray;
9+
import org.json.JSONObject;
10+
11+
import java.io.*;
12+
import java.util.*;
13+
14+
import static com.browserstack.automate.ci.common.logger.PluginLogger.logError;
15+
16+
public class BrowserStackCypressReportForBuild extends AbstractBrowserStackCypressReportForBuild {
17+
private static PrintStream logger;
18+
private final String buildName;
19+
private final transient JSONObject result;
20+
private final Map<String, String> resultAggregation;
21+
private final ProjectType projectType;
22+
// to make them available in jelly
23+
private final String passedConst = Constants.SessionStatus.PASSED;
24+
private final String failedConst = Constants.SessionStatus.FAILED;
25+
private final transient PluginsTracker tracker;
26+
private final boolean pipelineStatus;
27+
28+
public BrowserStackCypressReportForBuild(final Run<?, ?> build,
29+
final ProjectType projectType,
30+
final String buildName,
31+
final PrintStream logger,
32+
final PluginsTracker tracker,
33+
final boolean pipelineStatus) {
34+
super();
35+
setBuild(build);
36+
this.buildName = buildName;
37+
this.result = new JSONObject();
38+
this.resultAggregation = new HashMap<>();
39+
this.projectType = projectType;
40+
this.logger = logger;
41+
this.tracker = tracker;
42+
this.pipelineStatus = pipelineStatus;
43+
}
44+
45+
46+
public JSONObject getCypressMatrix(String filepath) {
47+
JSONObject report = null;
48+
final String reportJSONPath = filepath + "/results/browserstack-cypress-report.json";
49+
50+
try {
51+
InputStream is = new FileInputStream(reportJSONPath);
52+
String jsonTxt = IOUtils.toString(is, "UTF-8");
53+
report = new JSONObject(jsonTxt);
54+
} catch (FileNotFoundException e) {
55+
logError(logger, "No BrowserStackBuildAction found");
56+
tracker.sendError("BrowserStack Cypress Report Not Found", pipelineStatus, "CypressReportGeneration");
57+
} catch (IOException e) {
58+
logError(logger, "There was a problem while reading report files");
59+
tracker.sendError(e.toString(), pipelineStatus, "CypressReportGeneration");
60+
}
61+
return report;
62+
}
63+
64+
public boolean generateBrowserStackCypressReport(String workspace) {
65+
if (result.length() == 0) {
66+
JSONObject matrix = getCypressMatrix(workspace);
67+
68+
if(matrix == null) {
69+
return false;
70+
}
71+
72+
String buildNameWithBuildNumber = matrix.optString("build_name");
73+
String buildNameWithoutBuildNumber = buildNameWithBuildNumber.substring(0, buildNameWithBuildNumber.lastIndexOf(": "));
74+
75+
if (buildNameWithoutBuildNumber == null) {
76+
logError(logger, "BrowserStack Cypress Report not generated, result json may have been corrupted. Please retry.");
77+
tracker.sendError("Report not generated", pipelineStatus, "CypressReportGeneration");
78+
return false;
79+
}
80+
81+
if (!buildNameWithoutBuildNumber.equalsIgnoreCase(this.buildName)) {
82+
logError(logger, "BrowserStack Cypress Report not generated, build name mismatch.");
83+
tracker.sendError("Report not generated", pipelineStatus, "CypressReportGeneration");
84+
return false;
85+
}
86+
87+
generateResult(matrix);
88+
89+
if (result.length() > 0) {
90+
generateAggregationInfo();
91+
return true;
92+
}
93+
return false;
94+
}
95+
return true;
96+
}
97+
98+
private void generateResult(JSONObject matrix) {
99+
result.put("buildName", matrix.getString("build_name"));
100+
result.put("buildId", matrix.getString("build_id"));
101+
result.put("projectName", matrix.getString("project_name"));
102+
result.put("buildUrl", matrix.getString("build_url"));
103+
104+
JSONArray specs = new JSONArray();
105+
JSONObject rows = matrix.getJSONObject("rows");
106+
rows.keySet().forEach(specName ->
107+
{
108+
JSONObject spec = new JSONObject();
109+
JSONObject specData = rows.getJSONObject(specName);
110+
JSONObject specMeta = specData.getJSONObject("meta");
111+
112+
spec.put("name", specName);
113+
spec.put("path", specData.getString("path"));
114+
115+
// Meta
116+
spec.put("total", specMeta.getInt("total"));
117+
spec.put("failed", specMeta.getInt("failed"));
118+
spec.put("passed", specMeta.getInt("passed"));
119+
120+
// Sessions
121+
JSONArray sessions = specData.getJSONArray("sessions");
122+
spec.put("sessions", sessions);
123+
124+
specs.put(spec);
125+
});
126+
127+
result.put("specs", specs);
128+
}
129+
130+
private void generateAggregationInfo() {
131+
int totalSpecs = 0, totalErrors = 0;
132+
133+
JSONArray specs = result.getJSONArray("specs");
134+
135+
for(int i = 0; i < specs.length(); i++){
136+
JSONObject spec = specs.getJSONObject(i);
137+
totalSpecs += spec.getInt("total");
138+
totalErrors += spec.getInt("failed");
139+
}
140+
141+
resultAggregation.put("totalSpecs", String.valueOf(totalSpecs));
142+
resultAggregation.put("totalErrors", String.valueOf(totalErrors));
143+
}
144+
145+
public JSONObject getResult() {
146+
return result;
147+
}
148+
149+
public Map<String, String> getResultAggregation() {
150+
return resultAggregation;
151+
}
152+
153+
public String getBuildName() {
154+
return buildName;
155+
}
156+
157+
public ProjectType getProjectType() {
158+
return projectType;
159+
}
160+
161+
public String getPassedConst() {
162+
return passedConst;
163+
}
164+
165+
public String getFailedConst() {
166+
return failedConst;
167+
}
168+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import com.browserstack.automate.ci.common.BrowserStackEnvVars;
4+
import com.browserstack.automate.ci.common.constants.Constants;
5+
import com.browserstack.automate.ci.common.enums.ProjectType;
6+
import com.browserstack.automate.ci.common.tracking.PluginsTracker;
7+
import hudson.EnvVars;
8+
import hudson.Extension;
9+
import hudson.FilePath;
10+
import hudson.Launcher;
11+
import hudson.model.AbstractProject;
12+
import hudson.model.Run;
13+
import hudson.model.TaskListener;
14+
import hudson.tasks.BuildStepDescriptor;
15+
import hudson.tasks.BuildStepMonitor;
16+
import hudson.tasks.Publisher;
17+
import hudson.tasks.Recorder;
18+
import jenkins.tasks.SimpleBuildStep;
19+
import org.kohsuke.stapler.DataBoundConstructor;
20+
21+
import javax.annotation.Nonnull;
22+
import java.io.IOException;
23+
import java.io.PrintStream;
24+
import java.util.Optional;
25+
26+
import static com.browserstack.automate.ci.common.logger.PluginLogger.log;
27+
28+
public class BrowserStackCypressReportPublisher extends Recorder implements SimpleBuildStep {
29+
30+
@DataBoundConstructor
31+
public BrowserStackCypressReportPublisher() {
32+
}
33+
34+
public BuildStepMonitor getRequiredMonitorService() {
35+
return BuildStepMonitor.NONE;
36+
}
37+
38+
39+
@Override
40+
public void perform(@Nonnull Run<?, ?> build, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException {
41+
final PrintStream logger = listener.getLogger();
42+
final PluginsTracker tracker = new PluginsTracker();
43+
final boolean pipelineStatus = false;
44+
45+
log(logger, "Generating BrowserStack Cypress Test Report");
46+
47+
final EnvVars parentEnvs = build.getEnvironment(listener);
48+
String browserStackBuildName = parentEnvs.get(BrowserStackEnvVars.BROWSERSTACK_BUILD_NAME);
49+
browserStackBuildName = Optional.ofNullable(browserStackBuildName).orElse(parentEnvs.get(Constants.JENKINS_BUILD_TAG));
50+
final String jenkinsFolder = parentEnvs.get("WORKSPACE");
51+
ProjectType product = ProjectType.AUTOMATE;
52+
53+
tracker.reportGenerationInitialized(browserStackBuildName, product.name(), pipelineStatus);
54+
log(logger, "BrowserStack Cypress Project identified as : " + product.name());
55+
56+
final BrowserStackCypressReportForBuild bstackReportAction =
57+
new BrowserStackCypressReportForBuild(build, product, browserStackBuildName, logger, tracker, pipelineStatus);
58+
final boolean reportResult = bstackReportAction.generateBrowserStackCypressReport(jenkinsFolder);
59+
build.addAction(bstackReportAction);
60+
61+
String reportStatus = reportResult ? Constants.ReportStatus.SUCCESS : Constants.ReportStatus.FAILED;
62+
log(logger, "BrowserStack Cypress Report Status: " + reportStatus);
63+
}
64+
65+
@Extension
66+
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
67+
68+
@Override
69+
@SuppressWarnings("rawtypes")
70+
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
71+
// indicates that this builder can be used with all kinds of project types
72+
return true;
73+
}
74+
75+
/**
76+
* This human readable name is used in the configuration screen.
77+
*/
78+
@Override
79+
public String getDisplayName() {
80+
return Constants.BROWSERSTACK_CYPRESS_REPORT_DISPLAY_NAME;
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)