Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add page.on('response') #4296

Merged
merged 12 commits into from
Feb 24, 2025
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 @@ -589,6 +589,10 @@ func mapPageOn(vu moduleVU, p *common.Page) func(common.PageOnEventName, sobek.C
mapp: mapRequestEvent,
wait: false,
},
common.EventPageResponseCalled: {
mapp: mapResponseEvent,
wait: false,
},
}
pageOnEvent, ok := pageOnEvents[eventName]
if !ok {
Expand Down
4 changes: 4 additions & 0 deletions internal/js/modules/k6/browser/browser/response_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
"go.k6.io/k6/internal/js/modules/k6/browser/k6ext"
)

func mapResponseEvent(vu moduleVU, event common.PageOnEvent) mapping {
return mapResponse(vu, event.Response)
}

// mapResponse to the JS module.
func mapResponse(vu moduleVU, r *common.Response) mapping {
if r == nil {
Expand Down
38 changes: 29 additions & 9 deletions internal/js/modules/k6/browser/common/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,18 +387,18 @@ func (r *Request) URL() string {

// RemoteAddress contains informationa about a remote target.
type RemoteAddress struct {
IPAddress string `json:"ipAddress"`
Port int64 `json:"port"`
IPAddress string `json:"ipAddress" js:"ipAddress"`
Port int64 `json:"port" js:"port"`
}

// SecurityDetails contains informationa about the security details of a TLS connection.
type SecurityDetails struct {
SubjectName string `json:"subjectName"`
Issuer string `json:"issuer"`
ValidFrom int64 `json:"validFrom"`
ValidTo int64 `json:"validTo"`
Protocol string `json:"protocol"`
SANList []string `json:"sanList"`
SubjectName string `json:"subjectName" js:"subjectName"`
Issuer string `json:"issuer" js:"issuer"`
ValidFrom int64 `json:"validFrom" js:"validFrom"`
ValidTo int64 `json:"validTo" js:"validTo"`
Protocol string `json:"protocol" js:"protocol"`
SANList []string `json:"sanList" js:"sanList"`
}

// Response represents a browser HTTP response.
Expand Down Expand Up @@ -488,7 +488,27 @@ func (r *Response) fetchBody() error {
return nil
}
action := network.GetResponseBody(r.request.requestID)
body, err := action.Do(cdp.WithExecutor(r.ctx, r.request.frame.manager.session))

// Try to fetch the response body. If the request to retrieve the response
// body is too "quick" then the response body is not available. After
// retrying we have a better chance of getting the response body.
var body []byte
var err error
maxRetries := 5
for i := 0; i <= maxRetries; i++ {
body, err = action.Do(cdp.WithExecutor(r.ctx, r.request.frame.manager.session))
if err == nil {
break
}
if strings.Contains(err.Error(), "No data found for resource with given identifier") {
if i == maxRetries {
break
}
time.Sleep(100 * time.Millisecond)
continue
}
break
}
if err != nil {
return fmt.Errorf("fetching response body: %w", err)
}
Expand Down
3 changes: 3 additions & 0 deletions internal/js/modules/k6/browser/common/network_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (c Credentials) IsEmpty() bool {
type eventInterceptor interface {
urlTagName(urlTag string, method string) (string, bool)
onRequest(request *Request)
onResponse(response *Response)
}

// NetworkManager manages all frames in HTML document.
Expand Down Expand Up @@ -664,6 +665,8 @@ func (m *NetworkManager) onResponseReceived(event *network.EventResponseReceived
req.responseMu.Unlock()

m.logger.Debugf("FrameManager:onResponseReceived", "rid:%s rurl:%s", event.RequestID, resp.URL())

m.eventInterceptor.onResponse(resp)
}

func (m *NetworkManager) requestFromID(reqID network.RequestID) (*Request, bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ func (m *EventInterceptorMock) urlTagName(_ string, _ string) (string, bool) {
return "", false
}

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

func (m *EventInterceptorMock) onResponse(_ *Response) {}

func TestNetworkManagerEmitRequestResponseMetricsTimingSkew(t *testing.T) {
t.Parallel()
Expand Down
35 changes: 35 additions & 0 deletions internal/js/modules/k6/browser/common/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const (

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

// EventPageResponseCalled represents the page.on('response') event.
EventPageResponseCalled PageOnEventName = "response"
)

// MediaType represents the type of media to emulate.
Expand Down Expand Up @@ -514,6 +517,35 @@ func (p *Page) onRequest(request *Request) {
}
}

// onResponse will call the handlers for the page.on('response') event.
func (p *Page) onResponse(resp *Response) {
p.logger.Debugf("Page:onResponse", "sid:%v url:%v", p.sessionID(), resp.URL())

if !hasPageOnHandler(p, EventPageResponseCalled) {
return
}

p.eventHandlersMu.RLock()
defer p.eventHandlersMu.RUnlock()
for _, h := range p.eventHandlers[EventPageResponseCalled] {
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{
Response: resp,
})
}()
if err != nil {
p.logger.Warnf("onResponse", "handler returned an error: %v", err)
return
}
}
}

func (p *Page) onConsoleAPICalled(event *runtime.EventConsoleAPICalled) {
if !hasPageOnHandler(p, EventPageConsoleAPICalled) {
return
Expand Down Expand Up @@ -1202,6 +1234,9 @@ type PageOnEvent struct {
// Request is the read only request that is about to be sent from the
// browser to the WuT.
Request *Request

// Response is the read only response that was received from the WuT.
Response *Response
}

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