MarketSquare / MarketSquare/robotframework-browser
Enrich `Wait For Request` / `Wait For Response`: metadata, binary body support, and sibling consistency
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 655
- Forks
- 147
- Avg merge
- 5h 27m
- Merged PRs (30d)
- 59
Description
Closes #3320 (binary response body, priority: high).
Use case
Wait For Request and Wait For Response return dicts that users assert on, but three distinct problems limit them today:
1. Missing metadata. Several pieces of data Playwright already holds on the captured Request/Response objects are not copied into the dicts:
set-cookiecannot be asserted at all.headerscomes from Playwright'sheaders(), which deliberately omits security-related headers such asset-cookie. Verifying that a login response actually set a session cookie has no workaround via this keyword.allHeaders()fixes this.- Requests: resource type (
xhr,fetch,image, …), navigation flag, redirect origin, timing/sizes — e.g. "the API call finished under 500 ms". - Responses: HTTP version, server address, TLS details, served from a service worker.
2. Binary bodies are destroyed (#3320). The wrapper reads every response body with data.text() (node/playwright-wrapper/network.ts:79); Playwright's response.body() (Buffer) is never used. A user intercepting an image gets UTF-8-mangled text that cannot be converted back to bytes. The reporter's current workaround is re-issuing the captured request with Python requests — which defeats the purpose of interception (second request, different session). aaltat's guidance in the issue: let the user choose text vs. bytes.
3. The two sibling keywords are inconsistent. Users reasonably expect "a request" to look the same everywhere, but today:
Wait For Request (flat dict) |
request nested in Wait For Response |
|
|---|---|---|
url |
present | missing (only the response's url exists) |
postData parsing |
JSON.parse attempted unconditionally (node side, network.ts:122) |
parsed only if request content-type is application/json (Python side, network.py:43 _jsonize_content) |
| headers transport | plain dict | JSON-string, re-parsed Python-side |
Additionally, the response body is auto-JSON-parsed regardless of content-type (network.py:141 tries json.loads on every body), so users can neither rely on a documented rule nor opt out of the parsing magic.
We cannot silently "fix" the parsing rules — thousands of suites depend on the current shapes. The design below adds consistency additively and gives users an explicit opt-in switch, so nothing existing changes behavior.
Proposed changes
a) Additive metadata keys (no signature change)
Wait For Request dict gains: resourceType, isNavigationRequest, redirectedFrom (URL or None), timing (dict), sizes (dict), allHeaders (incl. browser-added headers).
Wait For Response dict gains: allHeaders (including set-cookie), httpVersion, serverAddr ({ipAddress, port} or None), securityDetails (dict or None), fromServiceWorker (bool), timing.
Sibling consistency: the nested request dict inside Wait For Response gains the same request-side keys with the same names and semantics as the flat Wait For Request dict — including the currently missing url. From then on, every documented request-side key exists in both places; only the legacy postData parsing difference remains (kept as-is, but now explicitly documented in both keyword docs, see c).
b) body_format= argument on Wait For Response (solves #3320)
New named-only argument:
Wait For Response matcher timeout=None *, body_format=AUTO
AUTO(default — exactly today's behavior): body transferred as text, JSON-parsed to a dict when parseable.TEXT: body as plainstr, no JSON auto-parsing (documented opt-out of the magic).BYTES: body returned as Pythonbytes, read viaresponse.body()and transferred as raw bytes — binary-safe. No JSON parsing.NONE: body isNone, transfer skipped entirely (multi-MB payloads no longer cross the gRPC boundary just to be thrown away).
*** Test Cases ***
Login Sets Session Cookie
${promise}= Promise To Wait For Response matcher=**/api/login
Click id=login-button
${response}= Wait For ${promise}
Should Contain ${response.allHeaders}[set-cookie] sessionid=
Should Be Equal ${response.httpVersion} h2
Save Intercepted Image # the #3320 scenario
${promise}= Promise To Wait For Response matcher=**/logo.png body_format=BYTES
Go To ${URL}
${response}= Wait For ${promise}
${path}= Evaluate pathlib.Path('${OUTPUT_DIR}/logo.png').write_bytes($response.body) modules=pathlib
API Call Is Fast Fetch
${promise}= Promise To Wait For Request matcher=**/api/data
Click id=load-data
${request}= Wait For ${promise}
Should Be Equal ${request.resourceType} fetch
Should Be True ${request.timing.responseEnd} < 500
The same enum can later be reused for a post_data_format= argument on Wait For Request (Playwright offers request.postDataBuffer()); out of scope here but the naming should anticipate it.
c) Documentation of the legacy quirks
The keyword docs of both siblings get an explicit "Parsing rules" section stating: how postData is parsed in each place, that body_format=AUTO JSON-parses regardless of content-type, and that headers omits security headers while allHeaders does not. This costs nothing and removes the surprise factor for the differences we deliberately keep.
Migration / mitigation story
- No behavior change without opt-in. All defaults (
body_format=AUTO, existing keys, existing parsing) stay byte-for-byte identical. New keys are additive; DotDict access to existing keys is unaffected. - Users hit by the quirks get an explicit escape hatch instead of a silent change:
body_format=TEXT|BYTESreplaces guessing,allHeadersreplaces missing headers, nestedrequest.urlremoves the need to correlate flat and nested shapes. - If a future major release ever wants to unify the
postDataparsing rules, the path is already laid: introducepost_data_format=with the same enum, defaultAUTOdocumenting current behavior — never a silent flip of defaults.
Playwright API
Request side: request.resourceType, request.isNavigationRequest, request.redirectedFrom, request.timing, request.sizes, request.allHeaders.
Response side: response.allHeaders, response.body, response.httpVersion, response.serverAddr, response.securityDetails, response.fromServiceWorker.
Implementation notes
protobuf/playwright.proto:Request.HttpCapturegains abodyFormatfield; the streamedResponse.Jsonchunk message gains abytes bodyPartBytesfield alongside the existing stringbodyPart(additive proto changes). Raw protobufbyteschunks avoid the ~33 % base64 overhead; base64 through the existing string field is the fallback option if proto changes are to be avoided.node/playwright-wrapper/network.ts:waitForResponsebranches onbodyFormat—response.body()Buffer chunked intobodyPartBytesforBYTES, currentdata.text()+splitUtf8ByMaxBytespath forAUTO/TEXT, no body read forNONE. Metadata fields added to the serialized JSON (asyncallHeaders/sizes/serverAddr/securityDetailsawaited here).waitForRequestgains the same metadata fields; the nested request dict inwaitForResponseis built from the same serializer for consistency.Browser/keywords/network.py:_wait_for_http_responseassemblesbytesfrom the byte chunks and skips both JSON-parse steps forBYTES/TEXT/NONE; newBodyFormatenum inBrowser/utils/data_types.py; docs for both keywords incl. the "Parsing rules" section.- atest:
set-cookieassertion, redirect metadata, a binary endpoint in the dynamic test app (PNG) round-tripped viabody_format=BYTESand compared byte-for-byte,NONEreturningbody=None, and a regression test thatAUTOoutput is unchanged.
Backwards compatibility
Fully backwards compatible by construction: no signature changes except one new named-only argument whose default reproduces today's behavior exactly; returned DotDicts only gain keys; wire changes are additive proto fields. The known shape inconsistencies between the siblings are documented rather than silently changed; any future unification goes through explicit opt-in arguments, never through changed defaults.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with protobuf/playwright.proto, node/playwright-wrapper/network.ts, Browser/keywords/network.py, and Browser/utils/data_types.py to trace the existing request and response serialization. Then review the atest coverage for set-cookie, redirects, binary PNG bodies, NONE, and unchanged AUTO behavior. Done means the additive metadata, body_format modes, sibling consistency, parsing documentation, and regression coverage work without changing default behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, python, typescript
- Domain
- api, backend, testing-qa
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 42/100