mirror of
https://github.com/web-infra-dev/midscene.git
synced 2025-07-06 00:20:30 +00:00

* feat(web-integration): use xpath for cache instead of id * feat(web-integration): enhance TaskCache to support xpaths for cache matching and add new test cases * feat(web-integration): add debug log for unknown page types in TaskCache * feat(web-integration): update caching logic and cache hit conditions for Plan and Locate tasks * chore(core): update debug log * feat(web-integration): update rspress.config and enhance TaskCache structure with new properties * feat(web-integration): recalculate id when hit cache * fix(web-integration): update mock implementation in task-cache test to use evaluate method * feat(web-integration): enhance element caching by adding XPath support and improving cache hit logic * chore(core): lint * feat(web-integration): improve XPath handling in web-extractor * test(web-integration): fix tests * feat(core, web-integration): add attributes to LocateResultElement and enhance element handling * fix(core): lint * feat(web-integration): add midsceneVersion to TaskCache and update cache validation logic * fix(core): test * fix(web-integration): update cache validation logic to prevent reading outdated midscene cache files * feat(web-integration): enhance TaskCache to track used cache items and improve cache retrieval logic * fix(core): xpath logic (#710) * feat(core): resue context for locate * feat(core): build yamlFlow from aiAction * feat(core): refine task-cache * feat(core): update cache * feat(core): refine task-cache * feat(core): refine task-cache * feat(core): remove unused checkElementExistsByXPath * feat(core): use yaml file as cache * chore(core): fix lint * chore(core): print warning for previous cache * refactor(core): remove quickAnswer references and improve element matching logic * fix(core): update import path for buildYamlFlowFromPlans * chore(web-integration): update output image and skip task error test * fix(web-integration): update test snapshots to handle beta versions * fix(web-integration): adjust test snapshots for version consistency * fix(web-integration): track original cache length and adjust matching logic in tests * fix(web-integration): update test URLs to reflect new target site and enable previously skipped test * chore(core): update cache docs * fix(core): test * feat(core): try to match element from plan * fix(web-integration): cache id stable when retry in palywright * fix(web-integration): typo * style(web-integration): lint * fix(web-integration): stable cacheid in tests * fix(web-integration): cache id --------- Co-authored-by: quanruzhuoxiu <quanruzhuoxiu@gmail.com>
229 lines
6.6 KiB
TypeScript
229 lines
6.6 KiB
TypeScript
import { existsSync, readFileSync } from 'node:fs';
|
|
import {
|
|
type LocateCache,
|
|
type PlanningCache,
|
|
TaskCache,
|
|
} from '@/common/task-cache';
|
|
import { uuid } from '@midscene/shared/utils';
|
|
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
|
|
vi.mock('../../package.json', () => {
|
|
return {
|
|
version: '0.16.11',
|
|
};
|
|
});
|
|
|
|
const prepareCache = (
|
|
caches: (PlanningCache | LocateCache)[],
|
|
cacheId?: string,
|
|
) => {
|
|
const cache = new TaskCache(cacheId ?? uuid(), true);
|
|
|
|
caches.map((data: PlanningCache | LocateCache) => {
|
|
cache.appendCache(data);
|
|
});
|
|
|
|
return cache.cacheFilePath;
|
|
};
|
|
|
|
describe(
|
|
'TaskCache',
|
|
() => {
|
|
beforeAll(() => {
|
|
vi.resetModules();
|
|
});
|
|
|
|
afterAll(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('should create cache file', () => {
|
|
const cacheId = uuid();
|
|
const cache = new TaskCache(cacheId, true);
|
|
expect(cache.cacheFilePath).toBeDefined();
|
|
|
|
cache.appendCache({
|
|
type: 'plan',
|
|
prompt: 'test',
|
|
yamlWorkflow: 'test',
|
|
});
|
|
|
|
expect(existsSync(cache.cacheFilePath!)).toBe(true);
|
|
const cacheContent = readFileSync(cache.cacheFilePath!, 'utf-8').replace(
|
|
cacheId,
|
|
'cacheId',
|
|
);
|
|
expect(cacheContent).toMatchSnapshot();
|
|
|
|
expect(cache.isCacheResultUsed).toBe(true);
|
|
});
|
|
|
|
it('update or append cache record - should not match cache added in same run', () => {
|
|
const cacheId = uuid();
|
|
const cache = new TaskCache(cacheId, true);
|
|
|
|
cache.appendCache({
|
|
type: 'plan',
|
|
prompt: 'test-prompt',
|
|
yamlWorkflow: 'test-yaml-workflow',
|
|
});
|
|
|
|
const existingRecord = cache.matchPlanCache('test-prompt');
|
|
expect(existingRecord).toBeUndefined();
|
|
|
|
cache.updateOrAppendCacheRecord(
|
|
{
|
|
type: 'plan',
|
|
prompt: 'test-prompt',
|
|
yamlWorkflow: 'test-yaml-workflow-2',
|
|
},
|
|
existingRecord,
|
|
);
|
|
|
|
expect(cache.cache.caches.length).toBe(2);
|
|
expect(cache.cache.caches).toMatchSnapshot();
|
|
});
|
|
|
|
it('one cache record can only be matched once - when loaded from file', () => {
|
|
const cacheFilePath = prepareCache([
|
|
{
|
|
type: 'plan',
|
|
prompt: 'test-prompt',
|
|
yamlWorkflow: 'test-yaml-workflow',
|
|
},
|
|
]);
|
|
|
|
const newCache = new TaskCache(uuid(), true, cacheFilePath);
|
|
|
|
// should be able to match cache record
|
|
expect(newCache.matchPlanCache('test-prompt')).toBeDefined();
|
|
// should return undefined when matching the same record again
|
|
expect(newCache.matchPlanCache('test-prompt')).toBeUndefined();
|
|
});
|
|
|
|
it('same prompt with same type cache record can be matched twice - when loaded from file', () => {
|
|
const cacheFilePath = prepareCache([
|
|
{
|
|
type: 'plan',
|
|
prompt: 'test-prompt',
|
|
yamlWorkflow: 'test-yaml-workflow-1',
|
|
},
|
|
{
|
|
type: 'plan',
|
|
prompt: 'test-prompt',
|
|
yamlWorkflow: 'test-yaml-workflow-2',
|
|
},
|
|
]);
|
|
const newCache = new TaskCache(uuid(), true, cacheFilePath);
|
|
|
|
// should be able to match the first record
|
|
const firstMatch = newCache.matchPlanCache('test-prompt');
|
|
expect(firstMatch).toBeDefined();
|
|
expect(firstMatch?.cacheContent.yamlWorkflow).toBe(
|
|
'test-yaml-workflow-1',
|
|
);
|
|
|
|
// should be able to match the second record
|
|
const secondMatch = newCache.matchPlanCache('test-prompt');
|
|
expect(secondMatch).toBeDefined();
|
|
expect(secondMatch?.cacheContent.yamlWorkflow).toBe(
|
|
'test-yaml-workflow-2',
|
|
);
|
|
|
|
// should return undefined when matching the same record again
|
|
expect(newCache.matchPlanCache('test-prompt')).toBeUndefined();
|
|
});
|
|
|
|
it('should not match cache records added in the same run', () => {
|
|
const cacheId = uuid();
|
|
const cache = new TaskCache(cacheId, true);
|
|
|
|
// cache is empty, cacheOriginalLength should be 0
|
|
expect(cache.cacheOriginalLength).toBe(0);
|
|
|
|
// add a cache record
|
|
cache.appendCache({
|
|
type: 'plan',
|
|
prompt: 'test-prompt-1',
|
|
yamlWorkflow: 'test-yaml-workflow-1',
|
|
});
|
|
|
|
// add another cache record
|
|
cache.appendCache({
|
|
type: 'plan',
|
|
prompt: 'test-prompt-2',
|
|
yamlWorkflow: 'test-yaml-workflow-2',
|
|
});
|
|
|
|
// cache has two records
|
|
expect(cache.cache.caches.length).toBe(2);
|
|
|
|
// cacheOriginalLength should be 0
|
|
expect(cache.cacheOriginalLength).toBe(0);
|
|
|
|
// should not be able to match any record
|
|
expect(cache.matchPlanCache('test-prompt-1')).toBeUndefined();
|
|
expect(cache.matchPlanCache('test-prompt-2')).toBeUndefined();
|
|
});
|
|
|
|
it('save and retrieve cache from file', () => {
|
|
const cacheId = uuid();
|
|
const planningCachedPrompt = 'test';
|
|
const planningCachedYamlWorkflow = 'test-yaml-workflow';
|
|
|
|
const locateCachedPrompt = 'test-locate';
|
|
const locateCachedXpaths = ['test-xpath-1', 'test-xpath-2'];
|
|
|
|
const cacheFilePath = prepareCache(
|
|
[
|
|
{
|
|
type: 'plan',
|
|
prompt: planningCachedPrompt,
|
|
yamlWorkflow: planningCachedYamlWorkflow,
|
|
},
|
|
{
|
|
type: 'locate',
|
|
prompt: locateCachedPrompt,
|
|
xpaths: locateCachedXpaths,
|
|
},
|
|
],
|
|
cacheId,
|
|
);
|
|
|
|
const newTaskCache = new TaskCache(cacheId, true, cacheFilePath);
|
|
|
|
// should be able to match all cache records
|
|
const cachedPlanCache = newTaskCache.matchPlanCache(planningCachedPrompt);
|
|
const { cacheContent: cachedPlanCacheContent } = cachedPlanCache!;
|
|
expect(cachedPlanCacheContent.prompt).toBe(planningCachedPrompt);
|
|
expect(cachedPlanCacheContent.yamlWorkflow).toBe(
|
|
planningCachedYamlWorkflow,
|
|
);
|
|
|
|
const cachedLocateCache =
|
|
newTaskCache.matchLocateCache(locateCachedPrompt);
|
|
const {
|
|
cacheContent: cachedLocateCacheContent,
|
|
updateFn: cachedLocateCacheUpdateFn,
|
|
} = cachedLocateCache!;
|
|
expect(cachedLocateCacheContent.prompt).toBe(locateCachedPrompt);
|
|
expect(cachedLocateCacheContent.xpaths).toEqual(locateCachedXpaths);
|
|
|
|
expect(newTaskCache.cache.caches).toMatchSnapshot();
|
|
|
|
// test update cache
|
|
cachedLocateCacheUpdateFn((cache) => {
|
|
cache.xpaths = ['test-xpath-3', 'test-xpath-4'];
|
|
});
|
|
|
|
expect(newTaskCache.cache.caches).toMatchSnapshot();
|
|
const cacheFileContent = readFileSync(
|
|
newTaskCache.cacheFilePath!,
|
|
'utf-8',
|
|
).replace(newTaskCache.cacheId, 'cacheId');
|
|
expect(cacheFileContent).toMatchSnapshot();
|
|
});
|
|
},
|
|
{ timeout: 20000 },
|
|
);
|