Skip to content

Commit

Permalink
Add page.on('request') (#4290)
Browse files Browse the repository at this point in the history
* Add request to the PageOnEvent type
This is where the request will be set for the page.on handler to read.

* Add a new page on request event

* Add a onRequest method

This method is to be called when a new request is about to be sent
from chrome to the WuT. It takes the request and send it to the page.on
handler where the user test script can read the request data from.

* Add call to onRequest from network manager

* Update the page on mapping with the request event

* Refactor metricInterceptor to eventInterceptor

* Fix request.Timing

This is a temporary fix.

When working with page.on('request') the timing values haven't yet been
received, which are part of the response object. This will need to be
fixed later when we can wait for a response while working with
page.on('request').

* Add a test for page.on('request')

* Remove call to Body.Close() from server

* Refactor test to work with tb.url

* Refactor test to reduce boilerplate code

The issue was that HeadersArray was out of order. If they're put in
order then the comparison can be made.

* Refactor test to work with slices.IndexFunc
  • Loading branch information
ankur22 authored Feb 19, 2025
1 parent bfc1a53 commit 4a6887a
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 19 deletions.
4 changes: 4 additions & 0 deletions internal/js/modules/k6/browser/browser/page_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,10 @@ func mapPageOn(vu moduleVU, p *common.Page) func(common.PageOnEventName, sobek.C
init: prepK6BrowserRegExChecker(rt),
wait: true,
},
common.EventPageRequestCalled: {
mapp: mapRequestEvent,
wait: false,
},
}
pageOnEvent, ok := pageOnEvents[eventName]
if !ok {
Expand Down
6 changes: 6 additions & 0 deletions internal/js/modules/k6/browser/browser/request_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
"go.k6.io/k6/internal/js/modules/k6/browser/k6ext"
)

func mapRequestEvent(vu moduleVU, event common.PageOnEvent) mapping {
r := event.Request

return mapRequest(vu, r)
}

