From 61873b27a4f6834c2ed4e410078e2029fb90e0ce Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Sat, 28 Feb 2026 15:20:10 +0100 Subject: [PATCH] feat: descriptive click timeout errors + faster action timeouts for agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two improvements for agent experience with Playwright actions: 1. Descriptive timeout errors (via @xmorse/playwright-core 1.59.6): When locator.click() times out, the error now explains WHY — e.g. 'Element is not visible', '
intercepts pointer events'. The 'not visible' case includes tips about common causes (collapsed
, inactive tab, CSS) and suggests { force: true }. 2. Faster action timeouts in executor: - Action timeout (click, fill, hover): 2s (was 10s) - Navigation timeout (goto, reload): 10s (unchanged) - Code execution timeout: 10s (unchanged) Agents now get fast failure with descriptive errors instead of waiting 10 seconds for a generic 'Timeout exceeded.' Also removed the context.setDefaultTimeout(timeout) override in execute() that was resetting action timeouts back to 10s on every call. Tests added for three click failure scenarios: hidden element in collapsed
, element covered by overlay, and display:none element. --- playwright | 2 +- playwriter/CHANGELOG.md | 7 +++ playwriter/package.json | 2 +- playwriter/src/executor.ts | 11 +++- playwriter/src/relay-core.test.ts | 89 +++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/playwright b/playwright index 1ab3002..502df20 160000 --- a/playwright +++ b/playwright @@ -1 +1 @@ -Subproject commit 1ab300291c842a7d9356790bca0b08532bf336d6 +Subproject commit 502df20d7577b20edc760c45a68c18e1b44724b2 diff --git a/playwriter/CHANGELOG.md b/playwriter/CHANGELOG.md index 6503328..e0e8b5b 100644 --- a/playwriter/CHANGELOG.md +++ b/playwriter/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.0.80 + +### Improvements + +- **Descriptive click timeout errors**: When `locator.click()` times out due to actionability failures, the error now includes the reason (e.g. "Element is not visible", "Element is not stable", " +
+ \`); + `, + }, + }) + const result = await client.callTool({ + name: 'execute', + arguments: { + code: js` + await state.errorTestPage.click('#hidden-btn'); + `, + }, + }) + expect((result as any).isError).toBe(true) + const text = (result as any).content[0].text + // Strip stack traces and call logs to only match the descriptive error line + const errorLine = text.split('\n').find((l: string) => l.includes('Timeout') || l.includes('not visible') || l.includes('not stable')) + expect(errorLine).toMatchInlineSnapshot(`"Error executing code: page.click: Timeout 2000ms exceeded. Element is not visible"`) + // Cleanup + await client.callTool({ name: 'execute', arguments: { code: js`await state.errorTestPage.close(); delete state.errorTestPage;` } }) + }, 30000) + + it('should show descriptive error when clicking an element covered by another', async () => { + await client.callTool({ + name: 'execute', + arguments: { + code: js` + state.errorTestPage = await context.newPage(); + await state.errorTestPage.setContent(\` +
+ +
Overlay
+
+ \`); + `, + }, + }) + const result = await client.callTool({ + name: 'execute', + arguments: { + code: js` + await state.errorTestPage.click('#covered-btn'); + `, + }, + }) + expect((result as any).isError).toBe(true) + const text = (result as any).content[0].text + const errorLine = text.split('\n').find((l: string) => l.includes('Timeout') || l.includes('intercepts')) + expect(errorLine).toMatchInlineSnapshot(`"Error executing code: page.click: Timeout 2000ms exceeded.
Overlay
intercepts pointer events"`) + await client.callTool({ name: 'execute', arguments: { code: js`await state.errorTestPage.close(); delete state.errorTestPage;` } }) + }, 30000) + + it('should show descriptive error when clicking a display:none element', async () => { + await client.callTool({ + name: 'execute', + arguments: { + code: js` + state.errorTestPage = await context.newPage(); + await state.errorTestPage.setContent(''); + `, + }, + }) + const result = await client.callTool({ + name: 'execute', + arguments: { + code: js` + await state.errorTestPage.click('#invisible'); + `, + }, + }) + expect((result as any).isError).toBe(true) + const text = (result as any).content[0].text + const errorLine = text.split('\n').find((l: string) => l.includes('Timeout') || l.includes('not visible')) + expect(errorLine).toMatchInlineSnapshot(`"Error executing code: page.click: Timeout 2000ms exceeded. Element is not visible"`) + await client.callTool({ name: 'execute', arguments: { code: js`await state.errorTestPage.close(); delete state.errorTestPage;` } }) + }, 30000) + })