Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0d1b3f8
[maven-release-plugin] prepare release bstack-test
shandli123 Mar 6, 2025
9cad71d
[maven-release-plugin] prepare for next development iteration
shandli123 Mar 6, 2025
4afaf2f
[maven-release-plugin] prepare release browserstack-integration-1.2.18
shandli123 Mar 8, 2025
1f5e919
[maven-release-plugin] prepare for next development iteration
shandli123 Mar 8, 2025
74f637f
[maven-release-plugin] prepare release browserstack-integration-1.2.19
shandli123 Mar 8, 2025
835e379
[maven-release-plugin] prepare for next development iteration
shandli123 Mar 8, 2025
98383b4
[maven-release-plugin] prepare release browserstack-integration-1.2.20
shandli123 Mar 8, 2025
f0f049d
[maven-release-plugin] prepare for next development iteration
shandli123 Mar 8, 2025
4b2f799
intg 1575 adding support for showing bstack report in jenkins
shandli123 Mar 16, 2025
43b637b
intg 1575 rename classes for report
shandli123 Mar 16, 2025
739aa74
intg 1575 edge cases handling
shandli123 Mar 16, 2025
7844fe8
intg 1575 refactoring building query params
shandli123 Mar 19, 2025
6b0360c
updating hosts intg 1575
shandli123 Mar 19, 2025
943c3ab
intg 1575 refactoring under integration service package
shandli123 Mar 19, 2025
598f380
intg 1575 refactoring
shandli123 Mar 19, 2025
ca8bde4
intg 1575 operation name update
shandli123 Mar 20, 2025
035d1ec
intg 1575 adding symbol to invoke action
shandli123 Mar 20, 2025
d097b0b
intg 1575 symbol name change
shandli123 Mar 20, 2025
3f95ab7
intg 1575 demo enhancements
shandli123 Mar 21, 2025
8820fe6
intg 1575 formatting constants
shandli123 Mar 21, 2025
3b71266
intg 1575 format actions
shandli123 Mar 21, 2025
d46db06
intg 1575 demo updates
shandli123 Mar 23, 2025
259e14f
intg 1575 adding report summary on status page
shandli123 Mar 23, 2025
f234211
intg 1575 review comments resolution
shandli123 Mar 24, 2025
19f19ee
intg 1575 updating retry time
shandli123 Mar 24, 2025
ad22c2f
intg 1575 url endpoints constants
shandli123 Mar 24, 2025
5aec015
intg 1575 freestyle job refactor
shandli123 Mar 24, 2025
aa4225f
intg 1575 version update
shandli123 Mar 24, 2025
ee5802c
revamp based on new architecture
shandli123 May 16, 2025
f62cea1
response key change
shandli123 May 16, 2025
273ef30
dev test fixes
shandli123 May 16, 2025
77c599a
url changes
shandli123 May 16, 2025
940c3be
refactoring the code
shandli123 May 16, 2025
9ef21c5
adding requestType param
shandli123 May 19, 2025
0e535c3
contract changes
shandli123 May 21, 2025
1e17a08
dev testing enhancements
shandli123 May 23, 2025
19d18bb
handling report available in case of retry failure
shandli123 May 24, 2025
5091706
adding test available handling if next call fails
shandli123 May 27, 2025
76981eb
replacing old automate report with new Cad reports
shandli123 Jun 24, 2025
162fd1e
updating version
shandli123 Jun 24, 2025
7ec7273
Merge branch 'master' into INTG_1575_JENKINS_REPORT
shandli123 Jun 24, 2025
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>

<artifactId>browserstack-integration</artifactId>
<version>1.2.16-SNAPSHOT</version>
<version>1.2.21-SNAPSHOT</version>
<packaging>hpi</packaging>

