Skip to content

Commit 246fef3

Browse files
authored
chore: better error message for bad apiEndpoint and non-json cache (#38845)
1 parent 477cfe3 commit 246fef3

File tree

4 files changed

+69
-3
lines changed

4 files changed

+69
-3
lines changed

packages/playwright-core/src/server/agent/pageAgent.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ async function runLoop(progress: Progress, context: Context, toolDefinitions: To
9292
throw new Error(`This action requires the API and API key to be set on the page agent. Did you mean to --run-agents=missing?`);
9393
if (!context.agentParams.apiKey)
9494
throw new Error(`This action requires API key to be set on the page agent.`);
95+
if (context.agentParams.apiEndpoint && !URL.canParse(context.agentParams.apiEndpoint))
96+
throw new Error(`Agent API endpoint "${context.agentParams.apiEndpoint}" is not a valid URL.`);
9597

9698
const snapshot = await context.takeSnapshot(progress);
9799
const { tools, callTool, reportedResult, refusedToPerformReason } = toolsForLoop(progress, context, toolDefinitions, { resultSchema, refuseToPerform: 'allow' });
@@ -204,7 +206,13 @@ const allCaches = new Map<string, Cache>();
204206
async function cachedActions(cacheFile: string): Promise<Cache> {
205207
let cache = allCaches.get(cacheFile);
206208
if (!cache) {
207-
const json = await fs.promises.readFile(cacheFile, 'utf-8').then(text => JSON.parse(text)).catch(() => ({}));
209+
const content = await fs.promises.readFile(cacheFile, 'utf-8').catch(() => '');
210+
let json: any;
211+
try {
212+
json = JSON.parse(content.trim() || '{}');
213+
} catch (error) {
214+
throw new Error(`Failed to parse cache file ${cacheFile}:\n${error.message}`);
215+
}
208216
const parsed = actions.cachedActionsSchema.safeParse(json);
209217
if (parsed.error)
210218
throw new Error(`Failed to parse cache file ${cacheFile}:\n${zod.prettifyError(parsed.error)}`);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"29a2028099170f3ab333459d1f9c78de2767e7b3": {
3+
"result": {
4+
"role": "assistant",
5+
"content": [
6+
{
7+
"type": "text",
8+
"text": "I'll click the Test button for you."
9+
},
10+
{
11+
"type": "tool_call",
12+
"name": "browser_click",
13+
"arguments": {
14+
"element": "Test button",
15+
"ref": "e2",
16+
"_is_done": true
17+
},
18+
"id": "toolu_013jBJjsaeAjvjyjfWLYNFGb"
19+
}
20+
],
21+
"stopReason": {
22+
"code": "ok"
23+
}
24+
},
25+
"usage": {
26+
"input": 3043,
27+
"output": 101
28+
}
29+
}
30+
}

tests/library/agent-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export async function generateAgent(context: BrowserContext, options: AgentOptio
4242
provider: {
4343
api: 'anthropic' as const,
4444
apiKey: process.env.AZURE_SONNET_API_KEY ?? 'dummy',
45-
apiEndpoint: process.env.AZURE_SONNET_ENDPOINT ?? 'dummy',
45+
apiEndpoint: process.env.AZURE_SONNET_ENDPOINT,
4646
model: 'claude-sonnet-4-5',
4747
...{ _apiCacheFile: apiCacheFile }
4848
},

tests/library/agent-perform.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
import { z as zod3 } from 'zod/v3';
1818
import * as zod4 from 'zod';
19+
import fs from 'fs';
1920

2021
import { browserTest as test, expect } from '../config/browserTest';
21-
import { run, generateAgent, cacheObject, runAgent, setCacheObject } from './agent-helpers';
22+
import { run, generateAgent, cacheObject, runAgent, setCacheObject, cacheFile } from './agent-helpers';
2223

2324
// LOWIRE_NO_CACHE=1 to generate api caches
2425
// LOWIRE_FORCE_CACHE=1 to force api caches
@@ -191,6 +192,33 @@ Failed to parse cache file ${test.info().outputPath('agent-cache.json')}:
191192
`.trim());
192193
});
193194

195+
test('non-json cache file throws a nice error', async ({ context }) => {
196+
await fs.promises.writeFile(cacheFile(), 'bogus', 'utf8');
197+
const { agent } = await runAgent(context);
198+
const error = await agent.perform('click the Test button').catch(e => e);
199+
expect(error.message).toContain(`Failed to parse cache file ${test.info().outputPath('agent-cache.json')}:`);
200+
expect(error.message.toLowerCase()).toContain(`valid json`);
201+
});
202+
203+
test('empty cache file works', async ({ context }) => {
204+
await fs.promises.writeFile(cacheFile(), '', 'utf8');
205+
const { page, agent } = await generateAgent(context);
206+
await page.setContent(`<button>Test</button>`);
207+
await agent.perform('click the Test button');
208+
});
209+
210+
test('missing apiKey throws a nice error', async ({ page }) => {
211+
const agent = await page.agent({ provider: { api: 'anthropic', model: 'some model' } as any });
212+
const error = await agent.perform('click the Test button').catch(e => e);
213+
expect(error.message).toContain(`This action requires API key to be set on the page agent`);
214+
});
215+
216+
test('malformed apiEndpoint throws a nice error', async ({ page }) => {
217+
const agent = await page.agent({ provider: { api: 'anthropic', model: 'some model', apiKey: 'some key', apiEndpoint: 'foobar' } });
218+
const error = await agent.perform('click the Test button').catch(e => e);
219+
expect(error.message).toContain(`Agent API endpoint "foobar" is not a valid URL`);
220+
});
221+
194222
test('perform reports error', async ({ context }) => {
195223
const { page, agent } = await generateAgent(context);
196224
await page.setContent(`

0 commit comments

Comments
 (0)