├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── BrowserStackAutomateJenkinsPlugin.pdf ├── LICENSE ├── README.md ├── code_formatter ├── eclipse-java-google-style.xml └── intellij-java-google-style.xml ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── browserstack │ │ └── automate │ │ └── ci │ │ ├── common │ │ ├── AutomateTestCase.java │ │ ├── BrowserStackBuildWrapperOperations.java │ │ ├── BrowserStackEnvVars.java │ │ ├── Tools.java │ │ ├── analytics │ │ │ ├── Analytics.java │ │ │ └── VersionTracker.java │ │ ├── clienthandler │ │ │ └── ClientHandler.java │ │ ├── constants │ │ │ └── Constants.java │ │ ├── enums │ │ │ └── ProjectType.java │ │ ├── logger │ │ │ └── PluginLogger.java │ │ ├── model │ │ │ └── BrowserStackSession.java │ │ ├── proxysettings │ │ │ └── JenkinsProxySettings.java │ │ ├── report │ │ │ └── XmlReporter.java │ │ ├── tracking │ │ │ ├── PluginsTracker.java │ │ │ └── PluginsTrackerEvents.java │ │ └── uploader │ │ │ ├── AppUploader.java │ │ │ └── AppUploaderHelper.java │ │ └── jenkins │ │ ├── AbstractBrowserStackCypressReportForBuild.java │ │ ├── AbstractBrowserStackReportForBuild.java │ │ ├── AppUploaderBuilder.java │ │ ├── AutomateActionData.java │ │ ├── AutomateTestAction.java │ │ ├── AutomateTestDataPublisher.java │ │ ├── BrowserStackBuildAction.java │ │ ├── BrowserStackBuildWrapper.java │ │ ├── BrowserStackBuildWrapperDescriptor.java │ │ ├── BrowserStackCredentials.java │ │ ├── BrowserStackCypressReportFileCallable.java │ │ ├── BrowserStackCypressReportForBuild.java │ │ ├── BrowserStackCypressReportPublisher.java │ │ ├── BrowserStackReportFileCallable.java │ │ ├── BrowserStackReportForBuild.java │ │ ├── BrowserStackReportPublisher.java │ │ ├── BrowserStackResult.java │ │ ├── VariableInjectorAction.java │ │ ├── integrationService │ │ ├── BrowserStackReportStatus.java │ │ ├── BrowserStackTestReportAction.java │ │ ├── BrowserStackTestReportPublisher.java │ │ └── RequestsUtil.java │ │ ├── local │ │ ├── BrowserStackLocalUtils.java │ │ ├── JenkinsBrowserStackLocal.java │ │ └── LocalConfig.java │ │ ├── observability │ │ ├── AccessControlsFilter.java │ │ ├── BuildWithObservabilityConfigAction.java │ │ ├── BuildWithObservabilityConfigActionFreeStyleFactory.java │ │ ├── BuildWithObservabilityConfigActionWorkflowFactory.java │ │ ├── ObservabilityCause.java │ │ ├── ObservabilityConfig.java │ │ └── ObservabilityEnvironmentContributor.java │ │ ├── pipeline │ │ ├── AppUploadStepExecution.java │ │ ├── AppUploaderStep.java │ │ ├── BrowserStackPipelineStep.java │ │ ├── BrowserStackPipelineStepExecution.java │ │ ├── BrowserStackReportStep.java │ │ ├── BrowserStackReportStepExecution.java │ │ └── ExpanderImpl.java │ │ └── qualityDashboard │ │ ├── QualityDashboardAPIUtil.java │ │ ├── QualityDashboardInit.java │ │ ├── QualityDashboardInitItemListener.java │ │ ├── QualityDashboardPipelineTracker.java │ │ ├── QualityDashboardUtil.java │ │ └── UpstreamPipelineResolver.java ├── resources │ ├── META-INF │ │ └── hudson.remoting.ClassFilter │ ├── com │ │ └── browserstack │ │ │ └── automate │ │ │ └── ci │ │ │ └── jenkins │ │ │ ├── AbstractBrowserStackCypressReportForBuild │ │ │ ├── index.jelly │ │ │ └── summary.jelly │ │ │ ├── AbstractBrowserStackReportForBuild │ │ │ ├── index.jelly │ │ │ └── summary.jelly │ │ │ ├── AccessControlsFilter │ │ │ ├── config.jelly │ │ │ ├── help-allowedHeaders.html │ │ │ ├── help-allowedMethods.html │ │ │ ├── help-allowedOrigins.html │ │ │ ├── help-exposedHeaders.html │ │ │ └── help-maxAge.html │ │ │ ├── AppUploaderBuilder │ │ │ ├── config.jelly │ │ │ └── help-buildFilePath.html │ │ │ ├── AutomateTestAction │ │ │ ├── summary.jelly │ │ │ └── summary.properties │ │ │ ├── BrowserStackBuildWrapper │ │ │ ├── config.jelly │ │ │ ├── config.properties │ │ │ ├── global.jelly │ │ │ ├── global.properties │ │ │ ├── help-credentialsId.html │ │ │ ├── help-enableUsageStats.html │ │ │ ├── help-localConfig.html │ │ │ ├── help-localOptions.html │ │ │ └── help-localPath.html │ │ │ ├── BrowserStackCredentials │ │ │ ├── credentials.jelly │ │ │ ├── help-accesskey.html │ │ │ └── help-username.html │ │ │ ├── BrowserStackCypressReportPublisher │ │ │ └── config.jelly │ │ │ ├── BrowserStackReportPublisher │ │ │ └── config.jelly │ │ │ ├── integrationService │ │ │ ├── BrowserStackTestReportAction │ │ │ │ ├── index.jelly │ │ │ │ └── summary.jelly │ │ │ └── BrowserStackTestReportPublisher │ │ │ │ └── config.jelly │ │ │ └── pipeline │ │ │ ├── AppUploaderStep │ │ │ ├── config.jelly │ │ │ └── help-appPath.html │ │ │ ├── BrowserStackPipelineStep │ │ │ ├── config.jelly │ │ │ ├── config.properties │ │ │ ├── help-credentialsId.html │ │ │ ├── help-enableUsageStats.html │ │ │ ├── help-localConfig.html │ │ │ ├── help-localOptions.html │ │ │ └── help-localPath.html │ │ │ └── BrowserStackReportStep │ │ │ ├── config.jelly │ │ │ └── help-product.html │ ├── index.jelly │ └── plugin.properties └── webapp │ └── images │ ├── ajax-loader-main.gif │ └── logo.png └── test ├── java └── com │ └── browserstack │ └── automate │ ├── ci │ └── jenkins │ │ ├── AutomateTestActionTest.java │ │ ├── AutomateTestDataPublisherTest.java │ │ ├── GlobalConfigTest.java │ │ ├── local │ │ └── JenkinsBrowserStackLocalTest.java │ │ └── pipeline │ │ └── BrowserStackPipelineStepTest.java │ └── jenkins │ └── helpers │ ├── CopyResourceFileToWorkspaceTarget.java │ └── TempCredentialIdGenerator.java └── resources ├── REPORT-com.browserstack.automate.application.tests.TestCaseWithFourUniqueSessions.xml └── TEST-com.browserstack.automate.application.tests.TestCaseWithFourUniqueSessions.xml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 8 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: 8 21 | distribution: 'temurin' 22 | cache: 'maven' 23 | - name: Build 24 | run: mvn clean install -DskipTests 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | .DS_Store 3 | 4 | target 5 | build 6 | work 7 | *.log 8 | 9 | # IntelliJ 10 | *.iml 11 | .idea 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | 18 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 19 | hs_err_pid* 20 | 21 | .classpath 22 | .project 23 | .settings/ 24 | /bin/ 25 | .vscode/ 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk6 5 | - oraclejdk7 6 | - oraclejdk8 7 | 8 | 9 | script: "mvn clean package" 10 | 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) 13 | 14 | -------------------------------------------------------------------------------- /BrowserStackAutomateJenkinsPlugin.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/browserstack-integration-plugin/HEAD/BrowserStackAutomateJenkinsPlugin.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 BrowserStack Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 | 5 | # Overview 6 | [BrowserStack](https://browserstack.com) gives instant access to 2000+ real mobile devices and browsers that enables developers to test their websites and mobile applications without requiring to install or maintain an internal lab of virtual machines, devices, or emulators. 7 | 8 | This BrowserStack Jenkins plugin helps you integrate and run your test suite from a Jenkins CI server on the BrowserStack device cloud. 9 | 10 | # Features 11 | Use the BrowserStack Jenkins plugin to: 12 | - Configure your BrowserStack credentials for your Jenkins jobs. 13 | - Set up and tear down the BrowserStack Local binary for testing internal, development, and staging environments. 14 | - Upload your app build to the BrowserStack servers (for mobile app testing in App Automate). 15 | - Embed BrowserStack test results, including video, logs, and screenshots in your Jenkins job results. 16 | 17 | 18 | # Prerequisites 19 | You need the following to use the plugin: 20 | - An existing Jenkins CI server (version 1.653+) 21 | - A BrowserStack account. You can [sign-up for free trial](https://www.browserstack.com/users/sign_up) if you do not have an existing account. 22 | 23 | # Installation and setup 24 | - Follow [Automate Jenkins documentation](https://www.browserstack.com/docs/automate/selenium/jenkins) to integrate your Selenium and Appium test suite for website testing in **Automate**. 25 | - Follow [App Automate Jenkins documentation](https://www.browserstack.com/docs/app-automate/appium/integrations/jenkins) to integrate your Appium test suite for native and hybrid mobile app testing in **App Automate**. 26 | 27 | # Latest updates 28 | With the 1.1.10 version of the Jenkins plugin, you can now enable BrowserStack test reporting in Jenkins with all test languages and frameworks with proxy support. 29 | 30 | # Feature requests and bug reports 31 | Please file feature requests and bug reports to [BrowserStack Support team](https://www.browserstack.com/contact?ref=help#technical-support). 32 | -------------------------------------------------------------------------------- /src/main/java/com/browserstack/automate/ci/common/AutomateTestCase.java: -------------------------------------------------------------------------------- 1 | package com.browserstack.automate.ci.common; 2 | 3 | import com.browserstack.automate.ci.common.logger.PluginLogger; 4 | import org.apache.commons.lang.StringUtils; 5 | 6 | import java.io.Serializable; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class AutomateTestCase implements Serializable { 11 | 12 | private static final Pattern PATTERN_TEST_SESSION = Pattern.compile("^browserstack:session:([^:]+):test:([^\\{]+)\\{([^\\}]+)\\}(.*)"); 13 | private static final String REGEX_TEST_CLASSPATH = "^\\w+(\\.\\w+)+$"; 14 | private static final String REGEX_TEST_ID_HASH = "^\\w+$"; 15 | private static final String PACKAGE_DEFAULT = "(root)"; 16 | private static final String CLASS_DEFAULT = ""; 17 | private static final String TEST_DEFAULT = ""; 18 | 19 | public final String sessionId; 20 | public final String packageName; 21 | public final String className; 22 | public final String testName; 23 | public final String testFullPath; 24 | public final String testHash; 25 | public final long testIndex; 26 | public final String testCaseObjectId; 27 | 28 | public AutomateTestCase(String sessionId, String testHash, long testIndex, String testCaseObjectId) { 29 | this.sessionId = sessionId; 30 | this.testHash = testHash; 31 | this.testIndex = testIndex; 32 | this.testCaseObjectId = testCaseObjectId; 33 | this.packageName = PACKAGE_DEFAULT; 34 | this.className = CLASS_DEFAULT; 35 | this.testName = TEST_DEFAULT; 36 | this.testFullPath = null; 37 | } 38 | 39 | public AutomateTestCase(String sessionId, String packageName, String className, String testName, long testIndex, 40 | String testCaseObjectId) { 41 | this.sessionId = sessionId; 42 | this.packageName = packageName; 43 | this.className = className; 44 | this.testName = stripTestParams(testName); 45 | this.testIndex = testIndex; 46 | this.testCaseObjectId = testCaseObjectId; 47 | this.testFullPath = (this.packageName.equals(PACKAGE_DEFAULT) ? "" : this.packageName + '.') 48 | + this.className + '.' + this.testName; 49 | this.testHash = null; 50 | } 51 | 52 | public static String stripTestParams(String testCaseName) { 53 | if (StringUtils.isEmpty(testCaseName)) { 54 | return null; 55 | } 56 | 57 | int subscriptIndex = testCaseName.indexOf('['); 58 | if (subscriptIndex != -1) { 59 | return testCaseName.substring(0, subscriptIndex); 60 | } 61 | 62 | return testCaseName; 63 | } 64 | 65 | public static AutomateTestCase parse(final String line) { 66 | Matcher matcher = PATTERN_TEST_SESSION.matcher(line); 67 | if (matcher.find()) { 68 | String sessionId = matcher.group(1); 69 | String testCasePath = matcher.group(2); 70 | String testCaseIndexStr = matcher.group(3); 71 | String testCaseObjectId = matcher.group(4); 72 | long testCaseIndex; 73 | 74 | try { 75 | testCaseIndex = Long.parseLong(testCaseIndexStr); 76 | } catch (NumberFormatException e) { 77 | PluginLogger.logDebug(System.out, "ERROR: Failed to parse testCaseIndex as Long: " + testCaseIndexStr); 78 | return null; 79 | } 80 | 81 | return parseTestCasePath(testCasePath, sessionId, testCaseIndex, testCaseObjectId); 82 | } 83 | 84 | return null; 85 | } 86 | 87 | private static AutomateTestCase parseTestCasePath(final String testCasePath, final String sessionId, 88 | final long testCaseIndex, final String testCaseObjectId) { 89 | if (testCasePath.matches(REGEX_TEST_CLASSPATH)) { 90 | PluginLogger.logDebug(System.out, "Parsing as package.class.testName"); 91 | 92 | int pos = testCasePath.lastIndexOf("."); 93 | if (pos != -1) { 94 | // try for package.class.[testName] 95 | String testCaseName = testCasePath.substring(pos + 1, testCasePath.length()); 96 | String path = testCasePath.substring(0, pos); 97 | 98 | pos = path.lastIndexOf("."); 99 | if (pos != -1) { 100 | // try for [package.class].testName 101 | String className = path.substring(pos + 1, testCasePath.length()); 102 | String packageName = path.substring(0, pos); 103 | PluginLogger.logDebug(System.out, "Parsed as package.class.testName"); 104 | return new AutomateTestCase(sessionId, packageName, className, testCaseName, testCaseIndex, testCaseObjectId); 105 | } else { 106 | // try for [class].testName 107 | PluginLogger.logDebug(System.out, "Parsed as (root).class.testName"); 108 | return new AutomateTestCase(sessionId, PACKAGE_DEFAULT, path, testCaseName, testCaseIndex, testCaseObjectId); 109 | } 110 | } 111 | } 112 | 113 | if (testCasePath.matches(REGEX_TEST_ID_HASH)) { 114 | PluginLogger.logDebug(System.out, "Parsed as test hash"); 115 | return new AutomateTestCase(sessionId, testCasePath, testCaseIndex, testCaseObjectId); 116 | } 117 | 118 | return null; 119 | } 120 | 121 | public boolean hasTestHash() { 122 | return (testHash != null && testHash.length() > 0); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/browserstack/automate/ci/common/BrowserStackEnvVars.java: -------------------------------------------------------------------------------- 1 | package com.browserstack.automate.ci.common; 2 | 3 | public interface BrowserStackEnvVars { 4 | String BROWSERSTACK_USER = "BROWSERSTACK_USER"; 5 | String BROWSERSTACK_USERNAME = "BROWSERSTACK_USERNAME"; 6 | String BROWSERSTACK_ACCESSKEY = "BROWSERSTACK_ACCESSKEY"; 7 | String BROWSERSTACK_ACCESS_KEY = "BROWSERSTACK_ACCESS_KEY"; 8 | String BROWSERSTACK_LOCAL = "BROWSERSTACK_LOCAL"; 9 | String BROWSERSTACK_LOCAL_IDENTIFIER = "BROWSERSTACK_LOCAL_IDENTIFIER"; 10 | String BROWSERSTACK_BUILD = "BROWSERSTACK_BUILD"; 11 | String BROWSERSTACK_BUILD_NAME = "BROWSERSTACK_BUILD_NAME"; 12 | 13 | String BROWSERSTACK_PROJECT_NAME = "BROWSERSTACK_PROJECT_NAME"; 14 | String BROWSERSTACK_APP_ID = "BROWSERSTACK_APP_ID"; 15 | String BROWSERSTACK_RERUN = "BROWSERSTACK_RERUN"; 16 | String BROWSERSTACK_RERUN_TESTS = "BROWSERSTACK_RERUN_TESTS"; 17 | String GRR_JENKINS_KEY = "BROWSERSTACK_GRR_REGION"; 18 | String AUTOMATE_API_ENV_KEY = "browserstack.automate.api"; 19 | String APP_AUTOMATE_API_ENV_KEY = "browserstack.app-automate.api"; 20 | String QEI_URL = "QEI_URL"; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/browserstack/automate/ci/common/Tools.java: -------------------------------------------------------------------------------- 1 | package com.browserstack.automate.ci.common; 2 | 3 | import org.apache.commons.lang.RandomStringUtils; 4 | 5 | import hudson.FilePath; 6 | import hudson.model.Run; 7 | 8 | import java.io.File; 9 | import java.io.PrintWriter; 10 | import java.io.StringWriter; 11 | import java.text.DateFormat; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Arrays; 14 | import java.util.Locale; 15 | import java.util.regex.Pattern; 16 | 17 | public class Tools { 18 | 19 | public static final Pattern BUILD_URL_PATTERN = Pattern.compile("(https?:\\/\\/[\\w-.]+\\/builds\\/\\w+)\\/sessions\\/\\w+"); 20 | public static final SimpleDateFormat READABLE_DATE_FORMAT = new SimpleDateFormat("dd MMM yyyy, HH:mm"); 21 | public static final DateFormat SESSION_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH); 22 | 23 | /** 24 | * Returns a string with only '*' of length equal to the length of the inputStr 25 | * 26 | * @param strToMask 27 | * @return masked string 28 | */ 29 | public static String maskString(String strToMask) { 30 | char[] maskChars = new char[strToMask.length()]; 31 | Arrays.fill(maskChars, '*'); 32 | return new String(maskChars); 33 | } 34 | 35 | /** 36 | * Returns human readable form 37 | * 38 | * @param duration in seconds 39 | * @return 40 | */ 41 | public static String durationToHumanReadable(long duration) { 42 | String result = ""; 43 | int seconds = (int) duration % 60; 44 | duration /= 60; 45 | int minutes = (int) duration % 60; 46 | duration /= 60; 47 | int hours = (int) duration % 24; 48 | int days = (int) duration / 24; 49 | 50 | if (days == 0) { 51 | if (hours == 0) { 52 | if (minutes == 0) { 53 | result = String.format("%02ds", seconds); 54 | } else { 55 | result = String.format("%02dm %02ds", minutes, seconds); 56 | } 57 | } else { 58 | result = String.format("%02dh %02dm %02ds", hours, minutes, seconds); 59 | } 60 | } else { 61 | result = String.format("%dd %02dh %02dm %02ds", days, hours, minutes, seconds); 62 | } 63 | return result; 64 | } 65 | 66 | public static String getUniqueString(boolean letters, boolean numbers) { 67 | return RandomStringUtils.random(48, letters, numbers); 68 | } 69 | 70 | /** Gets the directory to store report files */ 71 | public static FilePath getBrowserStackReportDir(Run, ?> build, String dirName) { 72 | return new FilePath(new File(build.getRootDir(), dirName)); 73 | } 74 | 75 | public static String getStackTraceAsString(Throwable throwable) { 76 | try { 77 | StringWriter stringWriter = new StringWriter(); 78 | throwable.printStackTrace(new PrintWriter(stringWriter)); 79 | return stringWriter.toString(); 80 | } catch(Throwable e) { 81 | return throwable != null ? throwable.toString() : ""; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/browserstack/automate/ci/common/analytics/VersionTracker.java: -------------------------------------------------------------------------------- 1 | package com.browserstack.automate.ci.common.analytics; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.node.ObjectNode; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.UUID; 9 | 10 | /** 11 | * @author Shirish Kamath 12 | * @author Anirudha Khanna 13 | */ 14 | public class VersionTracker { 15 | 16 | private static final String ID_FILENAME = "browserstack-id.txt"; 17 | 18 | private static final ObjectMapper mapper = new ObjectMapper(); 19 | 20 | private final File rootDir; 21 | 22 | private ObjectNode metadata; 23 | 24 | public VersionTracker(File rootDir) { 25 | this.rootDir = rootDir; 26 | } 27 | 28 | public boolean init(String pluginVersion) throws IOException { 29 | try { 30 | loadMetadata(); 31 | } catch (IOException e) { 32 | // first install (?) 33 | saveMetadata(pluginVersion); 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | public String getClientId() throws IOException { 41 | return getProperty("id"); 42 | } 43 | 44 | public String getPluginVersion() { 45 | return getProperty("version"); 46 | } 47 | 48 | public String getProperty(String name) { 49 | return (metadata != null && metadata.has(name)) ? metadata.get(name).asText() : null; 50 | } 51 | 52 | public boolean updateVersion(String version) throws IOException { 53 | String pluginVersion = getPluginVersion(); 54 | boolean needsUpdate = (pluginVersion == null || !pluginVersion.equals(version)); 55 | if (needsUpdate) { 56 | saveMetadata(version); 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | public void loadMetadata() throws IOException { 64 | metadata = mapper.readValue(new File(rootDir, ID_FILENAME), ObjectNode.class); 65 | } 66 | 67 | private void saveMetadata(String pluginVersion) throws IOException { 68 | String instanceId = UUID.randomUUID().toString().replace("\\-", ""); 69 | 70 | ObjectNode metadata = mapper.createObjectNode(); 71 | metadata.put("id", instanceId); 72 | metadata.put("version", pluginVersion); 73 | metadata.put("timestamp", System.currentTimeMillis()); 74 | 75 | File f = new File(rootDir, ID_FILENAME); 76 | try { 77 | mapper.writeValue(f, metadata); 78 | } catch (IOException ex) { 79 | throw new IOException("Failed to store plugin metadata", ex); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/browserstack/automate/ci/common/clienthandler/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.browserstack.automate.ci.common.clienthandler; 2 | 3 | import com.browserstack.appautomate.AppAutomateClient; 4 | import com.browserstack.automate.AutomateClient; 5 | import com.browserstack.automate.ci.common.enums.ProjectType; 6 | import com.browserstack.automate.ci.common.proxysettings.JenkinsProxySettings; 7 | import com.browserstack.client.BrowserStackClient; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | import java.io.PrintStream; 12 | 13 | public class ClientHandler { 14 | 15 | /** 16 | * Returns BrowserStackClient based on Project, i.e Automate or App Automate. 17 | * Also decides and sets the proxy for the client 18 | * @param project ProjectType 19 | * @param username Username of BrowserStack 20 | * @param accessKey Access Key of BrowserStack 21 | * @param customProxy Custom Proxy String 22 | * @param logger Logger 23 | * @return BrowserStackClient 24 | */ 25 | public static BrowserStackClient getBrowserStackClient(@Nonnull final ProjectType project, @Nonnull final String username, 26 | @Nonnull final String accessKey, @Nullable final String customProxy, 27 | @Nullable final PrintStream logger) { 28 | BrowserStackClient client = decideAndGetClient(project, username, accessKey); 29 | 30 | JenkinsProxySettings proxy; 31 | if (customProxy != null) { 32 | proxy = new JenkinsProxySettings(customProxy, logger); 33 | } else { 34 | proxy = new JenkinsProxySettings(logger); 35 | } 36 | 37 | if (proxy.hasProxy()) { 38 | client.setProxy(proxy.getHost(), proxy.getPort(), proxy.getUsername(), proxy.getPassword()); 39 | } 40 | 41 | return client; 42 | } 43 | 44 | /** 45 | * Initializes BrowserStack client based on project type 46 | * @param project ProjectType 47 | * @param username Username of BrowserStack 48 | * @param accessKey Access Key of BrowserStack 49 | * @return BrowserStackClient 50 | */ 51 | private static BrowserStackClient decideAndGetClient(@Nonnull final ProjectType project, @Nonnull final String username, @Nonnull final String accessKey) { 52 | if (project == ProjectType.APP_AUTOMATE) { 53 | return new AppAutomateClient(username, accessKey); 54 | } 55 | 56 | return new AutomateClient(username, accessKey); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/browserstack/automate/ci/common/constants/Constants.java: -------------------------------------------------------------------------------- 1 | package com.browserstack.automate.ci.common.constants; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import jenkins.model.Jenkins; 7 | 8 | public class Constants { 9 | public static final String BROWSERSTACK_REPORT_DISPLAY_NAME = "BrowserStack Test Report"; 10 | public static final String BROWSERSTACK_CYPRESS_REPORT_DISPLAY_NAME = "BrowserStack Cypress Test Report"; 11 | public static final String BROWSERSTACK_LOGO = String.format("%s/plugin/browserstack-integration/images/logo.png", Jenkins.RESOURCE_PATH); 12 | public static final String BROWSERSTACK_REPORT_URL = "testReportBrowserStack"; 13 | public static final String BROWSERSTACK_CYPRESS_REPORT_URL = "testReportBrowserStackCypress"; 14 | public static final String BROWSERSTACK_REPORT_PIPELINE_FUNCTION = "browserStackReportPublisher"; 15 | public static final String BROWSERSTACK_REPORT_PATH_PATTERN = "**/browserstack-artifacts/*"; 16 | public static final String JENKINS_CI_PLUGIN = "JenkinsCiPlugin"; 17 | 18 | public static final String CAD_BASE_URL = "https://api-observability.browserstack.com/ext"; 19 | public static final String BROWSERSTACK_CONFIG_DETAILS_ENDPOINT = "/v1/builds/buildReport"; 20 | 21 | public static final String INTEGRATIONS_TOOL_KEY = "jenkins"; 22 | 23 | public static final String BROWSERSTACK_TEST_REPORT_URL = "testReportBrowserStack"; 24 | public static final String BROWSERSTACK_CAD_REPORT_DISPLAY_NAME = "BrowserStack Test Report and Insights"; 25 | public static final String BROWSERSTACK_REPORT_FILENAME = "browserstack-report"; 26 | public static final String BROWSERSTACK_REPORT_FOLDER = "browserstack-artifacts"; 27 | 28 | public static final String BROWSERSTACK_REPORT_AUT_PIPELINE_FUNCTION = "browserStackReportAut"; 29 | 30 | // Product 31 | public static final String AUTOMATE = "automate"; 32 | public static final String APP_AUTOMATE = "app-automate"; 33 | 34 | //GRR REGION vs API URL mapping for Automate 35 | public static Map20 | * Logic for detection of stale report files is from Jenkins JUnit plugin. 21 | * 22 | * @author Shirish Kamath 23 | * @author Anirudha Khanna 24 | */ 25 | public class BrowserStackReportFileCallable extends MasterToSlaveFileCallable