<name>BrowserStack</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public interface BrowserStackEnvVars {
String BROWSERSTACK_LOCAL_IDENTIFIER = "BROWSERSTACK_LOCAL_IDENTIFIER";
String BROWSERSTACK_BUILD = "BROWSERSTACK_BUILD";
String BROWSERSTACK_BUILD_NAME = "BROWSERSTACK_BUILD_NAME";

String BROWSERSTACK_PROJECT_NAME = "BROWSERSTACK_PROJECT_NAME";
String BROWSERSTACK_APP_ID = "BROWSERSTACK_APP_ID";
String BROWSERSTACK_RERUN = "BROWSERSTACK_RERUN";
String BROWSERSTACK_RERUN_TESTS = "BROWSERSTACK_RERUN_TESTS";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public class Constants {
public static final String BROWSERSTACK_REPORT_PATH_PATTERN = "**/browserstack-artifacts/*";
public static final String JENKINS_CI_PLUGIN = "JenkinsCiPlugin";

public static final String BROWSERSTACK_CONFIG_DETAILS_ENDPOINT = "https://integrate.browserstack.com/api/ci-tools/v1/events/report-config";
public static final String BROWSERSTACK_TEST_REPORT_DISPLAY_NAME = "BrowserStack Build Test Reports";
public static final String INTEGRATIONS_TOOL_KEY = "jenkins";
public static final String REPORT_CONFIG_OPERATION_NAME = "report-config-details";

// Product
public static final String AUTOMATE = "automate";
public static final String APP_AUTOMATE = "app-automate";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.browserstack.automate.ci.jenkins.integrationService;

public enum BrowserStackReportStatus {
IN_PROGRESS,
COMPLETED,
FAILED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.browserstack.automate.ci.jenkins.integrationService;

import com.browserstack.automate.ci.common.constants.Constants;
import com.browserstack.automate.ci.jenkins.BrowserStackCredentials;
import hudson.model.Action;
import hudson.model.Run;
import org.json.JSONObject;
import okhttp3.*;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static com.browserstack.automate.ci.common.logger.PluginLogger.log;
import static com.browserstack.automate.ci.common.logger.PluginLogger.logError;

public class BrowserStackTestReportAction implements Action {

private Run<?, ?> run;
private BrowserStackCredentials credentials;
private String reportUrl;
private final String UUID;
private String reportHtml;
private final transient PrintStream logger;
private String reportStyle;
public String reportName;
public String urlName;

private int maxRetryReportAttempt;
private static final String REPORT_IN_PROGRESS = "REPORT_IN_PROGRESS";
private static final String REPORT_FAILED = "REPORT_FAILED";

private static final String RETRY_REPORT = "RETRY_REPORT";

private static final int MAX_ATTEMPTS = 3;
private static final OkHttpClient client = new OkHttpClient();
RequestsUtil requestsUtil;

public BrowserStackTestReportAction(Run<?, ?> run, BrowserStackCredentials credentials, String reportUrl, String UUID, String reportName, String tabUrl, final PrintStream logger) {
super();
setBuild(run);
this.credentials = credentials;
this.reportUrl = reportUrl;
this.UUID = UUID;
this.reportHtml = null;
this.reportStyle = "";
this.logger = logger;
this.reportName = reportName;
this.urlName = tabUrl;
this.maxRetryReportAttempt = MAX_ATTEMPTS;

requestsUtil = new RequestsUtil();
}


public String getReportHtml() {
fetchReportConditions();
return reportHtml;
}

public String getReportStyle() {
fetchReportConditions();
return reportStyle;
}

private void fetchReportConditions() {
if (reportHtml == null || reportHtml.equals(REPORT_IN_PROGRESS) || reportHtml.equals(RETRY_REPORT)) {
fetchReport();
}
}

private void fetchReport() {
if (UUID == null) {
reportHtml = REPORT_FAILED;
return;
}
Map<String, String> params = new HashMap<>();
params.put("UUID", UUID);
params.put("report_name", reportName);
params.put("tool", Constants.INTEGRATIONS_TOOL_KEY);

try {
String ciReportUrlWithParams = requestsUtil.buildQueryParams(reportUrl, params);
log(logger, "Fetching browserstack report " + reportName);
Response response = requestsUtil.makeRequest(ciReportUrlWithParams, credentials);
if (response.isSuccessful()) {
JSONObject reportResponse = new JSONObject(response.body().string());
String reportStatus = reportResponse.optString("report_status");
if (reportStatus.equalsIgnoreCase(String.valueOf(BrowserStackReportStatus.COMPLETED))) {
String defaultHTML = "<h1>No Report Found</h1>";
reportHtml = reportResponse.optString("report_html", defaultHTML);
reportStyle = reportResponse.optString("report_style", "");
} else if (reportStatus.equalsIgnoreCase(String.valueOf(BrowserStackReportStatus.IN_PROGRESS))) {
reportHtml = REPORT_IN_PROGRESS;
} else {
reportHtml = REPORT_FAILED;
}
logError(logger, "Received Non success response while fetching report" + response.code());
}
} catch (Exception e) {
reportHtml = RETRY_REPORT;
this.maxRetryReportAttempt--;
if (this.maxRetryReportAttempt < 0) {
reportHtml = REPORT_FAILED;
}
logError(logger, "Exception while fetching the report" + Arrays.toString(e.getStackTrace()));
}
}

public boolean isReportInProgress() {
return reportHtml.equals(REPORT_IN_PROGRESS);
}

public boolean isReportFailed() {
return reportHtml.equals(REPORT_FAILED);
}

public boolean reportRetryRequired() {
return reportHtml.equals(RETRY_REPORT);
}

public boolean isReportAvailable() {
if (reportHtml != null && !reportHtml.equals(REPORT_IN_PROGRESS) && !reportHtml.equals(REPORT_FAILED) && !reportHtml.equals(RETRY_REPORT)) {
return true;
}
return false;
}

public boolean reportHasStatus() {
if (reportHtml == null) return false;
return reportHtml.equals(REPORT_IN_PROGRESS) || reportHtml.equals(REPORT_FAILED);
}

public Run<?, ?> getBuild() {
return run;
}

public void setBuild(Run<?, ?> build) {
this.run = build;
}

@Override
public String getIconFileName() {
return Constants.BROWSERSTACK_LOGO;
}

@Override
public String getDisplayName() {
return this.reportName;
}

@Override
public String getUrlName() {
return this.urlName;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package com.browserstack.automate.ci.jenkins.integrationService;

import com.browserstack.automate.ci.common.BrowserStackEnvVars;
import com.browserstack.automate.ci.common.constants.Constants;
import com.browserstack.automate.ci.jenkins.BrowserStackBuildAction;
import com.browserstack.automate.ci.jenkins.BrowserStackCredentials;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.FilePath;
import hudson.Launcher;
import jenkins.tasks.SimpleBuildStep;
import org.jenkinsci.Symbol;
import org.json.JSONArray;
import org.json.JSONObject;
import okhttp3.*;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import static com.browserstack.automate.ci.common.logger.PluginLogger.log;
import static com.browserstack.automate.ci.common.logger.PluginLogger.logError;

public class BrowserStackTestReportPublisher extends Recorder implements SimpleBuildStep {
private static final Logger LOGGER = Logger.getLogger(BrowserStackTestReportPublisher.class.getName());
private static final OkHttpClient client = new OkHttpClient();
private static final int MAX_ATTEMPTS = 3;
private static final int RETRY_DELAY_SECONDS = 5;
private final Map<String, String> customEnvVars;

RequestsUtil requestsUtil;

@DataBoundConstructor
public BrowserStackTestReportPublisher(Map<String, String> customEnvVars) {
this.customEnvVars = customEnvVars != null && !customEnvVars.isEmpty() ? new HashMap<>(customEnvVars) : new HashMap<>();
requestsUtil = new RequestsUtil();
}

@Override
public void perform(Run<?, ?> build, @NonNull FilePath workspace, @NonNull Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
final PrintStream logger = listener.getLogger();
log(logger, "Adding BrowserStack Report");

EnvVars parentEnvs = build.getEnvironment(listener);
parentEnvs.putAll(getCustomEnvVars());

String browserStackBuildName = Optional.ofNullable(parentEnvs.get(BrowserStackEnvVars.BROWSERSTACK_BUILD_NAME))
.orElse(parentEnvs.get(Constants.JENKINS_BUILD_TAG));
String projectName = parentEnvs.get(BrowserStackEnvVars.BROWSERSTACK_PROJECT_NAME);

BrowserStackBuildAction buildAction = build.getAction(BrowserStackBuildAction.class);
if (buildAction == null || buildAction.getBrowserStackCredentials() == null) {
logError(logger, "No BrowserStackBuildAction or credentials found");
return;
}

BrowserStackCredentials credentials = buildAction.getBrowserStackCredentials();

LOGGER.info("Adding BrowserStack Report Action");

JSONObject reportConfigDetailsResponse = fetchConfigDetails(logger, Constants.BROWSERSTACK_CONFIG_DETAILS_ENDPOINT, credentials);
if (reportConfigDetailsResponse == null) {
logError(logger, "Could not fetch the report config details");
return;
}

JSONArray configDetails = reportConfigDetailsResponse.getJSONArray("config_details");
String lookUpURL = reportConfigDetailsResponse.getString("lookup_endpoint");
Date buildTimestamp = new Date(build.getStartTimeInMillis());

// Format the timestamp (e.g., YYYY-MM-DD HH:MM:SS)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
String formattedTime = sdf.format(buildTimestamp);

// Encode the timestamp to make it URL-safe
String encodedTimestamp = URLEncoder.encode(formattedTime, "UTF-8");

String UUID = fetchBuildUUID(logger, lookUpURL, credentials, browserStackBuildName, projectName, encodedTimestamp);
if (UUID == null) {
logError(logger, "Cannot find a build with name " + browserStackBuildName);
}

for (int i = 0; i < configDetails.length(); i++) {
JSONObject config = configDetails.getJSONObject(i);

String reportUrl = config.getString("report_fetch_url");
String reportName = config.getString("report_name");
String reportTabUrl = config.getString("report_tab_url");
log(logger, reportUrl + " " + UUID);
build.addAction(new BrowserStackTestReportAction(build, credentials, reportUrl, UUID, reportName, reportTabUrl, logger));
}

}

private String fetchBuildUUID(PrintStream logger, String lookUpURL, BrowserStackCredentials credentials, String buildName, String projectName, String encodedTimestamp) throws InterruptedException {

Map<String, String> params = new HashMap<>();
params.put("build_name", buildName);
params.put("pipeline_timestamp", encodedTimestamp);
params.put("tool", Constants.INTEGRATIONS_TOOL_KEY);
if (projectName != null) {
params.put("project_name", projectName);
}

log(logger, "Fetching build....");
String lookUpURLWithParams;

//constructing build params
try {
lookUpURLWithParams = requestsUtil.buildQueryParams(lookUpURL, params);
} catch (URISyntaxException uriSyntaxException) {
logError(logger, "Could not build look up url with params" + Arrays.toString(uriSyntaxException.getStackTrace()));
return null;
}

//making attempts
for (int attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
try {
Response response = requestsUtil.makeRequest(lookUpURLWithParams, credentials);
if (response.isSuccessful()) {
assert response.body() != null;
JSONObject lookUpResponse = new JSONObject(response.body().string());
String UUID = lookUpResponse.optString("UUID", null);

if (UUID != null) {
log(logger, "build found with " + buildName + " and project name " + projectName);
return UUID;
}

LOGGER.info("build not found will retry in sometime.." + lookUpResponse.getString("message"));
}
} catch (Exception e) {
logError(logger, "Attempt " + (attempts + 1) + " failed: " + Arrays.toString(e.getStackTrace()));
}
TimeUnit.SECONDS.sleep(RETRY_DELAY_SECONDS);
}

LOGGER.info("build not found even after " + MAX_ATTEMPTS + "attempts");
return null;
}

private JSONObject fetchConfigDetails(PrintStream logger, String configDetailsURL, BrowserStackCredentials credentials) {
log(logger, "Fetching config details for reports");
Map<String, String> params = new HashMap<>();
params.put("tool", Constants.INTEGRATIONS_TOOL_KEY);
params.put("operation", Constants.REPORT_CONFIG_OPERATION_NAME);
try {
String configDetailsURLWithParams = requestsUtil.buildQueryParams(configDetailsURL, params);
Response response = requestsUtil.makeRequest(configDetailsURLWithParams, credentials);
if (response.isSuccessful()) {
assert response.body() != null;
return new JSONObject(response.body().string());
}
logError(logger, "Failed to fetch config details: " + response.code());
} catch (Exception e) {
logError(logger, "Exception occurred while fetching config details: " + Arrays.toString(e.getStackTrace()));
}
return null;
}

public Map<String, String> getCustomEnvVars() {
return customEnvVars;
}

@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}

@Symbol("browserStackBuildTestReports")
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {

@Override
@SuppressWarnings("rawtypes")
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
// indicates that this builder can be used with all kinds of project types
return true;
}

@Override
public String getDisplayName() {
return Constants.BROWSERSTACK_TEST_REPORT_DISPLAY_NAME;
}

}
}
Loading