fix: improve error message when element is blocked by overlay (#59)

When clicking an element that is blocked by a cookie banner or modal overlay,
the error message incorrectly showed "Element not found or not visible" even
though the element was found and visible.

The issue was in toAIFriendlyError(): the check for "Timeout" was evaluated
before "intercepts pointer events", causing the wrong error message to be
returned.

Changes:
- Reorder error detection to check "intercepts pointer events" before "Timeout"
- Improve error message to suggest dismissing modals/cookie banners
- Export toAIFriendlyError for testing
- Add focused tests for overlay blocking behavior

Before:
  Element "@e4" not found or not visible. Run 'snapshot' to see current page elements.

After:
  Element "@e4" is blocked by another element (likely a modal or overlay).
  Try dismissing any modals/cookie banners first.

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Noel
2026-01-13 15:25:46 -06:00
committed by GitHub
co-authored by Claude Sonnet 4.5
parent 2dc093cd62
commit 7b43d408da
2 changed files with 59 additions and 10 deletions
+20 -10
View File
@@ -134,8 +134,9 @@ interface SnapshotData {
/**
* Convert Playwright errors to AI-friendly messages
* @internal Exported for testing
*/
function toAIFriendlyError(error: unknown, selector: string): Error {
export function toAIFriendlyError(error: unknown, selector: string): Error {
const message = error instanceof Error ? error.message : String(error);
// Handle strict mode violation (multiple elements match)
@@ -150,7 +151,24 @@ function toAIFriendlyError(error: unknown, selector: string): Error {
);
}
// Handle element not found
// Handle element not interactable (must be checked BEFORE timeout case)
// This includes cases where an overlay/modal blocks the element
if (message.includes('intercepts pointer events')) {
return new Error(
`Element "${selector}" is blocked by another element (likely a modal or overlay). ` +
`Try dismissing any modals/cookie banners first.`
);
}
// Handle element not visible
if (message.includes('not visible') && !message.includes('Timeout')) {
return new Error(
`Element "${selector}" is not visible. ` +
`Try scrolling it into view or check if it's hidden.`
);
}
// Handle element not found (timeout waiting for element)
if (
message.includes('waiting for') &&
(message.includes('to be visible') || message.includes('Timeout'))
@@ -161,14 +179,6 @@ function toAIFriendlyError(error: unknown, selector: string): Error {
);
}
// Handle element not interactable
if (message.includes('intercepts pointer events') || message.includes('not visible')) {
return new Error(
`Element "${selector}" is not interactable (may be hidden or covered). ` +
`Try scrolling it into view or check if a modal/overlay is blocking it.`
);
}
// Return original error for unknown cases
return error instanceof Error ? error : new Error(message);
}