Skip to content

Commit 03f42a7

Browse files
committed
refactor(core): improve error messages in FileStorage and normalize task recorder fields in ExecutionDump
1 parent f96148c commit 03f42a7

5 files changed

Lines changed: 58 additions & 41 deletions

File tree

‎packages/core/src/screenshot-item.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ export class ScreenshotItem {
8282

8383
/** Check if a value is a serialized ScreenshotItem */
8484
static isSerialized(value: unknown): value is { $screenshot: string } {
85-
if (typeof value !== 'object' || value === null) {
86-
return false;
87-
}
88-
const obj = value as Record<string, unknown>;
89-
return '$screenshot' in obj && typeof obj.$screenshot === 'string';
85+
return (
86+
typeof value === 'object' &&
87+
value !== null &&
88+
'$screenshot' in value &&
89+
typeof (value as Record<string, unknown>).$screenshot === 'string'
90+
);
9091
}
9192
}

‎packages/core/src/storage/file.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,14 @@ export class FileStorage implements StorageProvider {
6868
async retrieve(id: string): Promise<string> {
6969
const filePath = this.registry.get(id);
7070
if (!filePath) {
71-
throw new Error(`FileStorage: File not found for id: ${id}`);
71+
throw new Error(
72+
`FileStorage: Data not found for id: ${id} (not in registry)`,
73+
);
7274
}
7375
if (!fs.existsSync(filePath)) {
74-
throw new Error(`FileStorage: File does not exist: ${filePath}`);
76+
throw new Error(
77+
`FileStorage: Data not found for id: ${id} (file missing at: ${filePath})`,
78+
);
7579
}
7680
return fs.readFileSync(filePath, 'utf-8');
7781
}

‎packages/core/src/types.ts‎

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -415,12 +415,31 @@ function reviverForDumpDeserialization(key: string, value: any): any {
415415
if (key === 'screenshot' && ScreenshotItem.isSerialized(value)) {
416416
// Use MemoryStorage as default provider for deserialization
417417
// The actual data will be loaded later via storageProvider
418+
// Note: Import here to avoid circular dependency issues
419+
// eslint-disable-next-line @typescript-eslint/no-require-imports
418420
const { MemoryStorage } = require('./storage');
419421
return ScreenshotItem.restore(value.$screenshot, new MemoryStorage());
420422
}
421423
return value;
422424
}
423425

426+
/**
427+
* Helper to ensure all tasks have recorder field (even if empty)
428+
*/
429+
function normalizeTaskRecorder(
430+
tasks: IExecutionDump['tasks'],
431+
): IExecutionDump['tasks'] {
432+
return tasks.map((task: any) => ({
433+
...task,
434+
recorder: task.recorder || [],
435+
}));
436+
}
437+
438+
/**
439+
* Type for screenshot serialization result
440+
*/
441+
type SerializedScreenshot = { base64: string };
442+
424443
/**
425444
* ExecutionDump class for serializing and deserializing execution dumps
426445
*/
@@ -526,43 +545,37 @@ export class ExecutionDump implements IExecutionDump {
526545

527546
// Use async processing for screenshots (inline or file path mode)
528547
if (inlineScreenshots || screenshotsPath) {
529-
const collectScreenshotPromises = (obj: any): Promise<any> => {
548+
const processValue = async (
549+
obj: unknown,
550+
): Promise<
551+
unknown | SerializedScreenshot | unknown[] | Record<string, unknown>
552+
> => {
530553
if (obj instanceof ScreenshotItem) {
531554
if (screenshotsPath) {
532555
// Directory mode: return file path
533-
return Promise.resolve({
534-
base64: `${screenshotsPath}/${obj.id}.png`,
535-
});
556+
return { base64: `${screenshotsPath}/${obj.id}.png` };
536557
}
537558
// Inline mode: return base64 data
538-
return obj.getData().then((base64) => ({ base64 }));
559+
const base64 = await obj.getData();
560+
return { base64 };
539561
}
540562
if (Array.isArray(obj)) {
541-
return Promise.all(obj.map(collectScreenshotPromises));
563+
return Promise.all(obj.map(processValue));
542564
}
543565
if (obj && typeof obj === 'object') {
544-
const promises: Record<string, Promise<any>> = {};
545-
for (const [key, value] of Object.entries(obj)) {
546-
promises[key] = collectScreenshotPromises(value);
547-
}
548-
return (async () => {
549-
const resolved: Record<string, any> = {};
550-
for (const [key, promise] of Object.entries(promises)) {
551-
resolved[key] = await promise;
552-
}
553-
return resolved;
554-
})();
566+
const entries = await Promise.all(
567+
Object.entries(obj).map(async ([key, value]) => [
568+
key,
569+
await processValue(value),
570+
]),
571+
);
572+
return Object.fromEntries(entries);
555573
}
556-
return Promise.resolve(obj);
574+
return obj;
557575
};
558576

559-
const resolvedData = await collectScreenshotPromises(this.toJSON());
560-
const result = resolvedData as IExecutionDump;
561-
// Ensure all tasks have recorder field (even if empty)
562-
result.tasks = result.tasks.map((task: any) => ({
563-
...task,
564-
recorder: task.recorder || [],
565-
}));
577+
const result = (await processValue(this.toJSON())) as IExecutionDump;
578+
result.tasks = normalizeTaskRecorder(result.tasks);
566579
return result;
567580
}
568581

@@ -573,12 +586,8 @@ export class ExecutionDump implements IExecutionDump {
573586
}
574587
return value;
575588
});
576-
const result = JSON.parse(jsonString);
577-
// Ensure all tasks have recorder field (even if empty)
578-
result.tasks = result.tasks.map((task: any) => ({
579-
...task,
580-
recorder: task.recorder || [],
581-
}));
589+
const result = JSON.parse(jsonString) as IExecutionDump;
590+
result.tasks = normalizeTaskRecorder(result.tasks);
582591
return result;
583592
}
584593
}

‎packages/core/tests/unit-test/execution-dump.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('ExecutionDump', () => {
9898
});
9999

100100
describe('toJSON', () => {
101-
it('should return a plain object', () => {
101+
it('should return a plain object with recorder fields normalized', () => {
102102
const data = createMockExecutionDumpData();
103103
const dump = new ExecutionDump(data);
104104
const json = dump.toJSON();
@@ -107,7 +107,10 @@ describe('ExecutionDump', () => {
107107
logTime: data.logTime,
108108
name: data.name,
109109
description: data.description,
110-
tasks: data.tasks,
110+
tasks: data.tasks.map((task) => ({
111+
...task,
112+
recorder: task.recorder || [],
113+
})),
111114
aiActContext: data.aiActContext,
112115
});
113116
});

‎packages/web-integration/tests/ai/web/puppeteer/open-new-tab.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('agent with forceSameTabNavigation', () => {
2525
resetFn = reset;
2626
agent = new PuppeteerAgent(originPage, {
2727
cacheId: 'puppeteer-open-new-tab',
28-
useDirectoryReport: true,
28+
useDirectoryReport: false,
2929
});
3030
const inputXpath = '//*[@id="sb_form_q"]';
3131
await agent.aiInput('The search input box', {

0 commit comments

Comments
 (0)