diff --git a/js/modules/k6/browser/common/browser.go b/js/modules/k6/browser/common/browser.go index 83108b67075..c0f167a2b95 100644 --- a/js/modules/k6/browser/common/browser.go +++ b/js/modules/k6/browser/common/browser.go @@ -493,12 +493,22 @@ func (b *Browser) newPageInContext(id cdp.BrowserContextID) (*Page, error) { page = b.pages[tid] b.pagesMu.RUnlock() case <-ctx.Done(): + b.logger.Debugf("Browser:newPageInContext:<-ctx.Done", "tid:%v bctxid:%v err:%v", tid, id, ctx.Err()) + } + + if err = ctx.Err(); err != nil { err = &k6ext.UserFriendlyError{ Err: ctx.Err(), Timeout: b.browserOpts.Timeout, } - b.logger.Debugf("Browser:newPageInContext:<-ctx.Done", "tid:%v bctxid:%v err:%v", tid, id, err) } + + if err == nil && page == nil { + err = &k6ext.UserFriendlyError{ + Err: errors.New("can't fetch the page for unknown reason"), + } + } + return page, err } diff --git a/js/modules/k6/browser/common/browser_context.go b/js/modules/k6/browser/common/browser_context.go index 6e0b842f969..c051cb86951 100644 --- a/js/modules/k6/browser/common/browser_context.go +++ b/js/modules/k6/browser/common/browser_context.go @@ -262,18 +262,7 @@ func (b *BrowserContext) NewPage() (*Page, error) { return nil, err } - var ( - bctxid cdp.BrowserContextID - ptid target.ID - ) - if b != nil { - bctxid = b.id - } - if p != nil { - ptid = p.targetID - } - b.logger.Debugf("BrowserContext:NewPage:return", "bctxid:%v ptid:%s", bctxid, ptid) - + b.logger.Debugf("BrowserContext:NewPage:return", "bctxid:%v ptid:%s", b.id, p.targetID) return p, nil } diff --git a/js/modules/k6/browser/common/http.go b/js/modules/k6/browser/common/http.go index 7c55209cd3d..f83f245a66d 100644 --- a/js/modules/k6/browser/common/http.go +++ b/js/modules/k6/browser/common/http.go @@ -20,6 +20,30 @@ import ( k6modules "go.k6.io/k6/js/modules" ) +// These ResourceTypes are duplicates of CDP's network.ResourceType. We want to work +// with our version of ResourceType to catch any breaking changes early. +const ( + ResourceTypeDocument string = "Document" + ResourceTypeStylesheet string = "Stylesheet" + ResourceTypeImage string = "Image" + ResourceTypeMedia string = "Media" + ResourceTypeFont string = "Font" + ResourceTypeScript string = "Script" + ResourceTypeTextTrack string = "TextTrack" + ResourceTypeXHR string = "XHR" + ResourceTypeFetch string = "Fetch" + ResourceTypePrefetch string = "Prefetch" + ResourceTypeEventSource string = "EventSource" + ResourceTypeWebSocket string = "WebSocket" + ResourceTypeManifest string = "Manifest" + ResourceTypeSignedExchange string = "SignedExchange" + ResourceTypePing string = "Ping" + ResourceTypeCSPViolationReport string = "CSPViolationReport" + ResourceTypePreflight string = "Preflight" + ResourceTypeOther string = "Other" + ResourceTypeUnknown string = "Unknown" +) + // HTTPHeader is a single HTTP header. type HTTPHeader struct { Name string `json:"name"` @@ -88,7 +112,7 @@ type NewRequestParams struct { } // NewRequest creates a new HTTP request. -func NewRequest(ctx context.Context, rp NewRequestParams) (*Request, error) { +func NewRequest(ctx context.Context, logger *log.Logger, rp NewRequestParams) (*Request, error) { ev := rp.event documentID := cdp.LoaderID("") @@ -129,7 +153,7 @@ func NewRequest(ctx context.Context, rp NewRequestParams) (*Request, error) { requestID: ev.RequestID, method: ev.Request.Method, postDataEntries: pd, - resourceType: ev.Type.String(), + resourceType: validateResourceType(logger, ev.Type.String()), isNavigationRequest: isNavigationRequest, allowInterception: rp.allowInterception, interceptionID: rp.interceptionID, @@ -150,6 +174,41 @@ func NewRequest(ctx context.Context, rp NewRequestParams) (*Request, error) { return &r, nil } +// validateResourceType will validate network.ResourceType string values against our own +// ResourceType string values. +// - If a new network.ResourceType is added, this will log a warn and return +// ResourceTypeUnknown. +// - If an existing network.ResourceType is amended, this will log a warn and return +// ResourceTypeUnknown. +// - If a network.ResourceType is deleted then we will get a compilation error. +func validateResourceType(logger *log.Logger, t string) string { + switch t { + case ResourceTypeDocument: + case ResourceTypeStylesheet: + case ResourceTypeImage: + case ResourceTypeMedia: + case ResourceTypeFont: + case ResourceTypeScript: + case ResourceTypeTextTrack: + case ResourceTypeXHR: + case ResourceTypeFetch: + case ResourceTypePrefetch: + case ResourceTypeEventSource: + case ResourceTypeWebSocket: + case ResourceTypeManifest: + case ResourceTypeSignedExchange: + case ResourceTypePing: + case ResourceTypeCSPViolationReport: + case ResourceTypePreflight: + case ResourceTypeOther: + default: + t = ResourceTypeUnknown + logger.Warnf("http:resourceType", "unknown network.ResourceType %q detected", t) + } + + return t +} + func (r *Request) getFrame() *Frame { return r.frame } diff --git a/js/modules/k6/browser/common/http_test.go b/js/modules/k6/browser/common/http_test.go index 7f770246b1a..8869a699c14 100644 --- a/js/modules/k6/browser/common/http_test.go +++ b/js/modules/k6/browser/common/http_test.go @@ -1,6 +1,7 @@ package common import ( + "strings" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "go.k6.io/k6/js/modules/k6/browser/k6ext/k6test" + "go.k6.io/k6/js/modules/k6/browser/log" ) func TestRequest(t *testing.T) { @@ -30,7 +32,7 @@ func TestRequest(t *testing.T) { WallTime: &wt, } vu := k6test.NewVU(t) - req, err := NewRequest(vu.Context(), NewRequestParams{ + req, err := NewRequest(vu.Context(), log.NewNullLogger(), NewRequestParams{ event: evt, interceptionID: "intercept", }) @@ -51,7 +53,7 @@ func TestRequest(t *testing.T) { WallTime: &wt, } vu := k6test.NewVU(t) - req, err := NewRequest(vu.Context(), NewRequestParams{ + req, err := NewRequest(vu.Context(), log.NewNullLogger(), NewRequestParams{ event: evt, interceptionID: "intercept", }) @@ -124,3 +126,44 @@ func TestResponse(t *testing.T) { assert.Equal(t, "value", got) }) } + +func TestValidateResourceType(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + want string + }{ + {name: ResourceTypeDocument, input: network.ResourceTypeDocument.String(), want: ResourceTypeDocument}, + {name: ResourceTypeStylesheet, input: network.ResourceTypeStylesheet.String(), want: ResourceTypeStylesheet}, + {name: ResourceTypeImage, input: network.ResourceTypeImage.String(), want: ResourceTypeImage}, + {name: ResourceTypeMedia, input: network.ResourceTypeMedia.String(), want: ResourceTypeMedia}, + {name: ResourceTypeFont, input: network.ResourceTypeFont.String(), want: ResourceTypeFont}, + {name: ResourceTypeScript, input: network.ResourceTypeScript.String(), want: ResourceTypeScript}, + {name: ResourceTypeTextTrack, input: network.ResourceTypeTextTrack.String(), want: ResourceTypeTextTrack}, + {name: ResourceTypeXHR, input: network.ResourceTypeXHR.String(), want: ResourceTypeXHR}, + {name: ResourceTypeFetch, input: network.ResourceTypeFetch.String(), want: ResourceTypeFetch}, + {name: ResourceTypePrefetch, input: network.ResourceTypePrefetch.String(), want: ResourceTypePrefetch}, + {name: ResourceTypeEventSource, input: network.ResourceTypeEventSource.String(), want: ResourceTypeEventSource}, + {name: ResourceTypeWebSocket, input: network.ResourceTypeWebSocket.String(), want: ResourceTypeWebSocket}, + {name: ResourceTypeManifest, input: network.ResourceTypeManifest.String(), want: ResourceTypeManifest}, + {name: ResourceTypeSignedExchange, input: network.ResourceTypeSignedExchange.String(), want: ResourceTypeSignedExchange}, + {name: ResourceTypePing, input: network.ResourceTypePing.String(), want: ResourceTypePing}, + {name: ResourceTypeCSPViolationReport, input: network.ResourceTypeCSPViolationReport.String(), want: ResourceTypeCSPViolationReport}, + {name: ResourceTypePreflight, input: network.ResourceTypePreflight.String(), want: ResourceTypePreflight}, + {name: ResourceTypeOther, input: network.ResourceTypeOther.String(), want: ResourceTypeOther}, + {name: "fake", input: "fake", want: ResourceTypeUnknown}, + {name: "amended_existing", input: strings.ToLower(network.ResourceTypeOther.String()), want: ResourceTypeUnknown}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := validateResourceType(log.NewNullLogger(), tt.input) + assert.Equal(t, got, tt.want) + }) + } +} diff --git a/js/modules/k6/browser/common/network_manager.go b/js/modules/k6/browser/common/network_manager.go index a74a3b7de14..266e380b5af 100644 --- a/js/modules/k6/browser/common/network_manager.go +++ b/js/modules/k6/browser/common/network_manager.go @@ -180,6 +180,7 @@ func (m *NetworkManager) emitRequestMetrics(req *Request) { if state.Options.SystemTags.Has(k6metrics.TagURL) { tags = handleURLTag(m.mi, req.URL(), req.method, tags) } + tags = tags.With("resource_type", req.ResourceType()) k6metrics.PushIfNotDone(m.vu.Context(), state.Samples, k6metrics.ConnectedSamples{ Samples: []k6metrics.Sample{ @@ -192,6 +193,7 @@ func (m *NetworkManager) emitRequestMetrics(req *Request) { }) } +//nolint:funlen func (m *NetworkManager) emitResponseMetrics(resp *Response, req *Request) { state := m.vu.State() @@ -246,6 +248,7 @@ func (m *NetworkManager) emitResponseMetrics(resp *Response, req *Request) { tags = tags.With("from_cache", strconv.FormatBool(fromCache)) tags = tags.With("from_prefetch_cache", strconv.FormatBool(fromPreCache)) tags = tags.With("from_service_worker", strconv.FormatBool(fromSvcWrk)) + tags = tags.With("resource_type", req.ResourceType()) k6metrics.PushIfNotDone(m.vu.Context(), state.Samples, k6metrics.ConnectedSamples{ Samples: []k6metrics.Sample{ @@ -488,7 +491,7 @@ func (m *NetworkManager) onRequest(event *network.EventRequestWillBeSent, interc event.Request.URL, event.Request.Method, event.Initiator.Type, event.FrameID) } - req, err := NewRequest(m.ctx, NewRequestParams{ + req, err := NewRequest(m.ctx, m.logger, NewRequestParams{ event: event, frame: frame, redirectChain: redirectChain, diff --git a/js/modules/k6/browser/common/network_manager_test.go b/js/modules/k6/browser/common/network_manager_test.go index 07861162f04..ca3f8bfee7a 100644 --- a/js/modules/k6/browser/common/network_manager_test.go +++ b/js/modules/k6/browser/common/network_manager_test.go @@ -281,7 +281,7 @@ func TestNetworkManagerEmitRequestResponseMetricsTimingSkew(t *testing.T) { ) vu.ActivateVU() - req, err := NewRequest(vu.Context(), NewRequestParams{ + req, err := NewRequest(vu.Context(), log.NewNullLogger(), NewRequestParams{ event: &network.EventRequestWillBeSent{ Request: &network.Request{}, Timestamp: (*cdp.MonotonicTime)(&tt.req.ts),