-
Couldn't load subscription status.
- Fork 1.4k
Browser: add route.fulfill #4961
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
Changes from 11 commits
d48013d
f033f0e
49e4e61
fabd831
3f5b581
1926565
9d91850
50596d6
9955970
4a66aa0
4452b9e
0ed7528
55db130
adc5394
67f24be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package browser | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/grafana/sobek" | ||
|
|
||
| "go.k6.io/k6/internal/js/modules/k6/browser/common" | ||
| "go.k6.io/k6/internal/js/modules/k6/browser/k6ext" | ||
| ) | ||
|
|
||
| // mapRoute to the JS module. | ||
| func mapRoute(vu moduleVU, route *common.Route) mapping { | ||
| return mapping{ | ||
| "abort": func(reason string) *sobek.Promise { | ||
| return k6ext.Promise(vu.Context(), func() (any, error) { | ||
| return nil, route.Abort(reason) | ||
| }) | ||
| }, | ||
| "fulfill": func(opts sobek.Value) *sobek.Promise { | ||
| fopts := parseFulfillOptions(vu.Context(), opts) | ||
| return k6ext.Promise(vu.Context(), func() (any, error) { | ||
| return nil, route.Fulfill(fopts) | ||
| }) | ||
| }, | ||
| "request": func() mapping { | ||
| return mapRequest(vu, route.Request()) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func parseFulfillOptions(ctx context.Context, opts sobek.Value) common.FulfillOptions { | ||
| fopts := common.FulfillOptions{} | ||
| if !sobekValueExists(opts) { | ||
| return fopts | ||
| } | ||
|
|
||
| rt := k6ext.Runtime(ctx) | ||
| obj := opts.ToObject(rt) | ||
| for _, k := range obj.Keys() { | ||
| switch k { | ||
| case "body": | ||
| fopts.Body = obj.Get(k).String() | ||
| case "contentType": | ||
| fopts.ContentType = obj.Get(k).String() | ||
| case "headers": | ||
| headers := obj.Get(k).ToObject(rt) | ||
| headersKeys := headers.Keys() | ||
| fopts.Headers = make([]common.HTTPHeader, len(headersKeys)) | ||
| for i, hk := range headersKeys { | ||
| fopts.Headers[i] = common.HTTPHeader{ | ||
| Name: hk, | ||
| Value: headers.Get(hk).String(), | ||
| } | ||
| } | ||
| case "status": | ||
| fopts.Status = obj.Get(k).ToInteger() | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at https://playwright.dev/docs/api/class-route#route-fulfill - you are not handling But if not maybe lets throw an exception instead of silently doing the wrong thing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should be ok to wait for feedback for Instead of working with So, yeah, happy with throwing an error if a user tries to work with those fields that aren't implemented. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in 67f24be I agree that |
||
| } | ||
|
|
||
| return fopts | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -574,7 +574,11 @@ func (m *FrameManager) requestStarted(req *Request) { | |
|
|
||
| return | ||
| } | ||
| route.Continue() | ||
|
|
||
| if err := route.Continue(); err != nil { | ||
| m.logger.Warnf("FrameManager:requestStarted", | ||
|
||
| "fmid:%d rurl:%s error continuing request: %v", m.ID(), req.URL(), err) | ||
| } | ||
| } | ||
|
|
||
| // Frames returns a list of frames on the page. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,11 @@ package common | |
|
|
||
| import ( | ||
| "context" | ||
| "encoding/base64" | ||
| "errors" | ||
| "fmt" | ||
| "net" | ||
| "net/http" | ||
| "net/url" | ||
| "strconv" | ||
| "strings" | ||
|
|
@@ -642,7 +644,11 @@ func (m *NetworkManager) onRequestPaused(event *fetch.EventRequestPaused) { | |
|
|
||
| // If no route was added, continue all requests | ||
| if m.frameManager.page == nil || !m.frameManager.page.hasRoutes() { | ||
| m.ContinueRequest(event.RequestID) | ||
| err := m.ContinueRequest(event.RequestID) | ||
| if err != nil { | ||
| m.logger.Errorf("NetworkManager:onRequestPaused", | ||
| "continuing request %s %s: %s", event.Request.Method, event.Request.URL, err) | ||
| } | ||
| } | ||
| }() | ||
|
|
||
|
|
@@ -852,13 +858,12 @@ func (m *NetworkManager) Authenticate(credentials Credentials) error { | |
| return nil | ||
| } | ||
|
|
||
| func (m *NetworkManager) AbortRequest(requestID fetch.RequestID, errorReason string) { | ||
| func (m *NetworkManager) AbortRequest(requestID fetch.RequestID, errorReason string) error { | ||
AgnesToulet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| m.logger.Debugf("NetworkManager:AbortRequest", "aborting request (id: %s, errorReason: %s)", | ||
| requestID, errorReason) | ||
| netErrorReason, ok := m.errorReasons[errorReason] | ||
| if !ok { | ||
| m.logger.Errorf("NetworkManager:AbortRequest", "unknown error code: %s", errorReason) | ||
| return | ||
| return fmt.Errorf("unknown error code: %s", errorReason) | ||
| } | ||
|
|
||
| action := fetch.FailRequest(requestID, netErrorReason) | ||
|
|
@@ -869,13 +874,15 @@ func (m *NetworkManager) AbortRequest(requestID fetch.RequestID, errorReason str | |
| if errors.Is(err, context.Canceled) { | ||
| m.logger.Debug("NetworkManager:AbortRequest", "context canceled interrupting request") | ||
| } else { | ||
| m.logger.Errorf("NetworkManager:AbortRequest", "fail to abort request (id: %s): %s", requestID, err) | ||
| return fmt.Errorf("fail to abort request (id: %s): %w", requestID, err) | ||
| } | ||
| return | ||
| return nil | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func (m *NetworkManager) ContinueRequest(requestID fetch.RequestID) { | ||
| func (m *NetworkManager) ContinueRequest(requestID fetch.RequestID) error { | ||
| m.logger.Debugf("NetworkManager:ContinueRequest", "continuing request (id: %s)", requestID) | ||
|
|
||
| action := fetch.ContinueRequest(requestID) | ||
|
|
@@ -885,7 +892,7 @@ func (m *NetworkManager) ContinueRequest(requestID fetch.RequestID) { | |
| // while the iteration is ending and therefore the browser context is being closed. | ||
| if errors.Is(err, context.Canceled) { | ||
| m.logger.Debug("NetworkManager:ContinueRequest", "context canceled continuing request") | ||
| return | ||
| return nil | ||
| } | ||
|
|
||
| // This error message is an internal issue, rather than something that the user can | ||
|
|
@@ -895,32 +902,69 @@ func (m *NetworkManager) ContinueRequest(requestID fetch.RequestID) { | |
| if strings.Contains(err.Error(), "Invalid InterceptionId") { | ||
| m.logger.Debugf("NetworkManager:ContinueRequest", "invalid interception ID (%s) continuing request: %s", | ||
| requestID, err) | ||
| return | ||
| return nil | ||
| } | ||
|
|
||
| m.logger.Errorf("NetworkManager:ContinueRequest", "fail to continue request (id: %s): %s", | ||
| requestID, err) | ||
| return fmt.Errorf("fail to continue request (id: %s): %w", requestID, err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func (m *NetworkManager) FulfillRequest(requestID fetch.RequestID, params fetch.FulfillRequestParams) { | ||
| action := fetch.FulfillRequest(requestID, params.ResponseCode). | ||
| WithResponseHeaders(params.ResponseHeaders). | ||
| WithResponsePhrase(params.ResponsePhrase). | ||
| WithBody(params.Body) | ||
| func (m *NetworkManager) FulfillRequest(request *Request, opts FulfillOptions) error { | ||
| responseCode := int64(http.StatusOK) | ||
| if opts.Status != 0 { | ||
| responseCode = opts.Status | ||
| } | ||
|
|
||
| action := fetch.FulfillRequest(request.interceptionID, responseCode) | ||
|
|
||
| if opts.ContentType != "" { | ||
AgnesToulet marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering here... Is it better to keep it like this or actually remove this field from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it's better to be as close to Playwright as possible in UX terms. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I agree, this would only be "under the hood". The API spec would stay the same but we wouldn't bother with this field in the other layers of the codebase. |
||
| opts.Headers = append(opts.Headers, HTTPHeader{ | ||
| Name: "Content-Type", | ||
| Value: opts.ContentType, | ||
| }) | ||
| } | ||
|
|
||
| headers := toFetchHeaders(opts.Headers) | ||
| if len(headers) > 0 { | ||
| action = action.WithResponseHeaders(headers) | ||
| } | ||
|
|
||
| if opts.Body != "" { | ||
| b64Body := base64.StdEncoding.EncodeToString([]byte(opts.Body)) | ||
| action = action.WithBody(b64Body) | ||
| } | ||
|
|
||
| if err := action.Do(cdp.WithExecutor(m.ctx, m.session)); err != nil { | ||
| // Avoid logging as error when context is canceled. | ||
| // Most probably this happens when trying to fail a site's background request | ||
| // while the iteration is ending and therefore the browser context is being closed. | ||
| if errors.Is(err, context.Canceled) { | ||
| m.logger.Debug("NetworkManager:FulfillRequest", "context canceled fulfilling request") | ||
| } else { | ||
| m.logger.Errorf("NetworkManager:FulfillRequest", "fail to fulfill request (id: %s): %s", | ||
| requestID, err) | ||
| return nil | ||
| } | ||
|
|
||
| return fmt.Errorf("fail to fulfill request (id: %s): %w", | ||
| request.interceptionID, err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func toFetchHeaders(headers []HTTPHeader) []*fetch.HeaderEntry { | ||
| if len(headers) == 0 { | ||
| return nil | ||
| } | ||
|
|
||
| fetchHeaders := make([]*fetch.HeaderEntry, len(headers)) | ||
| for i, header := range headers { | ||
| fetchHeaders[i] = &fetch.HeaderEntry{ | ||
| Name: header.Name, | ||
| Value: header.Value, | ||
| } | ||
| return | ||
| } | ||
| return fetchHeaders | ||
| } | ||
|
|
||
| // SetExtraHTTPHeaders sets extra HTTP request headers to be sent with every request. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as I gave in the continue PR - this should use
common.ToBytesand handle this as binary data by the most part.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in adc5394