diff --git a/README.md b/README.md
index d16c2f0..5539fe8 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@ To add and configure the "BrowserStack App Automate - Espresso" step in Bitrise,
| `Devices` | Name of one or more device-OS combination in new line. For example:
`Samsung Galaxy S9 Plus-9.0`
`Google Pixel 3a-9.0` | required | |
| `instrumentation_logs` | Generate instrumentation logs of the test session | | `true` |
| `Network logs` | Generate network logs of your Espresso test sessions to capture network traffic, latency, etc. | | `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**. | | |
| `Android logcat logs` | Generate device logs (Android logcat) | | `false` |
| `Capture Screenshots` | Capture the screenshots of the test execution| | `false` |
| `Video recording` | Record video of the test execution | | `true` |
diff --git a/constants.go b/constants.go
index 2478dbf..86fe10b 100644
--- a/constants.go
+++ b/constants.go
@@ -20,4 +20,6 @@ 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"
+
+ 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 5499966..069d12d 100644
--- a/main_test.go
+++ b/main_test.go
@@ -1,6 +1,7 @@
package main
import (
+ "encoding/json"
"testing"
"github.com/stretchr/testify/require"
@@ -86,3 +87,71 @@ func TestGetDevices(t *testing.T) {
require.Error(t, err)
}
}
+
+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", "Google Pixel 6 Pro-15.0")
+
+ 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)
+ require.Contains(t, string(marshalled), `"networkLogs":true`)
+ require.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)
+ require.NotContains(t, string(marshalled), "networkLogsOptions")
+ }
+}
diff --git a/step.yml b/step.yml
index 65241a2..cbd9393 100644
--- a/step.yml
+++ b/step.yml
@@ -140,6 +140,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: "Android logcat logs"
diff --git a/structs.go b/structs.go
index 9584efc..36bff81 100644
--- a/structs.go
+++ b/structs.go
@@ -12,26 +12,31 @@ 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"`
- ClearAppData bool `json:"clearPackageData,omitempty"`
- SingleRunnerInvocation bool `json:"singleRunnerInvocation,omitempty"`
- Class []string `json:"class,omitempty"`
- Package []string `json:"package,omitempty"`
- Annotation []string `json:"annotation,omitempty"`
- Size []string `json:"size,omitempty"`
- UseMockServer bool `json:"allowDeviceMockServer,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"`
+ ClearAppData bool `json:"clearPackageData,omitempty"`
+ SingleRunnerInvocation bool `json:"singleRunnerInvocation,omitempty"`
+ Class []string `json:"class,omitempty"`
+ Package []string `json:"package,omitempty"`
+ Annotation []string `json:"annotation,omitempty"`
+ Size []string `json:"size,omitempty"`
+ UseMockServer bool `json:"allowDeviceMockServer,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 f9b4bd0..94a73a4 100644
--- a/util_fns.go
+++ b/util_fns.go
@@ -103,6 +103,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"))
@@ -115,6 +132,11 @@ func createBuildPayload() BrowserStackPayload {
use_single_runner_invocation, _ := strconv.ParseBool(os.Getenv("use_single_runner_invocation"))
use_mock_server, _ := strconv.ParseBool(os.Getenv("use_mock_server"))
+ 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)
@@ -138,6 +160,17 @@ func createBuildPayload() BrowserStackPayload {
UseMockServer: use_mock_server,
}
+ // 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 {