From d1b45bbcca78356163563d5c91329f1f0ea6439d Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Fri, 7 Oct 2022 13:44:01 +1100 Subject: [PATCH 01/11] feature: create .ipa from .app bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes an extra build step unnecessary on Bitrise since build for testing generates both app bundle and test runner – faster builds, fewer credits used. --- constants.go | 6 ++++++ main.go | 14 ++++++++++---- util_fns.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/constants.go b/constants.go index c686f7d..6a7f372 100644 --- a/constants.go +++ b/constants.go @@ -9,7 +9,9 @@ const ( APP_AUTOMATE_BUILD_STATUS_ENDPOINT = "/app-automate/xcuitest/v2/builds/" APP_AUTOMATE_BUILD_DASHBOARD_URL = "https://app-automate.browserstack.com/dashboard/v2/builds/" TEST_RUNNER_RELATIVE_PATH_BITRISE = "/Debug-iphoneos/Tests iOS-Runner.app" + TEST_APP_RELATIVE_PATH_BITRISE = "/Debug-iphoneos/Airwallex.app" TEST_RUNNER_ZIP_FILE_NAME = "test_suite.zip" + TEST_APP_ZIP_FILE_NAME = "Airwallex.ipa" SAMPLE_APP = "bs://b91841adbf33515fef7a1cca869a9526a86f9a0e" SAMPLE_TEST_SUITE = "bs://535a0932c8a785384b8470ec6166e093cd3b2c5f" @@ -22,6 +24,10 @@ const ( FETCH_BUILD_STATUS_ERROR = "Failed to fetch test results, error: %s" HTTP_ERROR = "Something went wrong while processing your request, error: %s" RUNNER_APP_NOT_FOUND = "xcuitest_testsuite_path: couldn’t find the -Runner.app . Please add the $BITRISE_TEST_BUNDLE_PATH from Xcode Build for testing for iOS step or the absolute path of -Runner.app" + BUNDLE_APP_NOT_FOUND = "PLACEHOLDER FOR NOW" IPA_NOT_FOUND = "app_ipa_path: couldn’t find the iOS app (.ipa file). Please add the $BITRISE_IPA_PATH from Xcode Archive & Export for iOS step or the absolute path of iOS app (.ipa file)" FILE_ZIP_ERROR = "Something went wrong while processing the test-suite, error: %s" + APP_COPY_ERROR = "App copy, error: %s" + APP_DIR_ERROR = "App dir, error: %s" + APP_ZIP_ERROR = "App zip, error: %s" ) diff --git a/main.go b/main.go index c3f839f..5413edb 100644 --- a/main.go +++ b/main.go @@ -24,25 +24,31 @@ func main() { failf(UPLOAD_APP_ERROR, "invalid credentials") } - if ios_app == "" { - failf(IPA_NOT_FOUND) - } + // if ios_app == "" { + // failf(IPA_NOT_FOUND) + // } if test_suite_path == "" { failf(RUNNER_APP_NOT_FOUND) } + find_and_ipa_file_err := locateAppFileAndIpa(test_suite_path) find_and_zip_file_err := locateTestRunnerFileAndZip(test_suite_path) + if find_and_ipa_file_err != nil { + failf(find_and_ipa_file_err.Error()) + } + if find_and_zip_file_err != nil { failf(find_and_zip_file_err.Error()) } + test_app_app := TEST_APP_ZIP_FILE_NAME test_runner_app := TEST_RUNNER_ZIP_FILE_NAME log.Print("Uploading app on BrowserStack App Automate") - upload_app, err := upload(ios_app, APP_UPLOAD_ENDPOINT, username, access_key) + upload_app, err := upload(test_app_app, APP_UPLOAD_ENDPOINT, username, access_key) if err != nil { failf(err.Error()) diff --git a/util_fns.go b/util_fns.go index 0f1d7d9..c290a1a 100644 --- a/util_fns.go +++ b/util_fns.go @@ -270,6 +270,48 @@ func WalkMatch(root, ext string) []string { return files_found } +func locateAppFileAndIpa(test_suite_location string) error { + split_test_suite_path := strings.Split(test_suite_location, "/") + get_file_name := split_test_suite_path[len(split_test_suite_path)-1] + + test_runner_app_path := "" + + check_file_extension := strings.Split(get_file_name, ".") + + // Checking 2 conditions here + // 1. test_suite_location - is this runner app + // 2. test_suite_location - if this is a directory, does runner app exists in this directory. + if len(check_file_extension) > 0 && check_file_extension[len(check_file_extension)-1] == "app" { + test_runner_app_path = test_suite_location + } else if strings.Contains(get_file_name, "test_bundle") { + // if test_suite_location is a directory instead of the file, then check if runner app exits + if _, err := os.Stat(test_suite_location + TEST_APP_RELATIVE_PATH_BITRISE); errors.Is(err, os.ErrNotExist) { + return errors.New(RUNNER_APP_NOT_FOUND) + } else { + test_runner_app_path = test_suite_location + TEST_APP_RELATIVE_PATH_BITRISE + } + } else { + return errors.New(RUNNER_APP_NOT_FOUND) + } + + _, mkdir_err := exec.Command("mkdir", "Payload").Output() + if mkdir_err != nil { + return errors.New(fmt.Sprintf(APP_DIR_ERROR, mkdir_err)) + } + + _, err := exec.Command("cp", "-r", test_runner_app_path, "Payload/Application.app").Output() + if err != nil { + return errors.New(fmt.Sprintf(FILE_ZIP_ERROR, err)) + } + + _, zipping_err := exec.Command("zip", "-r", "-D", TEST_APP_ZIP_FILE_NAME, "Payload").Output() + if zipping_err != nil { + return errors.New(fmt.Sprintf(FILE_ZIP_ERROR, zipping_err)) + } + + return nil +} + func locateTestRunnerFileAndZip(test_suite_location string) error { split_test_suite_path := strings.Split(test_suite_location, "/") get_file_name := split_test_suite_path[len(split_test_suite_path)-1] From f37e61954e21c8408000f76d9bddb983180e0433 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Fri, 7 Oct 2022 13:44:51 +1100 Subject: [PATCH 02/11] fix: use `deviceSelection` as documented on BrowserStack website --- structs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structs.go b/structs.go index 3d0741b..12d1d56 100644 --- a/structs.go +++ b/structs.go @@ -9,7 +9,7 @@ type TestMapping struct { type TestSharding struct { NumberOfShards int `json:"numberOfShards,omitempty"` Mapping []TestMapping `json:"mapping,omitempty"` - AutoStrategyDevices []string `json:"devices,omitempty"` + AutoStrategyDevices string `json:"deviceSelection,omitempty"` } type BrowserStackPayload struct { From 4af581a342234150347f5d43222e9ea85e318d31 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Fri, 7 Oct 2022 13:57:32 +1100 Subject: [PATCH 03/11] fix: remove an unused variable --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 5413edb..22ff549 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ func main() { username := os.Getenv("browserstack_username") access_key := os.Getenv("browserstack_accesskey") - ios_app := os.Getenv("app_ipa_path") + // ios_app := os.Getenv("app_ipa_path") test_suite_path := os.Getenv("xcui_test_suite") if username == "" || access_key == "" { From ed58d9171cb13961c638cde6e2c8fe05650b4b7d Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Fri, 7 Oct 2022 14:16:55 +1100 Subject: [PATCH 04/11] chore: reuse test runner logic for app bundle --- constants.go | 2 +- util_fns.go | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/constants.go b/constants.go index 6a7f372..d99552b 100644 --- a/constants.go +++ b/constants.go @@ -9,7 +9,7 @@ const ( APP_AUTOMATE_BUILD_STATUS_ENDPOINT = "/app-automate/xcuitest/v2/builds/" APP_AUTOMATE_BUILD_DASHBOARD_URL = "https://app-automate.browserstack.com/dashboard/v2/builds/" TEST_RUNNER_RELATIVE_PATH_BITRISE = "/Debug-iphoneos/Tests iOS-Runner.app" - TEST_APP_RELATIVE_PATH_BITRISE = "/Debug-iphoneos/Airwallex.app" + BUNDLE_APP_FILE_NAME = "Airwallex" TEST_RUNNER_ZIP_FILE_NAME = "test_suite.zip" TEST_APP_ZIP_FILE_NAME = "Airwallex.ipa" diff --git a/util_fns.go b/util_fns.go index c290a1a..6a61648 100644 --- a/util_fns.go +++ b/util_fns.go @@ -270,28 +270,29 @@ func WalkMatch(root, ext string) []string { return files_found } -func locateAppFileAndIpa(test_suite_location string) error { - split_test_suite_path := strings.Split(test_suite_location, "/") - get_file_name := split_test_suite_path[len(split_test_suite_path)-1] +func locateAppFileAndIpa(app_bundle_location string) error { + split_app_bundle_path := strings.Split(app_bundle_location, "/") + get_file_name := split_app_bundle_path[len(split_app_bundle_path)-1] - test_runner_app_path := "" + app_bundle_path := "" check_file_extension := strings.Split(get_file_name, ".") // Checking 2 conditions here - // 1. test_suite_location - is this runner app - // 2. test_suite_location - if this is a directory, does runner app exists in this directory. + // 1. app_bundle_location - is this runner app + // 2. app_bundle_location - if this is a directory, does any runner app exists in this directory. if len(check_file_extension) > 0 && check_file_extension[len(check_file_extension)-1] == "app" { - test_runner_app_path = test_suite_location + app_bundle_path = app_bundle_location } else if strings.Contains(get_file_name, "test_bundle") { - // if test_suite_location is a directory instead of the file, then check if runner app exits - if _, err := os.Stat(test_suite_location + TEST_APP_RELATIVE_PATH_BITRISE); errors.Is(err, os.ErrNotExist) { - return errors.New(RUNNER_APP_NOT_FOUND) - } else { - test_runner_app_path = test_suite_location + TEST_APP_RELATIVE_PATH_BITRISE + // if app_bundle_location is a directory instead of the file, then check if runner app exits + files := WalkMatch(app_bundle_location+"/Debug-iphoneos/"+BUNDLE_APP_FILE_NAME, ".app") + + if len(files) < 1 { + return errors.New(BUNDLE_APP_NOT_FOUND) } + app_bundle_path = files[len(files)-1] } else { - return errors.New(RUNNER_APP_NOT_FOUND) + return errors.New(BUNDLE_APP_NOT_FOUND) } _, mkdir_err := exec.Command("mkdir", "Payload").Output() @@ -299,7 +300,7 @@ func locateAppFileAndIpa(test_suite_location string) error { return errors.New(fmt.Sprintf(APP_DIR_ERROR, mkdir_err)) } - _, err := exec.Command("cp", "-r", test_runner_app_path, "Payload/Application.app").Output() + _, err := exec.Command("cp", "-r", app_bundle_path, "Payload/Application.app").Output() if err != nil { return errors.New(fmt.Sprintf(FILE_ZIP_ERROR, err)) } From 2aa1c7e4332503e40737484d03db445767c9aa00 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Fri, 7 Oct 2022 15:31:06 +1100 Subject: [PATCH 05/11] feature: reuse `WalkMatch` for both app bundle and runner; specify app name in step variables --- constants.go | 13 ++++------ main.go | 10 +++---- step.yml | 6 ++--- util_fns.go | 73 +++++++++++++++++++++------------------------------- 4 files changed, 43 insertions(+), 59 deletions(-) diff --git a/constants.go b/constants.go index d99552b..b5deca2 100644 --- a/constants.go +++ b/constants.go @@ -9,9 +9,7 @@ const ( APP_AUTOMATE_BUILD_STATUS_ENDPOINT = "/app-automate/xcuitest/v2/builds/" APP_AUTOMATE_BUILD_DASHBOARD_URL = "https://app-automate.browserstack.com/dashboard/v2/builds/" TEST_RUNNER_RELATIVE_PATH_BITRISE = "/Debug-iphoneos/Tests iOS-Runner.app" - BUNDLE_APP_FILE_NAME = "Airwallex" TEST_RUNNER_ZIP_FILE_NAME = "test_suite.zip" - TEST_APP_ZIP_FILE_NAME = "Airwallex.ipa" SAMPLE_APP = "bs://b91841adbf33515fef7a1cca869a9526a86f9a0e" SAMPLE_TEST_SUITE = "bs://535a0932c8a785384b8470ec6166e093cd3b2c5f" @@ -24,10 +22,9 @@ const ( FETCH_BUILD_STATUS_ERROR = "Failed to fetch test results, error: %s" HTTP_ERROR = "Something went wrong while processing your request, error: %s" RUNNER_APP_NOT_FOUND = "xcuitest_testsuite_path: couldn’t find the -Runner.app . Please add the $BITRISE_TEST_BUNDLE_PATH from Xcode Build for testing for iOS step or the absolute path of -Runner.app" - BUNDLE_APP_NOT_FOUND = "PLACEHOLDER FOR NOW" - IPA_NOT_FOUND = "app_ipa_path: couldn’t find the iOS app (.ipa file). Please add the $BITRISE_IPA_PATH from Xcode Archive & Export for iOS step or the absolute path of iOS app (.ipa file)" - FILE_ZIP_ERROR = "Something went wrong while processing the test-suite, error: %s" - APP_COPY_ERROR = "App copy, error: %s" - APP_DIR_ERROR = "App dir, error: %s" - APP_ZIP_ERROR = "App zip, error: %s" + IPA_NOT_FOUND = "Failed to generate an .ipa file. Please verify the value in $BUNDLE_APP_NAME" + FILE_NOT_FOUND = "File not found: %s" + FILE_COPY_ERROR = "Failed to copy file, error: %s" + FILE_DIR_ERROR = "Failed to create directory, error: %s" + FILE_ZIP_ERROR = "Failed to zip file, error: %s" ) diff --git a/main.go b/main.go index 22ff549..564e614 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ func main() { username := os.Getenv("browserstack_username") access_key := os.Getenv("browserstack_accesskey") - // ios_app := os.Getenv("app_ipa_path") + app_bundle_name := os.Getenv("app_bundle_name") test_suite_path := os.Getenv("xcui_test_suite") if username == "" || access_key == "" { @@ -32,18 +32,18 @@ func main() { failf(RUNNER_APP_NOT_FOUND) } - find_and_ipa_file_err := locateAppFileAndIpa(test_suite_path) + ipa_file_name := locateAppBundleFileAndIpa(test_suite_path, app_bundle_name) find_and_zip_file_err := locateTestRunnerFileAndZip(test_suite_path) - if find_and_ipa_file_err != nil { - failf(find_and_ipa_file_err.Error()) + if ipa_file_name == "" { + failf(IPA_NOT_FOUND) } if find_and_zip_file_err != nil { failf(find_and_zip_file_err.Error()) } - test_app_app := TEST_APP_ZIP_FILE_NAME + test_app_app := ipa_file_name test_runner_app := TEST_RUNNER_ZIP_FILE_NAME log.Print("Uploading app on BrowserStack App Automate") diff --git a/step.yml b/step.yml index 7da4f1a..5f59ce2 100644 --- a/step.yml +++ b/step.yml @@ -79,13 +79,13 @@ inputs: description: 'Access Key of the BrowserStack account' # IPA's - - app_ipa_path: $BITRISE_IPA_PATH + - app_bundle_name: $BUNDLE_APP_NAME opts: title: 'iOS app under test' - summary: 'Path to the app (.ipa) file' + summary: 'Name of the .app file' is_expand: true is_required: true - description: 'Path of the app (.ipa) file' + description: 'Name of the .app file generated when building for testing' - xcui_test_suite: $BITRISE_TEST_BUNDLE_PATH opts: title: 'XCUI test suite' diff --git a/util_fns.go b/util_fns.go index 6a61648..6fea846 100644 --- a/util_fns.go +++ b/util_fns.go @@ -270,73 +270,60 @@ func WalkMatch(root, ext string) []string { return files_found } -func locateAppFileAndIpa(app_bundle_location string) error { - split_app_bundle_path := strings.Split(app_bundle_location, "/") - get_file_name := split_app_bundle_path[len(split_app_bundle_path)-1] +func locateAppFile(location string, file_name string) string { + app_extension := "app" + file_name_and_extension := file_name + "." + app_extension - app_bundle_path := "" + split_path := strings.Split(location, "/") + get_file_name := split_path[len(split_path)-1] - check_file_extension := strings.Split(get_file_name, ".") + file_path := "" - // Checking 2 conditions here - // 1. app_bundle_location - is this runner app - // 2. app_bundle_location - if this is a directory, does any runner app exists in this directory. - if len(check_file_extension) > 0 && check_file_extension[len(check_file_extension)-1] == "app" { - app_bundle_path = app_bundle_location + // If location is already a .app file, return that. Else if location is a directory, + // check if it contains any .app files with the specified name. + check_file_extension := strings.Split(get_file_name, ".") + if len(check_file_extension) > 0 && check_file_extension[len(check_file_extension)-1] == app_extension { + file_path = location } else if strings.Contains(get_file_name, "test_bundle") { - // if app_bundle_location is a directory instead of the file, then check if runner app exits - files := WalkMatch(app_bundle_location+"/Debug-iphoneos/"+BUNDLE_APP_FILE_NAME, ".app") + files := WalkMatch(location+"/Debug-iphoneos/", file_name_and_extension) if len(files) < 1 { - return errors.New(BUNDLE_APP_NOT_FOUND) + failf(FILE_NOT_FOUND, file_name_and_extension) } - app_bundle_path = files[len(files)-1] + file_path = files[len(files)-1] } else { - return errors.New(BUNDLE_APP_NOT_FOUND) + failf(FILE_NOT_FOUND, file_name_and_extension) } + return file_path +} + +// Locates .app, moves it into a Payload folder and compresses that folder into an .ipa file. +func locateAppBundleFileAndIpa(app_bundle_location string, app_bundle_name string) string { + app_bundle_path := locateAppFile(app_bundle_location, app_bundle_name) + app_zip_name := app_bundle_name + ".ipa" + _, mkdir_err := exec.Command("mkdir", "Payload").Output() if mkdir_err != nil { - return errors.New(fmt.Sprintf(APP_DIR_ERROR, mkdir_err)) + failf(FILE_DIR_ERROR, mkdir_err) } _, err := exec.Command("cp", "-r", app_bundle_path, "Payload/Application.app").Output() if err != nil { - return errors.New(fmt.Sprintf(FILE_ZIP_ERROR, err)) + failf(FILE_COPY_ERROR, err) } - _, zipping_err := exec.Command("zip", "-r", "-D", TEST_APP_ZIP_FILE_NAME, "Payload").Output() + _, zipping_err := exec.Command("zip", "-r", "-D", app_zip_name, "Payload").Output() if zipping_err != nil { - return errors.New(fmt.Sprintf(FILE_ZIP_ERROR, zipping_err)) + failf(FILE_ZIP_ERROR, zipping_err) } - return nil + return app_zip_name } +// Locates a runner .app and compresses it into a .zip file. func locateTestRunnerFileAndZip(test_suite_location string) error { - split_test_suite_path := strings.Split(test_suite_location, "/") - get_file_name := split_test_suite_path[len(split_test_suite_path)-1] - - test_runner_app_path := "" - - check_file_extension := strings.Split(get_file_name, ".") - - // Checking 2 conditions here - // 1. test_suite_location - is this runner app - // 2. test_suite_location - if this is a directory, does any runner app exists in this directory. - if len(check_file_extension) > 0 && check_file_extension[len(check_file_extension)-1] == "app" { - test_runner_app_path = test_suite_location - } else if strings.Contains(get_file_name, "test_bundle") { - // if test_suite_location is a directory instead of the file, then check if runner app exits - files := WalkMatch(test_suite_location+"/Debug-iphoneos/", "*-Runner.app") - - if len(files) < 1 { - return errors.New(RUNNER_APP_NOT_FOUND) - } - test_runner_app_path = files[len(files)-1] - } else { - return errors.New(RUNNER_APP_NOT_FOUND) - } + test_runner_app_path := locateAppFile(test_suite_location, "*-Runner") file_path := strings.Split(test_runner_app_path, "/") test_runner_file_name := file_path[len(file_path)-1] From a58ac2b3a7e073168e626a4b465f29f3bdbc3df4 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Mon, 10 Oct 2022 17:38:26 +1100 Subject: [PATCH 06/11] feature: allow custom ID to be specified in step input --- constants.go | 1 + main.go | 5 +++-- main_test.go | 4 ++-- services.go | 10 +++++++++- step.yml | 7 +++++++ 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/constants.go b/constants.go index b5deca2..5362205 100644 --- a/constants.go +++ b/constants.go @@ -18,6 +18,7 @@ const ( UPLOAD_APP_ERROR = "Failed to upload app on BrowserStack, error : %s" FILE_NOT_AVAILABLE_ERROR = "Failed to upload test suite on BrowserStack, error: file not available" INVALID_FILE_TYPE_ERROR = "Failed to upload test suite on BrowserStack, error: invalid file type" + APP_CUSTOM_ID_ERROR = "Failed to attach app custom ID, error: %s" BUILD_FAILED_ERROR = "Failed to execute build on BrowserStack, error: %s" FETCH_BUILD_STATUS_ERROR = "Failed to fetch test results, error: %s" HTTP_ERROR = "Something went wrong while processing your request, error: %s" diff --git a/main.go b/main.go index 564e614..d040c97 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ func main() { username := os.Getenv("browserstack_username") access_key := os.Getenv("browserstack_accesskey") app_bundle_name := os.Getenv("app_bundle_name") + app_custom_id := os.Getenv("app_custom_id") test_suite_path := os.Getenv("xcui_test_suite") if username == "" || access_key == "" { @@ -48,7 +49,7 @@ func main() { log.Print("Uploading app on BrowserStack App Automate") - upload_app, err := upload(test_app_app, APP_UPLOAD_ENDPOINT, username, access_key) + upload_app, err := upload(test_app_app, APP_UPLOAD_ENDPOINT, &app_custom_id, username, access_key) if err != nil { failf(err.Error()) @@ -66,7 +67,7 @@ func main() { log.Print("Uploading test suite on BrowserStack App Automate") - upload_test_suite, err := upload(test_runner_app, TEST_SUITE_UPLOAD_ENDPOINT, username, access_key) + upload_test_suite, err := upload(test_runner_app, TEST_SUITE_UPLOAD_ENDPOINT, nil, username, access_key) if err != nil { failf(err.Error()) diff --git a/main_test.go b/main_test.go index ae730ce..9d58749 100644 --- a/main_test.go +++ b/main_test.go @@ -36,7 +36,7 @@ func TestBuild(t *testing.T) { func TestUpload(t *testing.T) { t.Log("It should throw file not found error with empty path") { - build, err := upload("", APP_UPLOAD_ENDPOINT, "username", "password") + build, err := upload("", APP_UPLOAD_ENDPOINT, nil, "username", "password") t.Log(build, err) require.Equal(t, "", build) require.Error(t, err) @@ -44,7 +44,7 @@ func TestUpload(t *testing.T) { t.Log("It should throw file not found error with invalid path") { - build, err := upload("invalidpath", APP_UPLOAD_ENDPOINT, "username", "password") + build, err := upload("invalidpath", APP_UPLOAD_ENDPOINT, nil, "username", "password") t.Log(build, err) require.Equal(t, "", build) require.Error(t, err) diff --git a/services.go b/services.go index 589904e..5cd2a76 100644 --- a/services.go +++ b/services.go @@ -54,7 +54,7 @@ func build(app_url string, test_suite_url string, username string, access_key st } // this function uploads both app and test suite -func upload(app_path string, endpoint string, username string, access_key string) (string, error) { +func upload(app_path string, endpoint string, custom_id *string, username string, access_key string) (string, error) { if app_path == "" { return "", errors.New(FILE_NOT_AVAILABLE_ERROR) } @@ -84,6 +84,14 @@ func upload(app_path string, endpoint string, username string, access_key string return "", errors.New(FILE_NOT_AVAILABLE_ERROR) } + if custom_id != nil { + fileErr := multipart_writer.WriteField("custom_id", *custom_id) + + if fileErr != nil { + return "", errors.New(APP_CUSTOM_ID_ERROR) + } + } + err := multipart_writer.Close() if err != nil { diff --git a/step.yml b/step.yml index 5f59ce2..8aa9752 100644 --- a/step.yml +++ b/step.yml @@ -86,6 +86,13 @@ inputs: is_expand: true is_required: true description: 'Name of the .app file generated when building for testing' + - app_custom_id: + opts: + title: 'Custom ID' + summary: 'App Custom ID in BrowserStack' + is_expand: true + is_required: false + description: 'App Custom ID in BrowserStack' - xcui_test_suite: $BITRISE_TEST_BUNDLE_PATH opts: title: 'XCUI test suite' From 4380d8e34417ffa536454b21f173b6ce1cff88c5 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Wed, 12 Oct 2022 21:00:59 +1100 Subject: [PATCH 07/11] feature: allow custom test suite ID to be specified in step input --- main.go | 7 ++----- step.yml | 9 ++++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index d040c97..8a99992 100644 --- a/main.go +++ b/main.go @@ -19,16 +19,13 @@ func main() { access_key := os.Getenv("browserstack_accesskey") app_bundle_name := os.Getenv("app_bundle_name") app_custom_id := os.Getenv("app_custom_id") + test_suite_custom_id := os.Getenv("test_suite_custom_id") test_suite_path := os.Getenv("xcui_test_suite") if username == "" || access_key == "" { failf(UPLOAD_APP_ERROR, "invalid credentials") } - // if ios_app == "" { - // failf(IPA_NOT_FOUND) - // } - if test_suite_path == "" { failf(RUNNER_APP_NOT_FOUND) } @@ -67,7 +64,7 @@ func main() { log.Print("Uploading test suite on BrowserStack App Automate") - upload_test_suite, err := upload(test_runner_app, TEST_SUITE_UPLOAD_ENDPOINT, nil, username, access_key) + upload_test_suite, err := upload(test_runner_app, TEST_SUITE_UPLOAD_ENDPOINT, &test_suite_custom_id, username, access_key) if err != nil { failf(err.Error()) diff --git a/step.yml b/step.yml index 8aa9752..8fc9075 100644 --- a/step.yml +++ b/step.yml @@ -88,11 +88,18 @@ inputs: description: 'Name of the .app file generated when building for testing' - app_custom_id: opts: - title: 'Custom ID' + title: 'App Custom ID' summary: 'App Custom ID in BrowserStack' is_expand: true is_required: false description: 'App Custom ID in BrowserStack' + - test_suite_custom_id: + opts: + title: 'Test Suite Custom ID' + summary: 'Test Suite Custom ID in BrowserStack' + is_expand: true + is_required: false + description: 'Test Suite Custom ID in BrowserStack' - xcui_test_suite: $BITRISE_TEST_BUNDLE_PATH opts: title: 'XCUI test suite' From cf36a107de1382163c940e73e32c83aebbf6714f Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Thu, 13 Oct 2022 19:23:57 +1100 Subject: [PATCH 08/11] chore: update comments --- util_fns.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util_fns.go b/util_fns.go index 6fea846..316dd8d 100644 --- a/util_fns.go +++ b/util_fns.go @@ -279,7 +279,7 @@ func locateAppFile(location string, file_name string) string { file_path := "" - // If location is already a .app file, return that. Else if location is a directory, + // If location is already .app file, return that. Else if location is directory, // check if it contains any .app files with the specified name. check_file_extension := strings.Split(get_file_name, ".") if len(check_file_extension) > 0 && check_file_extension[len(check_file_extension)-1] == app_extension { @@ -298,7 +298,7 @@ func locateAppFile(location string, file_name string) string { return file_path } -// Locates .app, moves it into a Payload folder and compresses that folder into an .ipa file. +// Locates .app, moves it into a Payload folder and compresses that folder into .ipa. func locateAppBundleFileAndIpa(app_bundle_location string, app_bundle_name string) string { app_bundle_path := locateAppFile(app_bundle_location, app_bundle_name) app_zip_name := app_bundle_name + ".ipa" @@ -321,7 +321,7 @@ func locateAppBundleFileAndIpa(app_bundle_location string, app_bundle_name strin return app_zip_name } -// Locates a runner .app and compresses it into a .zip file. +// Locates runner .app and compresses it into .zip. func locateTestRunnerFileAndZip(test_suite_location string) error { test_runner_app_path := locateAppFile(test_suite_location, "*-Runner") From a7fed0bd2ab9e61c22b730e6add815117ad42d40 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Mon, 24 Oct 2022 21:43:10 +1100 Subject: [PATCH 09/11] chore: update README and step to make changes official --- README.md | 20 +++++++++++++------- constants.go | 2 +- step.yml | 36 ++++++++++++++++++++---------------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 2f27a5d..d3e710e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Add this step directly to your workflow in the [Bitrise Workflow Editor](https:/
Description -Run your XCUI tests on BrowserStack App Automate. This step collects the built IPA from `$BITRISE_IPA_PATH` and the output bundle file from `$BITRISE_TEST_BUNDLE_PATH` environment variables. +Run your XCUI tests on BrowserStack App Automate. This step collects *both the built app and test suite* from the `$BITRISE_BUNDLE_PATH` environment variable, generates an IPA file, uploads and starts a test build. ## Configure the Step @@ -17,17 +17,21 @@ Complete the following steps to configure BrowserStack's XCUI step in Bitrise: 1. Open the Workflow you want to use in the Workflow Editor. ​ -2. Add the [Xcode Archive & Export for iOS](https://www.bitrise.io/integrations/steps/xcode-archive) and [Xcode Build for testing for iOS](https://www.bitrise.io/integrations/steps/xcode-build-for-test) steps to your workflow and configure them. +2. Add the [Xcode Build for testing for iOS](https://www.bitrise.io/integrations/steps/xcode-build-for-test) step to your workflow and configure it. ​ -3. Add the **BrowserStack App Automate - XCUI** step below the **Xcode Archive & Export for iOS** and **Xcode Build for testing for iOS** steps. +3. Add the **BrowserStack App Automate - XCUI** step below the **Xcode Build for testing for iOS** steps. ​ 4. Add your BrowserStack Username and Access Key in the **Authentication** step input. + +5. Provide the built application name in the **iOS app under test** input. This is typically the product name in your project. ​ -5. For the **iOS app under test** input, the **BITRISE_IPA_PATH** output variable from the **Xcode Archive & Export for iOS** step exports the IPA file. Add `$BITRISE_IPA_PATH` to the **iOS app under test** input.

For the **XCUI test suite** input, the **BITRISE_TEST_BUNDLE_PATH** output variable from the **Xcode Build for testing for iOS step** exports the test suite. Add `$BITRISE_TEST_BUNDLE_PATH` to the **iOS app under test** input.

If you are not using **Xcode Archive & Export for iOS** and **Xcode Build for testing for iOS** steps, ensure that the **iOS app under test** input points to the path of your app (`.ipa` file). Also, ensure that the **XCUI test suite** input points to the test suite runner file. In the case of the runner app, it should be in the `/Debug-iphoneos` directory if you are providing an absolute path.
+6. For the **XCUI test suite** input, the **BITRISE_TEST_BUNDLE_PATH** output variable from the **Xcode Build for testing for iOS step** indicates where the app bundle and test suite are located. Add `$BITRISE_TEST_BUNDLE_PATH` to the **iOS app under test** input.

If you are not using the **Xcode Build for testing for iOS** step, ensure that the **XCUI test suite** input points to a directory that contains both the test suite runner file and the app bundle (not .ipa). ​ -6. Add one or more devices in the **Devices** step input. +7. Add one or more devices in the **Devices** step input. ​ -7. Configure additional step inputs like **Debug logs** and **Test Configurations** and start your build. +8. Optionally provide custom IDs for the app and test suite in **Custom IDs** and configure additional step inputs like **Debug logs** and **Test Configurations**. + +9. Start your build.
@@ -38,9 +42,11 @@ Complete the following steps to configure BrowserStack's XCUI step in Bitrise: | Key | Description | Flags | Default | | --- | --- | --- | --- | -| `iOS app` | Set the path of the app (.ipa) file. | Required | N/A | +| `iOS app under test` | Set the name of the .app file. | Required | N/A | | `XCUI test suite` | Set the path of the output bundle file. | Required | N/A | | `Devices` | Provide one or more device-OS combination in a new line. For example:
`iPhone 11-13`
`iPhone XS-15` | Required | N/A | +| `App Custom ID` | Custom identifier for the app under testing. | Optional | N/A | +| `Test Suite Custom ID` | Custom identifier for the test suite to be run. | Optional | N/A | | `Instrumentation logs` | Generate instrumentation logs of the test session | Optional | `true` | | `Network logs` | Generate network logs of your test sessions to capture network traffic, latency, etc. | Optional | `false` | | `Device Logs` | Generate device logs | Optional | `false` | diff --git a/constants.go b/constants.go index 5362205..303abc3 100644 --- a/constants.go +++ b/constants.go @@ -22,7 +22,7 @@ const ( BUILD_FAILED_ERROR = "Failed to execute build on BrowserStack, error: %s" FETCH_BUILD_STATUS_ERROR = "Failed to fetch test results, error: %s" HTTP_ERROR = "Something went wrong while processing your request, error: %s" - RUNNER_APP_NOT_FOUND = "xcuitest_testsuite_path: couldn’t find the -Runner.app . Please add the $BITRISE_TEST_BUNDLE_PATH from Xcode Build for testing for iOS step or the absolute path of -Runner.app" + RUNNER_APP_NOT_FOUND = "xcuitest_testsuite_path: couldn’t find the -Runner.app. Please add the $BITRISE_TEST_BUNDLE_PATH from Xcode Build for testing for iOS step or the absolute path of -Runner.app" IPA_NOT_FOUND = "Failed to generate an .ipa file. Please verify the value in $BUNDLE_APP_NAME" FILE_NOT_FOUND = "File not found: %s" FILE_COPY_ERROR = "Failed to copy file, error: %s" diff --git a/step.yml b/step.yml index 8fc9075..5ddbc03 100644 --- a/step.yml +++ b/step.yml @@ -12,7 +12,7 @@ title: |- summary: | Run your XCUITest tests on BrowserStack App Automate description: | - Run your XCUITest tests on BrowserStack App Automate. This step collects the built IPA from `$BITRISE_IPA_PATH` and test suite from `$BITRISE_BUNDLE_PATH` Environment Variables + Run your XCUITest tests on BrowserStack App Automate. This step collects the built app and test suite from `$BITRISE_BUNDLE_PATH` environment variable website: https://github.com/browserstack/browserstack-bitrise-xcui-step source_code_url: https://github.com/browserstack/browserstack-bitrise-xcui-step support_url: https://github.com/browserstack/browserstack-bitrise-xcui-step/issues @@ -78,7 +78,7 @@ inputs: is_sensitive: true description: 'Access Key of the BrowserStack account' - # IPA's + # App and test suite - app_bundle_name: $BUNDLE_APP_NAME opts: title: 'iOS app under test' @@ -86,20 +86,6 @@ inputs: is_expand: true is_required: true description: 'Name of the .app file generated when building for testing' - - app_custom_id: - opts: - title: 'App Custom ID' - summary: 'App Custom ID in BrowserStack' - is_expand: true - is_required: false - description: 'App Custom ID in BrowserStack' - - test_suite_custom_id: - opts: - title: 'Test Suite Custom ID' - summary: 'Test Suite Custom ID in BrowserStack' - is_expand: true - is_required: false - description: 'Test Suite Custom ID in BrowserStack' - xcui_test_suite: $BITRISE_TEST_BUNDLE_PATH opts: title: 'XCUI test suite' @@ -125,6 +111,24 @@ inputs: is_expand: true is_required: true + # Custom IDs + - app_custom_id: + opts: + title: 'App Custom ID' + summary: 'App Custom ID in BrowserStack' + is_expand: true + is_required: false + description: 'App Custom ID in BrowserStack' + category: 'Customs IDs' + - test_suite_custom_id: + opts: + title: 'Test Suite Custom ID' + summary: 'Test Suite Custom ID in BrowserStack' + is_expand: true + is_required: false + description: 'Test Suite Custom ID in BrowserStack' + category: 'Customs IDs' + # Debug logs inputs - instrumentation_logs: "true" opts: From f054dde0247a2ef97569752536a9a32d6e2af89e Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Mon, 24 Oct 2022 21:43:36 +1100 Subject: [PATCH 10/11] chore: bump version to 1.1.0 --- bitrise.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitrise.yml b/bitrise.yml index 21271f9..3b66b00 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -7,7 +7,7 @@ app: - A_SECRET_PARAM: $A_SECRET_PARAM # If you want to share this step into a StepLib - BITRISE_STEP_ID: xcui-browserstack-official - - BITRISE_STEP_VERSION: "1.0.0" + - BITRISE_STEP_VERSION: "1.1.0" - BITRISE_STEP_GIT_CLONE_URL: https://github.com/browserstack/browserstack-bitrise-xcui-step.git - MY_STEPLIB_REPO_FORK_GIT_URL: $MY_STEPLIB_REPO_FORK_GIT_URL From 73736ea5c194e685eee48002cdcfc1ad63b4a031 Mon Sep 17 00:00:00 2001 From: Brunno Ferreira Date: Tue, 24 Jan 2023 14:35:39 +1100 Subject: [PATCH 11/11] chore: update README to clarify a variable --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3e710e..30b61ae 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Complete the following steps to configure BrowserStack's XCUI step in Bitrise: | Key | Description | Flags | Default | | --- | --- | --- | --- | -| `iOS app under test` | Set the name of the .app file. | Required | N/A | +| `iOS app under test` | Set the name of the .app file (same as `PRODUCT_NAME` under Packaging in Xcode Build Settings). | Required | N/A | | `XCUI test suite` | Set the path of the output bundle file. | Required | N/A | | `Devices` | Provide one or more device-OS combination in a new line. For example:
`iPhone 11-13`
`iPhone XS-15` | Required | N/A | | `App Custom ID` | Custom identifier for the app under testing. | Optional | N/A |