diff --git a/README.md b/README.md index d16c2f0..fe9c969 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ To add and configure the "BrowserStack App Automate - Espresso" step in Bitrise, | --- | --- | | `$BROWSERSTACK_BUILD_URL` |BrowserStack Dashboard url for the executed build| | `$BROWSERSTACK_BUILD_STATUS`| Status of the executed build. Check out the [test results guide](https://www.browserstack.com/docs/app-automate/espresso/view-test-results) to learn about available status | +| `$BROWSERSTACK_COVERAGE_REPORTS`| Path to the coverage report downloaded from Browserstack when coverage is enabled | diff --git a/constants.go b/constants.go index 2478dbf..9c1cd2a 100644 --- a/constants.go +++ b/constants.go @@ -8,6 +8,8 @@ const ( APP_AUTOMATE_BUILD_ENDPOINT = "/app-automate/espresso/v2/build" APP_AUTOMATE_BUILD_STATUS_ENDPOINT = "/app-automate/espresso/v2/builds/" APP_AUTOMATE_BUILD_DASHBOARD_URL = "https://app-automate.browserstack.com/dashboard/v2/builds/" + APP_AUTOMATE_SESSIONS_PATH = "/sessions/" + COVERAGE_FOLDER = "browserstack_coverage" SAMPLE_APP = "bs://b91841adbf33515fef7a1cca869a9526a86f9a0e" SAMPLE_TEST_SUITE = "bs://535a0932c8a785384b8470ec6166e093cd3b2c5f" diff --git a/main.go b/main.go index 24ab17d..8d59293 100644 --- a/main.go +++ b/main.go @@ -88,6 +88,19 @@ func main() { failf(err.Error()) } + use_coverage, _ := strconv.ParseBool(os.Getenv("use_coverage")) + if use_coverage { + err = getCoverageReports(build_id, username, access_key) + if err != nil { + fmt.Printf("Failed to get coverage reports, error: %#v", err) + } + cmd_log_coverage_report, err_coverage_report := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_COVERAGE_REPORTS", "--value", COVERAGE_FOLDER).CombinedOutput() + + if err_coverage_report != nil { + fmt.Printf("Failed to expose coverage report with envman, error: %#v | output: %s", err, cmd_log_coverage_report) + } + } + cmd_log_build_id, err_build_id := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_BUILD_URL", "--value", APP_AUTOMATE_BUILD_DASHBOARD_URL+build_parsed_response["build_id"].(string)).CombinedOutput() cmd_log_build_status, err_build_status := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_BUILD_STATUS", "--value", build_status).CombinedOutput() diff --git a/services.go b/services.go index ebcb064..1872c98 100644 --- a/services.go +++ b/services.go @@ -212,3 +212,96 @@ func checkBuildStatus(build_id string, username string, access_key string, waitF } } } + +func getCoverageReports(build_id string, username string, access_key string) error { + + var sessionIds, err = getSessionIds(build_id, username, access_key) + if err != nil { + return err + } + if err := os.Mkdir(COVERAGE_FOLDER, os.ModePerm); err != nil { + return err + } + for _, session := range sessionIds { + err = getCoverageReport(build_id, session, username, access_key) + if err != nil { + return err + } + } + return nil +} + +func getCoverageReport(build_id string, sessionId string, username string, access_key string) error { + client := &http.Client{} + req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id+APP_AUTOMATE_SESSIONS_PATH+sessionId+"/coverage", nil) + + req.SetBasicAuth(username, access_key) + + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + + if err != nil { + return fmt.Errorf(HTTP_ERROR, err) + } + + defer res.Body.Close() + + // Create the file + var filePath = COVERAGE_FOLDER + "/" + sessionId + "-coverage.ec" + out, err := os.Create(filePath) + if err != nil { + return fmt.Errorf(HTTP_ERROR, err) + } + defer out.Close() + + _, err = io.Copy(out, res.Body) + + if err != nil { + return fmt.Errorf(HTTP_ERROR, err) + } + // Download all coverage files (may not be android), document the output variable + log.Println("Coverage file saved : ", filePath) + return nil +} + +func getSessionIds(build_id string, username string, access_key string) ([]string, error) { + + var ids = make([]string, 0) + client := &http.Client{} + req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id, nil) + + req.SetBasicAuth(username, access_key) + + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + + if err != nil { + return ids, fmt.Errorf(HTTP_ERROR, err) + } + + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return ids, fmt.Errorf(HTTP_ERROR, err) + } + + var buildResult Build + unmarshal_err := json.Unmarshal([]byte(body), &buildResult) + + if unmarshal_err != nil { + return ids, fmt.Errorf(HTTP_ERROR, err) + } + + for _, device := range buildResult.Devices { + for _, session := range device.Sessions { + ids = append(ids, session.Id) + } + } + + return ids, nil + +} diff --git a/step.yml b/step.yml index 65241a2..14275c8 100644 --- a/step.yml +++ b/step.yml @@ -260,6 +260,15 @@ inputs: - "true" - "false" category: "Test Configuration" + - use_coverage: "false" + opts: + title: "Coverage" + description: "Generate the code coverage report" + summary: "Generate the code coverage report for projects that have jacoco and test coverage enabled" + value_options: + - "true" + - "false" + category: "Test Configuration" outputs: @@ -274,6 +283,12 @@ outputs: summary: Bs build status description: | Status of the executed build. Check values [here:] (https://www.browserstack.com/docs/app-automate/espresso/view-test-results) + - BROWSERSTACK_COVERAGE_REPORTS: + opts: + title: "BrowserStack Coverage Report Folder" + summary: Bs coverage report folder + description: | + Path to the coverage report folder downloaded from Browserstack when coverage is enabled diff --git a/structs.go b/structs.go index 9584efc..8975685 100644 --- a/structs.go +++ b/structs.go @@ -32,6 +32,7 @@ type BrowserStackPayload struct { Size []string `json:"size,omitempty"` UseMockServer bool `json:"allowDeviceMockServer,omitempty"` UseTestSharding interface{} `json:"shards,omitempty"` + UseCoverage bool `json:"coverage,omitempty"` // Apart from the inputs from UI, these are some more fields which we support. // We've mentioned the type and the json key for these field. @@ -54,3 +55,15 @@ type BrowserStackPayload struct { // LocalIdentifier string `json:"localIdentifier,omitempty"` // IdleTimeout string `json:"idleTimeout,omitempty"` } + +type Build struct { + Devices []Device `json:"devices"` +} + +type Device struct { + Sessions []Session `json:"sessions"` +} + +type Session struct { + Id string `json:"id"` +} diff --git a/util_fns.go b/util_fns.go index f9b4bd0..5321b21 100644 --- a/util_fns.go +++ b/util_fns.go @@ -114,6 +114,7 @@ func createBuildPayload() BrowserStackPayload { clear_app_data, _ := strconv.ParseBool(os.Getenv("clear_app_data")) use_single_runner_invocation, _ := strconv.ParseBool(os.Getenv("use_single_runner_invocation")) use_mock_server, _ := strconv.ParseBool(os.Getenv("use_mock_server")) + use_coverage, _ := strconv.ParseBool(os.Getenv("use_coverage")) sharding_data := TestSharding{} if os.Getenv("use_test_sharding") != "" { @@ -136,6 +137,7 @@ func createBuildPayload() BrowserStackPayload { UseLocal: use_local, ClearAppData: clear_app_data, UseMockServer: use_mock_server, + UseCoverage: use_coverage, } getTestFilters(&payload)