From 7a2e7afb8c9350ed27ed36b39dde2160dbe27867 Mon Sep 17 00:00:00 2001 From: Aditya Mane Date: Wed, 1 Jul 2026 12:02:26 +0530 Subject: [PATCH] Add network_logs_options input to capture network log content Adds a `network_logs_options` step input that mirrors the App Automate `networkLogsOptions` capability (e.g. {"captureContent": true}) so XCUITest builds can capture the request/response content (bodies) of network logs, not just metadata. Previously this could only be set on local/IDE runs. The input is parsed as JSON into networkLogsOptions and forwarded in the build payload. Providing it automatically enables networkLogs (the option is only honoured when network logs are on). When empty the payload is unchanged (networkLogsOptions is omitted), so it is fully backward compatible. Invalid JSON fails with a clear, actionable error. - step.yml: new network_logs_options JSON input under "Debug logs" - structs.go: NetworkLogsOptions struct + *NetworkLogsOptions field (omitempty) - util_fns.go: parseNetworkLogsOptions helper + wiring in createBuildPayload - constants.go: actionable error string for invalid JSON - main_test.go: unit tests for parsing and payload shape - README.md: documented the new input AAP-20051 Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 1 + constants.go | 2 ++ main_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ step.yml | 13 ++++++++++ structs.go | 35 ++++++++++++++------------ util_fns.go | 33 +++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2f27a5d..a2e7de7 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Complete the following steps to configure BrowserStack's XCUI step in Bitrise: | `Devices` | Provide one or more device-OS combination in a new line. For example:
`iPhone 11-13`
`iPhone XS-15` | Required | 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` | +| `Network logs options` | Refine network log capture as JSON (mirrors the `networkLogsOptions` capability). Supports `{"captureContent": true}` to capture request/response bodies, not just metadata. Automatically enables **Network logs**. | Optional | N/A | | `Device Logs` | Generate device logs | Optional | `false` | | `Capture screenshots` | Capture the screenshots of the test execution| Optional | `false` | | `Video recording` | Record video of the test execution | Optional | `true` | diff --git a/constants.go b/constants.go index c686f7d..0673d91 100644 --- a/constants.go +++ b/constants.go @@ -24,4 +24,6 @@ const ( 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" + + NETWORK_LOGS_OPTIONS_INVALID = "Invalid network_logs_options. Expected JSON such as {\"captureContent\": true}. Error: %s" ) diff --git a/main_test.go b/main_test.go index ae730ce..777b2e4 100644 --- a/main_test.go +++ b/main_test.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "os" "testing" @@ -109,3 +110,71 @@ func TestLocateTestRunnerFileAndZip(t *testing.T) { os.Remove("test_suite.zip") } } + +func TestParseNetworkLogsOptions(t *testing.T) { + t.Log("It should treat an empty value as not set") + { + options, err := parseNetworkLogsOptions("") + require.NoError(t, err) + require.Nil(t, options) + } + t.Log("It should parse captureContent true") + { + options, err := parseNetworkLogsOptions(`{"captureContent": true}`) + require.NoError(t, err) + require.NotNil(t, options) + require.True(t, options.CaptureContent) + } + t.Log("It should parse captureContent false") + { + options, err := parseNetworkLogsOptions(`{"captureContent": false}`) + require.NoError(t, err) + require.NotNil(t, options) + require.False(t, options.CaptureContent) + } + t.Log("It should return an actionable error on malformed JSON") + { + _, err := parseNetworkLogsOptions(`{captureContent: true}`) + require.Error(t, err) + require.Contains(t, err.Error(), "network_logs_options") + } + t.Log("It should return an actionable error on a wrong value type") + { + _, err := parseNetworkLogsOptions(`{"captureContent": "true"}`) + require.Error(t, err) + require.Contains(t, err.Error(), "network_logs_options") + } +} + +func TestCreateBuildPayloadNetworkLogsOptions(t *testing.T) { + t.Setenv("devices_list", "iPhone 14-16") + + t.Log("captureContent true -> networkLogs forced true + networkLogsOptions.captureContent true") + { + t.Setenv("network_logs", "false") + t.Setenv("network_logs_options", `{"captureContent": true}`) + + payload := createBuildPayload() + require.True(t, payload.NetworkLogs) + require.NotNil(t, payload.NetworkLogsOptions) + require.True(t, payload.NetworkLogsOptions.CaptureContent) + + marshalled, err := json.Marshal(payload) + require.NoError(t, err) + assert.Contains(t, string(marshalled), `"networkLogs":true`) + assert.Contains(t, string(marshalled), `"networkLogsOptions":{"captureContent":true}`) + } + + t.Log("options unset -> networkLogsOptions omitted (backward compatible)") + { + t.Setenv("network_logs", "true") + t.Setenv("network_logs_options", "") + + payload := createBuildPayload() + require.Nil(t, payload.NetworkLogsOptions) + + marshalled, err := json.Marshal(payload) + require.NoError(t, err) + assert.NotContains(t, string(marshalled), "networkLogsOptions") + } +} diff --git a/step.yml b/step.yml index 7da4f1a..18bf559 100644 --- a/step.yml +++ b/step.yml @@ -130,6 +130,19 @@ inputs: - "true" - "false" category: 'Debug logs' + - network_logs_options: + opts: + title: 'Network logs options' + summary: 'Refine network log capture as JSON. Supports captureContent to capture request/response bodies.' + description: | + Provide network log options as JSON to refine what your network logs capture. This mirrors the `networkLogsOptions` capability used in local/IDE runs. + + Currently supported: + + `{"captureContent": true}` - capture the request and response content (bodies), in addition to metadata such as URLs, headers, latency and status codes. + + Enabling this automatically enables **Network Logs** (it has no effect on its own). + category: 'Debug logs' - device_logs: "false" opts: title: 'Device Logs' diff --git a/structs.go b/structs.go index 3d0741b..179bba8 100644 --- a/structs.go +++ b/structs.go @@ -12,22 +12,27 @@ type TestSharding struct { AutoStrategyDevices []string `json:"devices,omitempty"` } +type NetworkLogsOptions struct { + CaptureContent bool `json:"captureContent"` +} + type BrowserStackPayload struct { - App string `json:"app"` - TestSuite string `json:"testSuite"` - Devices []string `json:"devices"` - InstrumentationLogs bool `json:"instrumentationLogs"` - NetworkLogs bool `json:"networkLogs"` - DeviceLogs bool `json:"deviceLogs"` - DebugScreenshots bool `json:"debugscreenshots,omitempty"` - VideoRecording bool `json:"video"` - Project string `json:"project,omitempty"` - ProjectNotifyURL string `json:"projectNotifyURL,omitempty"` - UseLocal bool `json:"local,omitempty"` - SkipTesting []string `json:"skip-testing,omitempty"` - OnlyTesting []string `json:"only-testing,omitempty"` - DynamicTests bool `json:"dynamicTests,omitempty"` - UseTestSharding interface{} `json:"shards,omitempty"` + App string `json:"app"` + TestSuite string `json:"testSuite"` + Devices []string `json:"devices"` + InstrumentationLogs bool `json:"instrumentationLogs"` + NetworkLogs bool `json:"networkLogs"` + NetworkLogsOptions *NetworkLogsOptions `json:"networkLogsOptions,omitempty"` + DeviceLogs bool `json:"deviceLogs"` + DebugScreenshots bool `json:"debugscreenshots,omitempty"` + VideoRecording bool `json:"video"` + Project string `json:"project,omitempty"` + ProjectNotifyURL string `json:"projectNotifyURL,omitempty"` + UseLocal bool `json:"local,omitempty"` + SkipTesting []string `json:"skip-testing,omitempty"` + OnlyTesting []string `json:"only-testing,omitempty"` + DynamicTests bool `json:"dynamicTests,omitempty"` + UseTestSharding interface{} `json:"shards,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. diff --git a/util_fns.go b/util_fns.go index 0f1d7d9..189ffc6 100644 --- a/util_fns.go +++ b/util_fns.go @@ -95,6 +95,23 @@ func getTestFilters(payload *BrowserStackPayload) { } } +// parseNetworkLogsOptions parses the network_logs_options input, which mirrors +// the App Automate `networkLogsOptions` capability (e.g. {"captureContent": true}). +// An empty value means "not set". Invalid JSON is a hard, actionable error. +func parseNetworkLogsOptions(raw string) (*NetworkLogsOptions, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return nil, nil + } + + options := NetworkLogsOptions{} + if err := json.Unmarshal([]byte(raw), &options); err != nil { + return nil, fmt.Errorf(NETWORK_LOGS_OPTIONS_INVALID, err) + } + + return &options, nil +} + // this util only picks data from env and map it to the struct func createBuildPayload() BrowserStackPayload { instrumentation_logs, _ := strconv.ParseBool(os.Getenv("instrumentation_logs")) @@ -105,6 +122,11 @@ func createBuildPayload() BrowserStackPayload { use_local, _ := strconv.ParseBool(os.Getenv("use_local")) use_dynamic_tests, _ := strconv.ParseBool(os.Getenv("use_dynamic_tests")) + network_logs_options, network_logs_options_err := parseNetworkLogsOptions(os.Getenv("network_logs_options")) + if network_logs_options_err != nil { + failf(network_logs_options_err.Error()) + } + sharding_data := TestSharding{} if os.Getenv("use_test_sharding") != "" { err := json.Unmarshal([]byte(os.Getenv("use_test_sharding")), &sharding_data) @@ -126,6 +148,17 @@ func createBuildPayload() BrowserStackPayload { UseLocal: use_local, } + // networkLogsOptions is only honoured by App Automate when networkLogs is + // also enabled, so enable it automatically when options are provided + // (avoids a backend "networkLogs not enabled" error). + if network_logs_options != nil { + if !network_logs { + log.Println("network_logs auto-enabled because network_logs_options was provided") + } + payload.NetworkLogs = true + payload.NetworkLogsOptions = network_logs_options + } + getTestFilters(&payload) if len(sharding_data.Mapping) != 0 && sharding_data.NumberOfShards != 0 {