Skip to content

Commit bd84338

Browse files
committed
fix: status misreports off when telemetry uses VM defaults
1 parent 116851b commit bd84338

2 files changed

Lines changed: 96 additions & 49 deletions

File tree

cmd/browsers.go

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -800,41 +800,56 @@ func (b BrowsersCmd) TelemetryStatus(ctx context.Context, in BrowsersTelemetrySt
800800
return util.PrintPrettyJSON(browser.Telemetry)
801801
}
802802
cfg := browser.Telemetry.Browser
803-
pterm.Printf("console: %s\n", onOff(cfg.Console.Enabled))
804-
pterm.Printf("interaction: %s\n", onOff(cfg.Interaction.Enabled))
805-
pterm.Printf("network: %s\n", onOff(cfg.Network.Enabled))
806-
pterm.Printf("page: %s\n", onOff(cfg.Page.Enabled))
803+
pterm.Printf("console: %s\n", categoryOnOff(cfg.Console))
804+
pterm.Printf("interaction: %s\n", categoryOnOff(cfg.Interaction))
805+
pterm.Printf("network: %s\n", categoryOnOff(cfg.Network))
806+
pterm.Printf("page: %s\n", categoryOnOff(cfg.Page))
807807
return nil
808808
}
809809

810-
func onOff(v bool) string {
811-
if v {
810+
// categoryOnOff returns "on" or "off" for a category config, respecting the SDK
811+
// default: if the enabled field is absent from the response, it defaults to true.
812+
func categoryOnOff(c kernel.BrowserTelemetryCategoryConfig) string {
813+
if !c.JSON.Enabled.Valid() {
814+
return "on"
815+
}
816+
if c.Enabled {
812817
return "on"
813818
}
814819
return "off"
815820
}
816821

817-
// eventCategory derives the category prefix from a telemetry event type string.
818-
// e.g. "network_response" -> "network", "monitor_screenshot" -> "monitor".
819-
func eventCategory(eventType string) string {
820-
if i := strings.Index(eventType, "_"); i > 0 {
821-
return eventType[:i]
822+
// eventCategoryFromRaw reads the category field directly from the raw event JSON.
823+
// Falls back to prefix derivation (split on first _) when the field is absent.
824+
func eventCategoryFromRaw(ev kernel.BrowserTelemetryEventUnion) string {
825+
var obj struct {
826+
Category string `json:"category"`
827+
}
828+
if raw := ev.RawJSON(); raw != "" {
829+
if err := json.Unmarshal([]byte(raw), &obj); err == nil && obj.Category != "" {
830+
return obj.Category
831+
}
832+
}
833+
// fallback: derive from type prefix (e.g. "network_response" -> "network")
834+
if i := strings.Index(ev.Type, "_"); i > 0 {
835+
return ev.Type[:i]
822836
}
823-
return eventType
837+
return ev.Type
824838
}
825839

826840
// shouldEmit applies client-side category/type filters to a telemetry event.
827-
func shouldEmit(eventType string, categories, types []string) bool {
828-
if len(categories) > 0 && !slices.Contains(categories, eventCategory(eventType)) {
841+
func shouldEmit(ev kernel.BrowserTelemetryEventUnion, categories, types []string) bool {
842+
if len(categories) > 0 && !slices.Contains(categories, eventCategoryFromRaw(ev)) {
829843
return false
830844
}
831-
if len(types) > 0 && !slices.Contains(types, eventType) {
845+
if len(types) > 0 && !slices.Contains(types, ev.Type) {
832846
return false
833847
}
834848
return true
835849
}
836850

837-
var knownTelemetryCategories = []string{"console", "network", "page", "interaction", "monitor"}
851+
// knownTelemetryCategories are the real API event categories observable on stream.
852+
var knownTelemetryCategories = []string{"console", "network", "page", "interaction", "system", "api"}
838853

839854
var knownTelemetryTypes = []string{
840855
"console_log", "console_error",
@@ -872,15 +887,15 @@ func (b BrowsersCmd) TelemetryStream(ctx context.Context, in BrowsersTelemetrySt
872887
defer stream.Close()
873888
for stream.Next() {
874889
ev := stream.Current()
875-
if !shouldEmit(ev.Event.Type, in.Categories, in.Types) {
890+
if !shouldEmit(ev.Event, in.Categories, in.Types) {
876891
continue
877892
}
878893
if in.Output == "json" {
879894
_ = util.PrintCompactJSONLine(ev)
880895
continue
881896
}
882897
ts := time.UnixMicro(ev.Event.Ts).Local().Format("15:04:05")
883-
pterm.Printf("%s [%s] %s\n", ts, eventCategory(ev.Event.Type), ev.Event.Type)
898+
pterm.Printf("%s [%s] %s\n", ts, eventCategoryFromRaw(ev.Event), ev.Event.Type)
884899
}
885900
if err := stream.Err(); err != nil {
886901
return util.CleanedUpSdkError{Err: err}
@@ -2517,7 +2532,7 @@ func init() {
25172532
// telemetry
25182533
telemetryRoot := &cobra.Command{Use: "telemetry", Short: "Browser telemetry operations"}
25192534
telemetryStream := &cobra.Command{Use: "stream <id>", Short: "Stream live telemetry events", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetryStream}
2520-
telemetryStream.Flags().StringSlice("categories", []string{}, "Filter by category (console,network,page,interaction,monitor); monitor is always-on and filter-only — it cannot be toggled via telemetry set")
2535+
telemetryStream.Flags().StringSlice("categories", []string{}, "Filter by API event category (console,network,page,interaction,system,api); system covers monitor_* and cdp_* events")
25212536
telemetryStream.Flags().StringSlice("types", []string{}, "Filter by event type (e.g. network_response,console_error)")
25222537
telemetryStream.Flags().Int64("seq", 0, "Resume stream from sequence number (Last-Event-ID)")
25232538
telemetryStream.Flags().StringP("output", "o", "", "Output format: json for newline-delimited JSON envelopes")

cmd/browsers_test.go

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,17 +1926,11 @@ func TestBrowsersTelemetrySet_WhitespaceTolerance(t *testing.T) {
19261926
func TestBrowsersTelemetryStatus_PrintsCategories(t *testing.T) {
19271927
setupStdoutCapture(t)
19281928
fake := &FakeBrowsersService{GetFunc: func(ctx context.Context, id string, query kernel.BrowserGetParams, opts ...option.RequestOption) (*kernel.BrowserGetResponse, error) {
1929-
return &kernel.BrowserGetResponse{
1930-
SessionID: id,
1931-
Telemetry: kernel.BrowserTelemetryConfig{
1932-
Browser: kernel.BrowserTelemetryCategoriesConfig{
1933-
Console: kernel.BrowserTelemetryCategoryConfig{Enabled: true},
1934-
Interaction: kernel.BrowserTelemetryCategoryConfig{Enabled: false},
1935-
Network: kernel.BrowserTelemetryCategoryConfig{Enabled: true},
1936-
Page: kernel.BrowserTelemetryCategoryConfig{Enabled: false},
1937-
},
1938-
},
1939-
}, nil
1929+
var resp kernel.BrowserGetResponse
1930+
if err := json.Unmarshal([]byte(`{"session_id":"session123","telemetry":{"browser":{"console":{"enabled":true},"interaction":{"enabled":false},"network":{"enabled":true},"page":{"enabled":false}}}}`), &resp); err != nil {
1931+
t.Fatalf("unmarshal: %v", err)
1932+
}
1933+
return &resp, nil
19401934
}}
19411935
b := BrowsersCmd{browsers: fake}
19421936

@@ -1950,6 +1944,28 @@ func TestBrowsersTelemetryStatus_PrintsCategories(t *testing.T) {
19501944
assert.Contains(t, out, "page: off")
19511945
}
19521946

1947+
func TestBrowsersTelemetryStatus_VMDefaultsAllOn(t *testing.T) {
1948+
setupStdoutCapture(t)
1949+
fake := &FakeBrowsersService{GetFunc: func(ctx context.Context, id string, query kernel.BrowserGetParams, opts ...option.RequestOption) (*kernel.BrowserGetResponse, error) {
1950+
// API returns browser:{} when using VM defaults — enabled field absent means true per SDK
1951+
var resp kernel.BrowserGetResponse
1952+
if err := json.Unmarshal([]byte(`{"session_id":"session123","telemetry":{"browser":{}}}`), &resp); err != nil {
1953+
t.Fatalf("unmarshal: %v", err)
1954+
}
1955+
return &resp, nil
1956+
}}
1957+
b := BrowsersCmd{browsers: fake}
1958+
1959+
err := b.TelemetryStatus(context.Background(), BrowsersTelemetryStatusInput{Identifier: "session123"})
1960+
1961+
assert.NoError(t, err)
1962+
out := outBuf.String()
1963+
assert.Contains(t, out, "console: on")
1964+
assert.Contains(t, out, "interaction: on")
1965+
assert.Contains(t, out, "network: on")
1966+
assert.Contains(t, out, "page: on")
1967+
}
1968+
19531969
func TestTelemetryStream_UnknownCategoryErrors(t *testing.T) {
19541970
b := BrowsersCmd{browsers: &FakeBrowsersService{}}
19551971

@@ -1978,39 +1994,55 @@ func TestTelemetryStream_UnknownTypeWarns(t *testing.T) {
19781994
assert.Contains(t, outBuf.String(), "unrecognized event type")
19791995
}
19801996

1981-
func TestEventCategory(t *testing.T) {
1982-
cases := map[string]string{
1983-
"network_response": "network",
1984-
"monitor_screenshot": "monitor",
1985-
"console_log": "console",
1986-
"page_navigation": "page",
1987-
"interaction_click": "interaction",
1988-
"nounderscore": "nounderscore",
1997+
func makeEvent(t *testing.T, raw string) kernel.BrowserTelemetryEventUnion {
1998+
t.Helper()
1999+
var ev kernel.BrowserTelemetryEventUnion
2000+
if err := json.Unmarshal([]byte(raw), &ev); err != nil {
2001+
t.Fatalf("makeEvent: %v", err)
2002+
}
2003+
return ev
2004+
}
2005+
2006+
func TestEventCategoryFromRaw(t *testing.T) {
2007+
cases := []struct {
2008+
raw string
2009+
want string
2010+
}{
2011+
// real category field present — used directly
2012+
{`{"type":"monitor_screenshot","category":"system","ts":0}`, "system"},
2013+
{`{"type":"network_response","category":"network","ts":0}`, "network"},
2014+
// no category field — falls back to type prefix
2015+
{`{"type":"console_log","ts":0}`, "console"},
2016+
{`{"type":"page_navigation","ts":0}`, "page"},
2017+
{`{"type":"nounderscore","ts":0}`, "nounderscore"},
19892018
}
1990-
for input, want := range cases {
1991-
assert.Equal(t, want, eventCategory(input), "eventCategory(%q)", input)
2019+
for _, tc := range cases {
2020+
ev := makeEvent(t, tc.raw)
2021+
assert.Equal(t, tc.want, eventCategoryFromRaw(ev), "raw=%s", tc.raw)
19922022
}
19932023
}
19942024

19952025
func TestShouldEmit(t *testing.T) {
19962026
cases := []struct {
19972027
name string
1998-
eventType string
2028+
raw string
19992029
categories []string
20002030
types []string
20012031
want bool
20022032
}{
2003-
{"no filters passes", "network_response", nil, nil, true},
2004-
{"matching category passes", "network_response", []string{"network"}, nil, true},
2005-
{"non-matching category drops", "console_log", []string{"network"}, nil, false},
2006-
{"matching type passes", "console_log", nil, []string{"console_log"}, true},
2007-
{"non-matching type drops", "network_response", nil, []string{"console_log"}, false},
2008-
{"both filters pass when both match", "network_response", []string{"network"}, []string{"network_response"}, true},
2009-
{"both filters drop when type misses", "network_response", []string{"network"}, []string{"console_log"}, false},
2033+
{"no filters passes", `{"type":"network_response","category":"network","ts":0}`, nil, nil, true},
2034+
{"matching category passes", `{"type":"network_response","category":"network","ts":0}`, []string{"network"}, nil, true},
2035+
{"non-matching category drops", `{"type":"console_log","category":"console","ts":0}`, []string{"network"}, nil, false},
2036+
{"system category matches monitor_screenshot", `{"type":"monitor_screenshot","category":"system","ts":0}`, []string{"system"}, nil, true},
2037+
{"matching type passes", `{"type":"console_log","category":"console","ts":0}`, nil, []string{"console_log"}, true},
2038+
{"non-matching type drops", `{"type":"network_response","category":"network","ts":0}`, nil, []string{"console_log"}, false},
2039+
{"both filters pass when both match", `{"type":"network_response","category":"network","ts":0}`, []string{"network"}, []string{"network_response"}, true},
2040+
{"both filters drop when type misses", `{"type":"network_response","category":"network","ts":0}`, []string{"network"}, []string{"console_log"}, false},
20102041
}
20112042
for _, tc := range cases {
20122043
t.Run(tc.name, func(t *testing.T) {
2013-
assert.Equal(t, tc.want, shouldEmit(tc.eventType, tc.categories, tc.types))
2044+
ev := makeEvent(t, tc.raw)
2045+
assert.Equal(t, tc.want, shouldEmit(ev, tc.categories, tc.types))
20142046
})
20152047
}
20162048
}

0 commit comments

Comments
 (0)