Skip to content

fix(toBeRequestedWith): restore postData/response body matching - #2184

Open
mccmrunal wants to merge 2 commits into
mainfrom
fix/toBeRequestedWith-body-matching
Open

fix(toBeRequestedWith): restore postData/response body matching#2184
mccmrunal wants to merge 2 commits into
mainfrom
fix/toBeRequestedWith-body-matching

Conversation

@mccmrunal

@mccmrunal mccmrunal commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Closes #2183
The postData/response body matching logic in expect-webdriverio’s toBeRequestedWith() mock matcher had been silently commented out since the WebdriverIO v9 BiDi migration in August 2024. This meant that assertions checking request or response bodies would pass regardless of what was actually sent, and the bug went unnoticed for nearly two years.

After cloning the repo and doing a broader review, I came across this issue along with several other real defects. I traced the problem back to the v9 interception internals and confirmed that the body data itself had not been removed. It had simply been left disconnected during the migration.

I restored the matching logic, re-enabled the disabled tests, and fixed a related timing race affecting .not assertions, where the body could be checked before it had finished loading asynchronously.

A follow-up review exposed a few more issues introduced by the initial fix: incorrect Buffer handling, ambiguity between null and a JSON parse failure, a performance regression where negated assertions could wait for the entire timeout even when the request was clearly a non-match, a stale documentation comment, and a public TypeScript type that no longer reflected the runtime behavior.

I addressed these in a second pass by narrowing the retry logic to only wait when it was actually necessary, removing redundant parsing, fixing Buffer handling, clarifying the parsing behavior, updating the documentation, and widening the public type.

The final change is committed as a1c4ef2 on the fix/toBeRequestedWith-body-matching branch. The test suite currently has 43 passing tests, including several tests that were verified by reverting individual fixes to confirm they actually catch the bugs they were intended to cover. There are no known regressions or additional overhead in the common case.

The changes have not yet been pushed or opened as a pull request.

@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR restores request and response payload matching in toBeRequestedWith, including JSON primitive and asymmetric matcher support, while handling asynchronously collected payloads.

  • Adds payload-aware retry behavior and more informative diagnostics for uncollected bodies.
  • Restores and expands payload matching tests, including Buffer and JSON primitive cases.
  • Raises the WebdriverIO peer requirement to the version that populates mock payload fields.
  • Updates the public types and API documentation to reflect runtime behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported primitive asymmetric matcher issue is addressed by recognizing constructor samples and parsing serialized JSON primitives before comparison.

Important Files Changed

Filename Overview
src/matchers/mock/toBeRequestedWith.ts Restores body matching, parses JSON-compatible payloads for asymmetric comparisons, and handles delayed payload collection without leaving the previously reported primitive-matcher defect.
test/matchers/mock/toBeRequestedWith.test.ts Restores payload scenarios and adds focused coverage for delayed bodies, Buffers, JSON primitives, and constructor-based asymmetric matchers.
types/expect-webdriverio.d.ts Expands request and response expectations to include JSON primitive values accepted by the runtime.
package.json Raises the WebdriverIO peer minimum to the release required for mock payload collection.
docs/API.md Documents asynchronous payload collection and the retry limitations of negated assertions.

Reviews (4): Last reviewed commit: "fix(toBeRequestedWith): require webdrive..." | Re-trigger Greptile

Comment on lines +275 to +277
const actualSample = isAsymmetricMatcher(expected)
? getAsymmetricMatcherValue(expected)
: expected

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Primitive type matchers skip parsing

When a JSON primitive body such as "42" is checked with expect.any(Number), the extracted matcher value is not classified as JSON-like, so the raw string is passed to the type matcher and a valid body assertion fails. Boolean type matchers encounter the same mismatch.

Knowledge Base Used: Elements-Array and Mock Matchers

@mccmrunal
mccmrunal force-pushed the fix/toBeRequestedWith-body-matching branch from a1c4ef2 to 7baa5da Compare August 15, 2026 16:34
Comment on lines +275 to +286
const actualSample = isAsymmetricMatcher(expected)
? getAsymmetricMatcherValue(expected)
: expected

return (
actualSample === null ||
typeof actualSample === 'boolean' ||
typeof actualSample === 'number' ||
Array.isArray(actualSample) ||
(typeof actualSample === 'object' &&
actualSample !== null &&
actualSample instanceof RegExp === false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Primitive type matchers skip parsing

When a JSON primitive body such as "42" or "true" is asserted with expect.any(Number) or expect.any(Boolean), getAsymmetricMatcherValue returns the constructor function, which is not classified as JSON-like. The body therefore remains a serialized string and the type matcher rejects an otherwise valid payload.

Knowledge Base Used: Elements-Array and Mock Matchers

@mccmrunal
mccmrunal force-pushed the fix/toBeRequestedWith-body-matching branch from 7baa5da to ee374a0 Compare August 15, 2026 16:39
@mccmrunal
mccmrunal requested a review from dprevost-LMI August 15, 2026 16:42
@mccmrunal

Copy link
Copy Markdown
Contributor Author

@dprevost-LMI Pleae review this

Comment on lines +42 to +72
/**
* `postData`/`body` are populated asynchronously (via a `network.getData` round-trip in
* WebDriverInterception), so they may not be attached to a call yet at the very first check.
* `.not` assertions normally check once, immediately (`wait: 0`), since a mock's call log only
* grows and can't "become unmatched" - but that immediate check would race the payload
* collection and could report a false pass. So when `postData`/`response` are part of the
* expectation, give `.not` the same "wait for a match to appear" retry window a positive
* assertion gets, instead of deciding on the first, possibly incomplete, snapshot of the call -
* but ONLY while there's an actual pending candidate (a call that already matches everything
* else and is just waiting on its body/postData to attach). If no call matches the non-payload
* criteria at all, the outcome can't change by waiting, so the predicate below sets `abort` and
* `waitUntil` resolves immediately - keeping the fast, single-check behavior for the common case
* (wrong URL, no calls made, etc.) while still closing the race for the one case that needs it.
* `pass` below always means "was a matching call found" either way - `.not` inversion is
* handled downstream by the test framework, not by this function - so no extra inversion here,
* only the retry direction passed into `waitUntil` changes.
*/
const hasPayloadExpectation = expectedValue.postData !== undefined || expectedValue.response !== undefined
const waitForPayloadOnNot = isNot && hasPayloadExpectation

// shared across every `waitUntil` iteration and the later message-building step, so a given
// postData/body string is JSON.parsed at most once per assertion instead of once per read
const parseCache: Map<string, ParsedJson> = new Map()

/**
* a call matched everything except its payload, and that payload never arrived. Kept from the
* final iteration so the failure message can explain *why* the body looks empty rather than
* just diffing against `undefined` - see `payloadCollectionHint()`.
*/
let payloadNeverCollected = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leverage waitForResponse from wdio here ?

@dprevost-LMI

Copy link
Copy Markdown
Contributor

FYI: We could add e2e in the playgrounds too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

toBeRequestedWith() silently ignores postData and response options assertion passes regardless of request/response body

2 participants