diff --git a/README.md b/README.md index 2f27a5d..30b61ae 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 (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 | +| `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/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 diff --git a/constants.go b/constants.go index c686f7d..303abc3 100644 --- a/constants.go +++ b/constants.go @@ -18,10 +18,14 @@ 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" - 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 = "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" + 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" + 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 c3f839f..8a99992 100644 --- a/main.go +++ b/main.go @@ -17,32 +17,36 @@ 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") + 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) } + ipa_file_name := locateAppBundleFileAndIpa(test_suite_path, app_bundle_name) find_and_zip_file_err := locateTestRunnerFileAndZip(test_suite_path) + 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 := ipa_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, &app_custom_id, username, access_key) if err != nil { failf(err.Error()) @@ -60,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, 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/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 7da4f1a..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,14 +78,14 @@ inputs: is_sensitive: true description: 'Access Key of the BrowserStack account' - # IPA's - - app_ipa_path: $BITRISE_IPA_PATH + # App and test suite + - 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' @@ -111,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: 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 { diff --git a/util_fns.go b/util_fns.go index 0f1d7d9..316dd8d 100644 --- a/util_fns.go +++ b/util_fns.go @@ -270,31 +270,61 @@ func WalkMatch(root, ext string) []string { return files_found } -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] +func locateAppFile(location string, file_name string) string { + app_extension := "app" + file_name_and_extension := file_name + "." + app_extension - test_runner_app_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. 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 + // 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 { + file_path = 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") + files := WalkMatch(location+"/Debug-iphoneos/", file_name_and_extension) if len(files) < 1 { - return errors.New(RUNNER_APP_NOT_FOUND) + failf(FILE_NOT_FOUND, file_name_and_extension) } - test_runner_app_path = files[len(files)-1] + file_path = files[len(files)-1] } else { - return errors.New(RUNNER_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 .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" + + _, mkdir_err := exec.Command("mkdir", "Payload").Output() + if mkdir_err != nil { + failf(FILE_DIR_ERROR, mkdir_err) + } + + _, err := exec.Command("cp", "-r", app_bundle_path, "Payload/Application.app").Output() + if err != nil { + failf(FILE_COPY_ERROR, err) + } + + _, zipping_err := exec.Command("zip", "-r", "-D", app_zip_name, "Payload").Output() + if zipping_err != nil { + failf(FILE_ZIP_ERROR, zipping_err) } + return app_zip_name +} + +// Locates runner .app and compresses it into .zip. +func locateTestRunnerFileAndZip(test_suite_location string) error { + 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]