// mapRequest to the JS module.
func mapRequest(vu moduleVU, r *common.Request) mapping {
rt := vu.Runtime()
Expand Down
4 changes: 4 additions & 0 deletions internal/js/modules/k6/browser/common/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ type resourceTiming struct {

// Timing returns the request timing information.
func (r *Request) Timing() *resourceTiming {
if r.response == nil {
return nil
}

timing := r.response.timing

return &resourceTiming{
Expand Down
35 changes: 19 additions & 16 deletions internal/js/modules/k6/browser/common/network_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,25 @@ func (c Credentials) IsEmpty() bool {
return c == (Credentials{})
}

type metricInterceptor interface {
type eventInterceptor interface {
urlTagName(urlTag string, method string) (string, bool)
onRequest(request *Request)
}

// NetworkManager manages all frames in HTML document.
type NetworkManager struct {
BaseEventEmitter

ctx context.Context
logger *log.Logger
session session
parent *NetworkManager
frameManager *FrameManager
credentials Credentials
resolver k6netext.Resolver
vu k6modules.VU
customMetrics *k6ext.CustomMetrics
mi metricInterceptor
ctx context.Context
logger *log.Logger
session session
parent *NetworkManager
frameManager *FrameManager
credentials Credentials
resolver k6netext.Resolver
vu k6modules.VU
customMetrics *k6ext.CustomMetrics
eventInterceptor eventInterceptor

// TODO: manage inflight requests separately (move them between the two maps
// as they transition from inflight -> completed)
Expand All @@ -84,7 +85,7 @@ func NewNetworkManager(
s session,
fm *FrameManager,
parent *NetworkManager,
mi metricInterceptor,
ei eventInterceptor,
) (*NetworkManager, error) {
vu := k6ext.GetVU(ctx)
state := vu.State()
Expand All @@ -110,7 +111,7 @@ func NewNetworkManager(
attemptedAuth: make(map[fetch.RequestID]bool),
extraHTTPHeaders: make(map[string]string),
networkProfile: NewNetworkProfile(),
mi: mi,
eventInterceptor: ei,
}
m.initEvents()
if err := m.initDomains(); err != nil {
Expand Down Expand Up @@ -178,7 +179,7 @@ func (m *NetworkManager) emitRequestMetrics(req *Request) {
tags = tags.With("method", req.method)
}
if state.Options.SystemTags.Has(k6metrics.TagURL) {
tags = handleURLTag(m.mi, req.URL(), req.method, tags)
tags = handleURLTag(m.eventInterceptor, req.URL(), req.method, tags)
}
tags = tags.With("resource_type", req.ResourceType())

Expand Down Expand Up @@ -232,7 +233,7 @@ func (m *NetworkManager) emitResponseMetrics(resp *Response, req *Request) {
tags = tags.With("method", req.method)
}
if state.Options.SystemTags.Has(k6metrics.TagURL) {
tags = handleURLTag(m.mi, url, req.method, tags)
tags = handleURLTag(m.eventInterceptor, url, req.method, tags)
}
if state.Options.SystemTags.Has(k6metrics.TagIP) {
tags = tags.With("ip", ipAddress)
Expand Down Expand Up @@ -280,7 +281,7 @@ func (m *NetworkManager) emitResponseMetrics(resp *Response, req *Request) {
// handleURLTag will check if the url tag needs to be grouped by testing
// against user supplied regex. If there's a match a user supplied name will
// be used instead of the url for the url tag, otherwise the url will be used.
func handleURLTag(mi metricInterceptor, url string, method string, tags *k6metrics.TagSet) *k6metrics.TagSet {
func handleURLTag(mi eventInterceptor, url string, method string, tags *k6metrics.TagSet) *k6metrics.TagSet {
if newTagName, urlMatched := mi.urlTagName(url, method); urlMatched {
tags = tags.With("url", newTagName)
tags = tags.With("name", newTagName)
Expand Down Expand Up @@ -511,6 +512,8 @@ func (m *NetworkManager) onRequest(event *network.EventRequestWillBeSent, interc
m.reqsMu.Unlock()
m.emitRequestMetrics(req)
m.frameManager.requestStarted(req)

m.eventInterceptor.onRequest(req)
}

func (m *NetworkManager) onRequestPaused(event *fetch.EventRequestPaused) {
Expand Down
8 changes: 5 additions & 3 deletions internal/js/modules/k6/browser/common/network_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,14 @@ func TestOnRequestPausedBlockedIPs(t *testing.T) {
}
}

type MetricInterceptorMock struct{}
type EventInterceptorMock struct{}

func (m *MetricInterceptorMock) urlTagName(_ string, _ string) (string, bool) {
func (m *EventInterceptorMock) urlTagName(_ string, _ string) (string, bool) {
return "", false
}

func (m *EventInterceptorMock) onRequest(request *Request) {}

func TestNetworkManagerEmitRequestResponseMetricsTimingSkew(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -277,7 +279,7 @@ func TestNetworkManagerEmitRequestResponseMetricsTimingSkew(t *testing.T) {

var (
vu = k6test.NewVU(t)
nm = &NetworkManager{ctx: vu.Context(), vu: vu, customMetrics: k6m, mi: &MetricInterceptorMock{}}
nm = &NetworkManager{ctx: vu.Context(), vu: vu, customMetrics: k6m, eventInterceptor: &EventInterceptorMock{}}
)
vu.ActivateVU()

Expand Down
33 changes: 33 additions & 0 deletions internal/js/modules/k6/browser/common/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const (

// EventPageMetricCalled represents the page.on('metric') event.
EventPageMetricCalled PageOnEventName = "metric"

// EventPageRequestCalled represents the page.on('request') event.
EventPageRequestCalled PageOnEventName = "request"
)

// MediaType represents the type of media to emulate.
Expand Down Expand Up @@ -485,6 +488,32 @@ func (p *Page) urlTagName(url string, method string) (string, bool) {
return newTagName, urlMatched
}

func (p *Page) onRequest(request *Request) {
if !hasPageOnHandler(p, EventPageRequestCalled) {
return
}

p.eventHandlersMu.RLock()
defer p.eventHandlersMu.RUnlock()
for _, h := range p.eventHandlers[EventPageRequestCalled] {
err := func() error {
// Handlers can register other handlers, so we need to
// unlock the mutex before calling the next handler.
p.eventHandlersMu.RUnlock()
defer p.eventHandlersMu.RLock()

// Call and wait for the handler to complete.
return h(PageOnEvent{
Request: request,
})
}()
if err != nil {
p.logger.Warnf("onRequest", "handler returned an error: %v", err)
return
}
}
}

func (p *Page) onConsoleAPICalled(event *runtime.EventConsoleAPICalled) {
if !hasPageOnHandler(p, EventPageConsoleAPICalled) {
return
Expand Down Expand Up @@ -1169,6 +1198,10 @@ type PageOnEvent struct {

// Metric is the metric event event.
Metric *MetricEvent

// Request is the read only request that is about to be sent from the
// browser to the WuT.
Request *Request
}

// On subscribes to a page event for which the given handler will be executed
Expand Down
Loading

0 comments on commit 4a6887a

Please sign in to comment.