-
Notifications
You must be signed in to change notification settings - Fork 54
Intg 1575 Adding support to view bstack reports in jenkins CI #89
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
Merged
francisf
merged 41 commits into
jenkinsci:master
from
shandli123:INTG_1575_JENKINS_REPORT
Jul 3, 2025
Merged
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 9cad71d
[maven-release-plugin] prepare for next development iteration
shandli123 4afaf2f
[maven-release-plugin] prepare release browserstack-integration-1.2.18
shandli123 1f5e919
[maven-release-plugin] prepare for next development iteration
shandli123 74f637f
[maven-release-plugin] prepare release browserstack-integration-1.2.19
shandli123 835e379
[maven-release-plugin] prepare for next development iteration
shandli123 98383b4
[maven-release-plugin] prepare release browserstack-integration-1.2.20
shandli123 f0f049d
[maven-release-plugin] prepare for next development iteration
shandli123 4b2f799
intg 1575 adding support for showing bstack report in jenkins
shandli123 43b637b
intg 1575 rename classes for report
shandli123 739aa74
intg 1575 edge cases handling
shandli123 7844fe8
intg 1575 refactoring building query params
shandli123 6b0360c
updating hosts intg 1575
shandli123 943c3ab
intg 1575 refactoring under integration service package
shandli123 598f380
intg 1575 refactoring
shandli123 ca8bde4
intg 1575 operation name update
shandli123 035d1ec
intg 1575 adding symbol to invoke action
shandli123 d097b0b
intg 1575 symbol name change
shandli123 3f95ab7
intg 1575 demo enhancements
shandli123 8820fe6
intg 1575 formatting constants
shandli123 3b71266
intg 1575 format actions
shandli123 d46db06
intg 1575 demo updates
shandli123 259e14f
intg 1575 adding report summary on status page
shandli123 f234211
intg 1575 review comments resolution
shandli123 19f19ee
intg 1575 updating retry time
shandli123 ad22c2f
intg 1575 url endpoints constants
shandli123 5aec015
intg 1575 freestyle job refactor
shandli123 aa4225f
intg 1575 version update
shandli123 ee5802c
revamp based on new architecture
shandli123 f62cea1
response key change
shandli123 273ef30
dev test fixes
shandli123 77c599a
url changes
shandli123 940c3be
refactoring the code
shandli123 9ef21c5
adding requestType param
shandli123 0e535c3
contract changes
shandli123 1e17a08
dev testing enhancements
shandli123 19d18bb
handling report available in case of retry failure
shandli123 5091706
adding test available handling if next call fails
shandli123 76981eb
replacing old automate report with new Cad reports
shandli123 162fd1e
updating version
shandli123 7ec7273
Merge branch 'master' into INTG_1575_JENKINS_REPORT
shandli123 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
...ava/com/browserstack/automate/ci/jenkins/integrationService/BrowserStackReportStatus.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } |
158 changes: 158 additions & 0 deletions
158
...com/browserstack/automate/ci/jenkins/integrationService/BrowserStackTestReportAction.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
|
|
||
| } |
201 changes: 201 additions & 0 deletions
201
.../browserstack/automate/ci/jenkins/integrationService/BrowserStackTestReportPublisher.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
|
|
||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.