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

Async Response #1364

Merged
merged 8 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions browser/mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,21 +479,22 @@ type requestAPI interface {
// responseAPI is the interface of an HTTP response.
type responseAPI interface {
AllHeaders() map[string]string
Body() goja.ArrayBuffer
Body() ([]byte, error)
Frame() *common.Frame
HeaderValue(string) goja.Value
HeaderValue(string) (string, bool)
HeaderValues(string) []string
Headers() map[string]string
HeadersArray() []common.HTTPHeader
JSON() goja.Value
JSON() (any, error)
Ok() bool
Request() *common.Request
SecurityDetails() goja.Value
ServerAddr() goja.Value
SecurityDetails() *common.SecurityDetails
ServerAddr() *common.RemoteAddress
Size() common.HTTPMessageSize
Status() int64
StatusText() string
URL() string
Text() (string, error)
}

// locatorAPI represents a way to find element(s) on a page at any moment.
Expand Down
96 changes: 72 additions & 24 deletions browser/response_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,85 @@ import (
"github.com/dop251/goja"

"github.com/grafana/xk6-browser/common"
"github.com/grafana/xk6-browser/k6ext"
)

// mapResponse to the JS module.
func mapResponse(vu moduleVU, r *common.Response) mapping {
func mapResponse(vu moduleVU, r *common.Response) mapping { //nolint:funlen
if r == nil {
return nil
}
rt := vu.Runtime()
maps := mapping{
"allHeaders": r.AllHeaders,
"body": r.Body,
"frame": func() *goja.Object {
mf := mapFrame(vu, r.Frame())
return rt.ToValue(mf).ToObject(rt)
},
"headerValue": r.HeaderValue,
"headerValues": r.HeaderValues,
"headers": r.Headers,
"headersArray": r.HeadersArray,
"json": r.JSON,
"ok": r.Ok,
"request": func() *goja.Object {
mr := mapRequest(vu, r.Request())
return rt.ToValue(mr).ToObject(rt)
},
"securityDetails": r.SecurityDetails,
"serverAddr": r.ServerAddr,
"size": r.Size,
"status": r.Status,
"statusText": r.StatusText,
"url": r.URL,
"allHeaders": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.AllHeaders(), nil
})
},
"body": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
body, err := r.Body()
if err != nil {
return nil, err //nolint: wrapcheck
}
buf := vu.Runtime().NewArrayBuffer(body)
return &buf, nil
})
},
"frame": func() mapping {
return mapFrame(vu, r.Frame())
},
"headerValue": func(name string) *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
v, ok := r.HeaderValue(name)
if !ok {
return nil, nil
}
return v, nil
})
},
"headerValues": func(name string) *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.HeaderValues(name), nil
})
},
"headers": r.Headers,
"headersArray": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.HeadersArray(), nil
})
},
"json": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.JSON() //nolint: wrapcheck
})
},
"ok": r.Ok,
"request": func() mapping {
return mapRequest(vu, r.Request())
},
"securityDetails": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.SecurityDetails(), nil
})
},
"serverAddr": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.ServerAddr(), nil
})
},
"size": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.Size(), nil
})
},
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
"status": r.Status,
"statusText": r.StatusText,
"url": r.URL,
"text": func() *goja.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
return r.Text() //nolint:wrapcheck
})
},
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
}

return maps
Expand Down
71 changes: 35 additions & 36 deletions common/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,18 +418,19 @@ func (r *Response) AllHeaders() map[string]string {
return headers
}

