diff --git a/packages/mobilewright-core/src/web-locator-pwexpect.test.ts b/packages/mobilewright-core/src/web-locator-pwexpect.test.ts index 4ccbc62..d81b8dc 100644 --- a/packages/mobilewright-core/src/web-locator-pwexpect.test.ts +++ b/packages/mobilewright-core/src/web-locator-pwexpect.test.ts @@ -39,3 +39,28 @@ test('expect(locator).toBeVisible() throws when the injected matcher does not ma } pwExpect(threw).toBe(true); }); + +// Regression: a malformed verdict (matches is undefined, e.g. no element matched +// the selector) must NOT leak back to Playwright as pass: undefined, which it +// rejects with "Unexpected return from a matcher function". We coerce to false. +function webLocatorWithMalformedVerdict(): Locator { + const { session } = fakeWebViewSession({ evaluateAlways: { received: undefined } }); + return new WebLocator(session, 'internal:role=button') as unknown as Locator; +} + +test('expect(locator).toBeVisible() fails cleanly when the verdict has no matches field', async () => { + const locator = webLocatorWithMalformedVerdict(); + let error: Error | undefined; + try { + await pwExpect(locator).toBeVisible({ timeout: 0 }); + } catch (e) { + error = e as Error; + } + pwExpect(error).toBeTruthy(); + pwExpect(error!.message).not.toContain('Unexpected return from a matcher function'); +}); + +test('expect(locator).not.toBeVisible() passes when the verdict has no matches field', async () => { + const locator = webLocatorWithMalformedVerdict(); + await pwExpect(locator).not.toBeVisible({ timeout: 0 }); +}); diff --git a/packages/mobilewright-core/src/web-locator.ts b/packages/mobilewright-core/src/web-locator.ts index 2602206..0953ad5 100644 --- a/packages/mobilewright-core/src/web-locator.ts +++ b/packages/mobilewright-core/src/web-locator.ts @@ -422,9 +422,13 @@ export class MobileWebViewLocator { isNot, timeout: 0, }); - lastMatches = result.matches; + // The injected matcher can hand back a non-boolean verdict (e.g. no element + // matched the selector yet). Coerce to a strict boolean so a stray undefined + // never leaks back to Playwright as pass: undefined ("Unexpected return from + // a matcher function"); treat anything but true as not-matched and keep polling. + lastMatches = result.matches === true; received = result.received; - return result.matches !== isNot; + return lastMatches !== isNot; }; let reached = await check();