Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/mobilewright-core/src/web-locator-pwexpect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
8 changes: 6 additions & 2 deletions packages/mobilewright-core/src/web-locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down