// Body returns the response body as a binary buffer.
func (r *Response) Body() goja.ArrayBuffer {
// Body returns the response body as a bytes buffer.
func (r *Response) Body() ([]byte, error) {
if r.status >= 300 && r.status <= 399 {
k6ext.Panic(r.ctx, "Response body is unavailable for redirect responses")
return nil, fmt.Errorf("response body is unavailable for redirect responses")
}
if err := r.fetchBody(); err != nil {
k6ext.Panic(r.ctx, "getting response body: %w", err)
return nil, fmt.Errorf("getting response body: %w", err)
}

r.bodyMu.RLock()
defer r.bodyMu.RUnlock()
rt := r.vu.Runtime()
return rt.NewArrayBuffer(r.body)

return r.body, nil
}

// bodySize returns the size in bytes of the response body.
Expand All @@ -456,14 +457,11 @@ func (r *Response) Frame() *Frame {
}

// HeaderValue returns the value of the given header.
func (r *Response) HeaderValue(name string) goja.Value {
// Returns true if the header is present, false otherwise.
func (r *Response) HeaderValue(name string) (string, bool) {
headers := r.AllHeaders()
val, ok := headers[name]
if !ok {
return goja.Null()
}
rt := r.vu.Runtime()
return rt.ToValue(val)
v, ok := headers[name]
return v, ok
}

// HeaderValues returns the values of the given header.
Expand Down Expand Up @@ -508,23 +506,24 @@ func (r *Response) HeadersArray() []HTTPHeader {
}

// JSON returns the response body as JSON data.
func (r *Response) JSON() goja.Value {
if r.cachedJSON == nil {
if err := r.fetchBody(); err != nil {
k6ext.Panic(r.ctx, "getting response body: %w", err)
}
func (r *Response) JSON() (any, error) {
if r.cachedJSON != nil {
return r.cachedJSON, nil
}
if err := r.fetchBody(); err != nil {
return nil, fmt.Errorf("getting response body: %w", err)
}

var v any
r.bodyMu.RLock()
defer r.bodyMu.RUnlock()
if err := json.Unmarshal(r.body, &v); err != nil {
k6ext.Panic(r.ctx, "unmarshalling response body to JSON: %w", err)
}
r.cachedJSON = v
r.bodyMu.RLock()
defer r.bodyMu.RUnlock()

var v any
if err := json.Unmarshal(r.body, &v); err != nil {
return nil, fmt.Errorf("unmarshalling response body to JSON: %w", err)
}
rt := r.vu.Runtime()
r.cachedJSON = v

return rt.ToValue(r.cachedJSON)
return v, nil
}

// Ok returns true if status code of response if considered ok, otherwise returns false.
Expand All @@ -541,15 +540,13 @@ func (r *Response) Request() *Request {
}

// SecurityDetails returns the security details of the response.
func (r *Response) SecurityDetails() goja.Value {
rt := r.vu.Runtime()
return rt.ToValue(r.securityDetails)
func (r *Response) SecurityDetails() *SecurityDetails {
return r.securityDetails
}

// ServerAddr returns the remote address of the server.
func (r *Response) ServerAddr() goja.Value {
rt := r.vu.Runtime()
return rt.ToValue(r.remoteAddress)
func (r *Response) ServerAddr() *RemoteAddress {
return r.remoteAddress
}

// Size returns the size in bytes of the response.
Expand All @@ -571,13 +568,15 @@ func (r *Response) StatusText() string {
}

// Text returns the response body as a string.
func (r *Response) Text() string {
func (r *Response) Text() (string, error) {
if err := r.fetchBody(); err != nil {
k6ext.Panic(r.ctx, "getting response body as text: %w", err)
return "", fmt.Errorf("getting response body as text: %w", err)
}

r.bodyMu.RLock()
defer r.bodyMu.RUnlock()
return string(r.body)

return string(r.body), nil
}

// URL returns the request URL.
Expand Down
8 changes: 7 additions & 1 deletion tests/browser_context_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,17 @@ func TestBrowserContextOptionsExtraHTTPHeaders(t *testing.T) {
return err
}
require.NotNil(t, resp)

responseBody, err := resp.Body()
require.NoError(t, err)

var body struct{ Headers map[string][]string }
require.NoError(t, json.Unmarshal(resp.Body().Bytes(), &body))
require.NoError(t, json.Unmarshal(responseBody, &body))

h := body.Headers["Some-Header"]
require.NotEmpty(t, h)
assert.Equal(t, "Some-Value", h[0])

return nil
})
require.NoError(t, err)
Expand Down
5 changes: 4 additions & 1 deletion tests/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,8 +679,11 @@ func TestPageSetExtraHTTPHeaders(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)

responseBody, err := resp.Body()
require.NoError(t, err)

var body struct{ Headers map[string][]string }
err = json.Unmarshal(resp.Body().Bytes(), &body)
err = json.Unmarshal(responseBody, &body)
require.NoError(t, err)

h := body.Headers["Some-Header"]
Expand Down
Loading