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:
co-authored by
Claude Sonnet 4.5
parent
2dc093cd62
commit
7b43d408da
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { toAIFriendlyError } from './actions.js';
|
||||||
|
|
||||||
|
describe('toAIFriendlyError', () => {
|
||||||
|
describe('element blocked by overlay', () => {
|
||||||
|
it('should detect intercepts pointer events even when Timeout is in message', () => {
|
||||||
|
// This is the exact error from Playwright when a cookie banner blocks an element
|
||||||
|
// Bug: Previously this was incorrectly reported as "not found or not visible"
|
||||||
|
const error = new Error(
|
||||||
|
'TimeoutError: locator.click: Timeout 10000ms exceeded.\n' +
|
||||||
|
'Call log:\n' +
|
||||||
|
" - waiting for getByRole('link', { name: 'Anmelden', exact: true }).first()\n" +
|
||||||
|
' - locator resolved to <a href="https://example.com/login">Anmelden</a>\n' +
|
||||||
|
' - attempting click action\n' +
|
||||||
|
' 2 x waiting for element to be visible, enabled and stable\n' +
|
||||||
|
' - element is visible, enabled and stable\n' +
|
||||||
|
' - scrolling into view if needed\n' +
|
||||||
|
' - done scrolling\n' +
|
||||||
|
' - <body class="font-sans antialiased">...</body> intercepts pointer events\n' +
|
||||||
|
' - retrying click action'
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = toAIFriendlyError(error, '@e4');
|
||||||
|
|
||||||
|
// Must NOT say "not found" - the element WAS found
|
||||||
|
expect(result.message).not.toContain('not found');
|
||||||
|
// Must indicate the element is blocked
|
||||||
|
expect(result.message).toContain('blocked by another element');
|
||||||
|
expect(result.message).toContain('modal or overlay');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should suggest dismissing cookie banners', () => {
|
||||||
|
const error = new Error('<div class="cookie-overlay"> intercepts pointer events');
|
||||||
|
const result = toAIFriendlyError(error, '@e1');
|
||||||
|
|
||||||
|
expect(result.message).toContain('cookie banners');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+20
-10
@@ -134,8 +134,9 @@ interface SnapshotData {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Playwright errors to AI-friendly messages
|
* 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);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
// Handle strict mode violation (multiple elements match)
|
// 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 (
|
if (
|
||||||
message.includes('waiting for') &&
|
message.includes('waiting for') &&
|
||||||
(message.includes('to be visible') || message.includes('Timeout'))
|
(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 original error for unknown cases
|
||||||
return error instanceof Error ? error : new Error(message);
|
return error instanceof Error ? error : new Error(message);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user