From 9f405f78eefda14cc10f70986e45b60257d33fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:32:15 +0300 Subject: [PATCH 1/8] Async response.body --- browser/mapping_test.go | 2 +- browser/response_mapping.go | 12 +++++++++++- common/http.go | 13 +++++++------ tests/browser_context_options_test.go | 8 +++++++- tests/page_test.go | 5 ++++- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index 4f2c61f3f..706670ab3 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -479,7 +479,7 @@ 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 HeaderValues(string) []string diff --git a/browser/response_mapping.go b/browser/response_mapping.go index c82abb0c2..9b57f99f2 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -4,6 +4,7 @@ import ( "github.com/dop251/goja" "github.com/grafana/xk6-browser/common" + "github.com/grafana/xk6-browser/k6ext" ) // mapResponse to the JS module. @@ -14,7 +15,16 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { rt := vu.Runtime() maps := mapping{ "allHeaders": r.AllHeaders, - "body": r.Body, + "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() *goja.Object { mf := mapFrame(vu, r.Frame()) return rt.ToValue(mf).ToObject(rt) diff --git a/common/http.go b/common/http.go index 13e5d2742..fc1b1c7f1 100644 --- a/common/http.go +++ b/common/http.go @@ -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. diff --git a/tests/browser_context_options_test.go b/tests/browser_context_options_test.go index 0e3ab30a6..ee062036f 100644 --- a/tests/browser_context_options_test.go +++ b/tests/browser_context_options_test.go @@ -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) diff --git a/tests/page_test.go b/tests/page_test.go index 188e56efd..50a97f904 100644 --- a/tests/page_test.go +++ b/tests/page_test.go @@ -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"] From c1623df2b1071515242b362b0b4698ed2039911f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:39:19 +0300 Subject: [PATCH 2/8] Async response header methods --- browser/mapping_test.go | 2 +- browser/response_mapping.go | 34 +++++++++++++++++++++++++++------- common/http.go | 11 ++++------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index 706670ab3..ada30560e 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -481,7 +481,7 @@ type responseAPI interface { AllHeaders() map[string]string Body() ([]byte, error) Frame() *common.Frame - HeaderValue(string) goja.Value + HeaderValue(string) (string, bool) HeaderValues(string) []string Headers() map[string]string HeadersArray() []common.HTTPHeader diff --git a/browser/response_mapping.go b/browser/response_mapping.go index 9b57f99f2..2cfa2730d 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -14,7 +14,11 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { } rt := vu.Runtime() maps := mapping{ - "allHeaders": r.AllHeaders, + "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() @@ -29,12 +33,28 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { 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, + "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": r.JSON, + "ok": r.Ok, "request": func() *goja.Object { mr := mapRequest(vu, r.Request()) return rt.ToValue(mr).ToObject(rt) diff --git a/common/http.go b/common/http.go index fc1b1c7f1..d3ac692ce 100644 --- a/common/http.go +++ b/common/http.go @@ -457,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. From 49fb89155071c8eab161e9be097b16232b60c3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:44:59 +0300 Subject: [PATCH 3/8] Async response.json --- browser/mapping_test.go | 2 +- browser/response_mapping.go | 8 ++++++-- common/http.go | 29 +++++++++++++++-------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index ada30560e..07a6d80be 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -485,7 +485,7 @@ type responseAPI interface { HeaderValues(string) []string Headers() map[string]string HeadersArray() []common.HTTPHeader - JSON() goja.Value + JSON() (any, error) Ok() bool Request() *common.Request SecurityDetails() goja.Value diff --git a/browser/response_mapping.go b/browser/response_mapping.go index 2cfa2730d..d525a2cf5 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -53,8 +53,12 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { return r.HeadersArray(), nil }) }, - "json": r.JSON, - "ok": r.Ok, + "json": func() *goja.Promise { + return k6ext.Promise(vu.Context(), func() (any, error) { + return r.JSON() //nolint: wrapcheck + }) + }, + "ok": r.Ok, "request": func() *goja.Object { mr := mapRequest(vu, r.Request()) return rt.ToValue(mr).ToObject(rt) diff --git a/common/http.go b/common/http.go index d3ac692ce..0d3966c42 100644 --- a/common/http.go +++ b/common/http.go @@ -506,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. From 2fdfe602da38f97e2b2b0fbf054d33a8a2139289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:46:23 +0300 Subject: [PATCH 4/8] Async response.securityDetails --- browser/mapping_test.go | 2 +- browser/response_mapping.go | 16 ++++++++++------ common/http.go | 5 ++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index 07a6d80be..34cd032aa 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -488,7 +488,7 @@ type responseAPI interface { JSON() (any, error) Ok() bool Request() *common.Request - SecurityDetails() goja.Value + SecurityDetails() *common.SecurityDetails ServerAddr() goja.Value Size() common.HTTPMessageSize Status() int64 diff --git a/browser/response_mapping.go b/browser/response_mapping.go index d525a2cf5..f19713e5b 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -63,12 +63,16 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { 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, + "securityDetails": func() *goja.Promise { + return k6ext.Promise(vu.Context(), func() (any, error) { + return r.SecurityDetails(), nil + }) + }, + "serverAddr": r.ServerAddr, + "size": r.Size, + "status": r.Status, + "statusText": r.StatusText, + "url": r.URL, } return maps diff --git a/common/http.go b/common/http.go index 0d3966c42..fe96ba920 100644 --- a/common/http.go +++ b/common/http.go @@ -540,9 +540,8 @@ 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. From 698892ef68b3d0d684d08a1bc1dc7cec86b5996b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:47:14 +0300 Subject: [PATCH 5/8] Async response.serverAddr --- browser/mapping_test.go | 2 +- browser/response_mapping.go | 6 +++++- common/http.go | 5 ++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index 34cd032aa..e5499e1cb 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -489,7 +489,7 @@ type responseAPI interface { Ok() bool Request() *common.Request SecurityDetails() *common.SecurityDetails - ServerAddr() goja.Value + ServerAddr() *common.RemoteAddress Size() common.HTTPMessageSize Status() int64 StatusText() string diff --git a/browser/response_mapping.go b/browser/response_mapping.go index f19713e5b..430ffffbf 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -68,7 +68,11 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { return r.SecurityDetails(), nil }) }, - "serverAddr": r.ServerAddr, + "serverAddr": func() *goja.Promise { + return k6ext.Promise(vu.Context(), func() (any, error) { + return r.ServerAddr(), nil + }) + }, "size": r.Size, "status": r.Status, "statusText": r.StatusText, diff --git a/common/http.go b/common/http.go index fe96ba920..91c9ab2b7 100644 --- a/common/http.go +++ b/common/http.go @@ -545,9 +545,8 @@ func (r *Response) SecurityDetails() *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. From 59ebf6744db6a4ad4673f1f3bfcdc3f7df665d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:51:56 +0300 Subject: [PATCH 6/8] Async response size --- browser/response_mapping.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/browser/response_mapping.go b/browser/response_mapping.go index 430ffffbf..48d5c9bd0 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -73,7 +73,11 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { return r.ServerAddr(), nil }) }, - "size": r.Size, + "size": func() *goja.Promise { + return k6ext.Promise(vu.Context(), func() (any, error) { + return r.Size(), nil + }) + }, "status": r.Status, "statusText": r.StatusText, "url": r.URL, From 5d44fa4c6a08a624a5370e7f567af0b3cd118aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 18:54:24 +0300 Subject: [PATCH 7/8] Async response text --- browser/mapping_test.go | 1 + browser/response_mapping.go | 7 ++++++- common/http.go | 8 +++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/browser/mapping_test.go b/browser/mapping_test.go index e5499e1cb..037e4fddb 100644 --- a/browser/mapping_test.go +++ b/browser/mapping_test.go @@ -494,6 +494,7 @@ type responseAPI interface { Status() int64 StatusText() string URL() string + Text() (string, error) } // locatorAPI represents a way to find element(s) on a page at any moment. diff --git a/browser/response_mapping.go b/browser/response_mapping.go index 48d5c9bd0..af4b808b1 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -8,7 +8,7 @@ import ( ) // 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 } @@ -81,6 +81,11 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { "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 + }) + }, } return maps diff --git a/common/http.go b/common/http.go index 91c9ab2b7..2d3afb1e1 100644 --- a/common/http.go +++ b/common/http.go @@ -568,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. From 1982d555f39eb3ccae1edb9a54e9552606218d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0nan=C3=A7=20G=C3=BCm=C3=BC=C5=9F?= Date: Fri, 31 May 2024 19:04:59 +0300 Subject: [PATCH 8/8] Fix response.[frame|request] goja.Object usage Converting to a goja.Object here is unnecessary. --- browser/response_mapping.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/browser/response_mapping.go b/browser/response_mapping.go index af4b808b1..dc63a5ba9 100644 --- a/browser/response_mapping.go +++ b/browser/response_mapping.go @@ -12,7 +12,6 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { //nolint:funlen if r == nil { return nil } - rt := vu.Runtime() maps := mapping{ "allHeaders": func() *goja.Promise { return k6ext.Promise(vu.Context(), func() (any, error) { @@ -29,9 +28,8 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { //nolint:funlen return &buf, nil }) }, - "frame": func() *goja.Object { - mf := mapFrame(vu, r.Frame()) - return rt.ToValue(mf).ToObject(rt) + "frame": func() mapping { + return mapFrame(vu, r.Frame()) }, "headerValue": func(name string) *goja.Promise { return k6ext.Promise(vu.Context(), func() (any, error) { @@ -59,9 +57,8 @@ func mapResponse(vu moduleVU, r *common.Response) mapping { //nolint:funlen }) }, "ok": r.Ok, - "request": func() *goja.Object { - mr := mapRequest(vu, r.Request()) - return rt.ToValue(mr).ToObject(rt) + "request": func() mapping { + return mapRequest(vu, r.Request()) }, "securityDetails": func() *goja.Promise { return k6ext.Promise(vu.Context(), func() (any, error) {