Skip to content

Commit 71e9398

Browse files
authored
refactor(core): update describeElementAtPoint integration;move recorder describer out of core (#2692)
Move recorder UI describer implementation and tests into playground. - Keep describeElementAtPoint and locatorVerifyFn exposed from core as - general-purpose element description utilities. - Update Studio recorder codegen to import recorder describer from playground.
1 parent 7a162d5 commit 71e9398

23 files changed

Lines changed: 1162 additions & 355 deletions

apps/studio/rsbuild.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export default defineConfig({
153153
'@midscene/harmony',
154154
'@midscene/ios',
155155
'@midscene/playground',
156+
'@midscene/playground/recorder-ui-describer',
156157
],
157158
sourceMap: true,
158159
},

apps/studio/src/main/recorder/codegen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {
22
convertRecordLogIntoMarkdown,
3-
describeRecorderUIEvents,
43
generatePlaywrightTest,
54
generateRecorderSessionMetadata,
65
generateRecorderYamlTest,
76
} from '@midscene/core/ai-model';
7+
import { describeRecorderUIEvents } from '@midscene/playground/recorder-ui-describer';
88
import { getDebug } from '@midscene/shared/logger';
99
import type {
1010
DescribeRecorderUIEventsRequest,

apps/studio/src/renderer/recorder/StudioRecorderProvider.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,28 @@ export function StudioRecorderProvider({ children }: PropsWithChildren) {
980980
result.trace.annotatedScreenshotRef?.path,
981981
}
982982
: undefined;
983+
const elementDescription = result.trace?.elementDescription?.trim();
984+
if (elementDescription && result.trace?.verifyPassed === false) {
985+
const semanticAction = buildRecorderSemanticAction(event);
986+
return normalizeInputRecorderSemantic({
987+
...event,
988+
semantic: {
989+
source: 'aiDescribe',
990+
status: 'ready',
991+
elementDescription,
992+
replayInstruction: buildMidsceneRecorderReplayInstruction(
993+
semanticAction,
994+
elementDescription,
995+
),
996+
actionSummary: buildMidsceneRecorderActionSummary(
997+
semanticAction,
998+
elementDescription,
999+
),
1000+
confidence: 'low',
1001+
...(aiDescribe ? { aiDescribe } : {}),
1002+
},
1003+
});
1004+
}
9831005
return {
9841006
...event,
9851007
semantic: {

apps/studio/tests/studio-recorder-main-codegen.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ vi.mock('@midscene/core/ai-model', () => ({
1616
),
1717
}));
1818

19+
vi.mock('@midscene/playground/recorder-ui-describer', () => ({
20+
describeRecorderUIEvents: vi.fn(async () => []),
21+
}));
22+
1923
import {
2024
convertRecordLogIntoMarkdown,
2125
generatePlaywrightTest,

apps/studio/tests/studio-recorder-provider.test.tsx

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,15 +382,15 @@ describe('StudioRecorderProvider preview recording', () => {
382382
}
383383
});
384384

385-
it('falls back from aiDescribe failed events to recorderAI descriptions', async () => {
385+
it('falls back from truly failed aiDescribe events to recorderAI descriptions', async () => {
386386
const event = {
387387
type: 'click',
388388
source: 'studio-preview',
389389
actionType: 'Click',
390390
semantic: {
391391
source: 'aiDescribe',
392392
status: 'failed',
393-
error: 'aiDescribe verification failed.',
393+
error: 'Active agent does not support describeElementAtPoint.',
394394
},
395395
elementRect: { x: 10, y: 20 },
396396
pageInfo: { width: 1200, height: 800 },
@@ -420,7 +420,69 @@ describe('StudioRecorderProvider preview recording', () => {
420420
fallbackFrom: {
421421
source: 'aiDescribe',
422422
status: 'failed',
423-
error: 'aiDescribe verification failed.',
423+
error: 'Active agent does not support describeElementAtPoint.',
424+
},
425+
},
426+
});
427+
428+
await mounted.cleanup();
429+
});
430+
431+
it('keeps aiDescribe when only verification fails and does not fallback to recorderAI', async () => {
432+
const event = {
433+
type: 'click',
434+
source: 'studio-preview',
435+
actionType: 'Click',
436+
elementRect: { x: 10, y: 20 },
437+
pageInfo: { width: 1200, height: 800 },
438+
screenshotAfter: 'data:image/png;base64,shot',
439+
timestamp: 123,
440+
hashId: 'click-ai-describe-verify-failed',
441+
};
442+
const describeRecorderEventAtPoint = vi.fn(async () => ({
443+
ok: false,
444+
error: 'aiDescribe verification failed.',
445+
trace: {
446+
elementDescription: 'Submit button in the form footer',
447+
verifyPassed: false,
448+
centerDistance: 24,
449+
point: [10, 20],
450+
verifyResult: {
451+
pass: false,
452+
center: [30, 20],
453+
centerDistance: 24,
454+
},
455+
annotatedScreenshotRef: {
456+
path: '/tmp/verify-failed-annotated.png',
457+
},
458+
},
459+
}));
460+
const { context } = createConnectedStudioContext({
461+
events: [event],
462+
describeRecorderEventAtPoint,
463+
});
464+
const mounted = await mountRecorder(context);
465+
466+
await act(async () => {
467+
await mounted.recorder?.startRecording();
468+
});
469+
await flushPromises();
470+
await flushPromises();
471+
472+
expect(describeStudioRecorderEventsWithAI).not.toHaveBeenCalled();
473+
expect(mounted.recorder?.currentSession?.events[0]).toMatchObject({
474+
hashId: 'click-ai-describe-verify-failed',
475+
semantic: {
476+
source: 'aiDescribe',
477+
status: 'ready',
478+
elementDescription: 'Submit button in the form footer',
479+
aiDescribe: {
480+
verifyPrompt: true,
481+
verifyPassed: false,
482+
centerDistance: 24,
483+
expectedCenter: [10, 20],
484+
actualCenter: [30, 20],
485+
annotatedScreenshotPath: '/tmp/verify-failed-annotated.png',
424486
},
425487
},
426488
});
@@ -1140,7 +1202,7 @@ describe('StudioRecorderProvider preview recording', () => {
11401202
semantic: {
11411203
source: 'aiDescribe',
11421204
status: 'failed',
1143-
error: 'aiDescribe verification failed.',
1205+
error: 'Active agent does not support describeElementAtPoint.',
11441206
},
11451207
},
11461208
}));
@@ -1238,7 +1300,7 @@ describe('StudioRecorderProvider preview recording', () => {
12381300
fallbackFrom: {
12391301
source: 'aiDescribe',
12401302
status: 'failed',
1241-
error: 'aiDescribe verification failed.',
1303+
error: 'Active agent does not support describeElementAtPoint.',
12421304
},
12431305
},
12441306
});

apps/studio/vitest.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export default defineConfig({
99
'@preload': path.resolve(__dirname, 'src/preload'),
1010
'@renderer': path.resolve(__dirname, 'src/renderer'),
1111
'@shared': path.resolve(__dirname, 'src/shared'),
12+
'@midscene/playground/recorder-ui-describer$': path.resolve(
13+
__dirname,
14+
'../../packages/playground/src/recorder-ui-describer.ts',
15+
),
1216
},
1317
},
1418
test: {

0 commit comments

Comments
 (0)