From 8b704dd1988a36cc955e7ddc0c386f31cab49cd4 Mon Sep 17 00:00:00 2001 From: gmegidish Date: Tue, 30 Jun 2026 21:35:35 +0200 Subject: [PATCH] fix(mobilewright-core): add strict element not found check --- .../src/web-locator-pwexpect.test.ts | 25 +++++++++++++++++++ packages/mobilewright-core/src/web-locator.ts | 8 ++++-- 2 files changed, 31 insertions(+), 2 deletions(-) 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();