@empiricalrun/test-gen 0.22.8 → 0.22.9-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/actions/assertTextVisibility.d.ts +4 -0
- package/dist/actions/assertTextVisibility.d.ts.map +1 -0
- package/dist/actions/assertTextVisibility.js +52 -0
- package/dist/agent/browsing/run.js +1 -1
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +15 -3
- package/dist/agent/codegen/run.js +1 -1
- package/dist/bin/ai/index.d.ts +9 -0
- package/dist/bin/ai/index.d.ts.map +1 -0
- package/dist/bin/ai/index.js +31 -0
- package/dist/bin/ai/prompts/provider/index.d.ts +9 -0
- package/dist/bin/ai/prompts/provider/index.d.ts.map +1 -0
- package/dist/bin/ai/prompts/provider/index.js +28 -0
- package/dist/bin/ai/trace/index.d.ts +37 -0
- package/dist/bin/ai/trace/index.d.ts.map +1 -0
- package/dist/bin/ai/trace/index.js +67 -0
- package/dist/bin/index.js +0 -0
- package/dist/bin/reporter/ci.d.ts +3 -0
- package/dist/bin/reporter/ci.d.ts.map +1 -0
- package/dist/bin/reporter/ci.js +24 -0
- package/dist/bin/scenarios/index.d.ts +4 -0
- package/dist/bin/scenarios/index.d.ts.map +1 -0
- package/dist/bin/scenarios/index.js +112 -0
- package/dist/bin/session/index.d.ts +6 -0
- package/dist/bin/session/index.d.ts.map +1 -0
- package/dist/bin/session/index.js +16 -0
- package/dist/bin/utils/platform/web/index.d.ts +4 -1
- package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
- package/dist/bin/utils/platform/web/index.js +13 -4
- package/dist/reporter/ci.js +1 -1
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PlaywrightActionGenerator } from "../types";
|
|
2
|
+
export declare const PLAYWRIGHT_ASSERT_TEXT_VISIBILITY_ACTION_NAME = "assert_text_visibility";
|
|
3
|
+
export declare const assertTextVisibilityActionGenerator: PlaywrightActionGenerator;
|
|
4
|
+
//# sourceMappingURL=assertTextVisibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertTextVisibility.d.ts","sourceRoot":"","sources":["../../src/actions/assertTextVisibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAGrD,eAAO,MAAM,6CAA6C,2BAChC,CAAC;AAE3B,eAAO,MAAM,mCAAmC,EAAE,yBAsDjD,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertTextVisibilityActionGenerator = exports.PLAYWRIGHT_ASSERT_TEXT_VISIBILITY_ACTION_NAME = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
exports.PLAYWRIGHT_ASSERT_TEXT_VISIBILITY_ACTION_NAME = "assert_text_visibility";
|
|
6
|
+
const assertTextVisibilityActionGenerator = (page) => {
|
|
7
|
+
return {
|
|
8
|
+
execute: async (args) => {
|
|
9
|
+
const locator = await (0, utils_1.getPlaywrightLocatorUsingCssSelector)(args.css_selector, page);
|
|
10
|
+
const exec = new Function("page", `return page.${locator}.isVisible({ timeout: 3000 })`);
|
|
11
|
+
await exec(page);
|
|
12
|
+
return {
|
|
13
|
+
locator,
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
// TODO: args transformer to be kept at a single place
|
|
17
|
+
template: (args, options) => {
|
|
18
|
+
const css = args.css_selector;
|
|
19
|
+
const assertStr = `await expect(page.locator("${css}")).toBeVisible();`;
|
|
20
|
+
if (options?.locator) {
|
|
21
|
+
return `
|
|
22
|
+
// ${assertStr}
|
|
23
|
+
await expect(page.${options.locator}).toBeVisible();
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
return assertStr;
|
|
27
|
+
},
|
|
28
|
+
name: exports.PLAYWRIGHT_ASSERT_TEXT_VISIBILITY_ACTION_NAME,
|
|
29
|
+
schema: {
|
|
30
|
+
type: "function",
|
|
31
|
+
function: {
|
|
32
|
+
name: exports.PLAYWRIGHT_ASSERT_TEXT_VISIBILITY_ACTION_NAME,
|
|
33
|
+
description: "assert whether the given element on the page is visible",
|
|
34
|
+
parameters: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
css_selector: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "CSS selector to identify the element uniquely and click it. When creating CSS selectors, ensure they are unique and specific enough to select only one element, even if there are multiple elements of the same type (like multiple h1 elements)",
|
|
40
|
+
},
|
|
41
|
+
reason: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "reason for calling this function",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ["css_selector", "reason"],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
exports.assertTextVisibilityActionGenerator = assertTextVisibilityActionGenerator;
|
|
@@ -23,7 +23,7 @@ async function generateTestsUsingBrowsingAgent(testFilePath) {
|
|
|
23
23
|
logger.log(`Detected playwright project name: ${project}`);
|
|
24
24
|
//TODO: change this to per test
|
|
25
25
|
let command = `npx playwright test ${testFilePath} --retries 0 --project ${project}`;
|
|
26
|
-
if (!
|
|
26
|
+
if (!process.env.CI) {
|
|
27
27
|
command = command.concat(` --headed`);
|
|
28
28
|
}
|
|
29
29
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAUvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAED,wBAAsB,2BAA2B,CAAC,SAAS,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAUvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAED,wBAAsB,2BAA2B,CAAC,SAAS,EAAE,aAAa,iBA+CzE;AAwCD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,iBASxD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,QA4BnD;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAM1E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,oBAAoB,GACrC,OAAO,CAAC,MAAM,CAAC,CAkDjB;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,YAAiB,EACjB,IAAS,EACT,eAAoB,EACpB,gBAAqB,EACrB,UAAyC,GAC1C,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,8EASA"}
|
|
@@ -33,14 +33,14 @@ async function prepareFileForBrowsingAgent(genConfig) {
|
|
|
33
33
|
}
|
|
34
34
|
if (name && steps && steps.length) {
|
|
35
35
|
const existingContents = await fs_extra_1.default.readFile(specPath, "utf-8");
|
|
36
|
-
const testBlock = (0, web_1.getTypescriptTestBlock)(name, existingContents);
|
|
36
|
+
const { testBlock, parentDescribe } = (0, web_1.getTypescriptTestBlock)(name, existingContents);
|
|
37
37
|
const mergedSteps = prepareBrowsingAgentTask(steps);
|
|
38
38
|
let newContents = existingContents;
|
|
39
39
|
if (testBlock) {
|
|
40
40
|
logger.log("appending to existing test block");
|
|
41
41
|
// test scenario is already present
|
|
42
|
-
const
|
|
43
|
-
newContents = existingContents
|
|
42
|
+
const updatedTestBlock = (0, web_1.appendToTestBlock)(testBlock, createTestGenBlock(mergedSteps));
|
|
43
|
+
newContents = newContentsWithTestOnly(existingContents, testBlock, updatedTestBlock, parentDescribe);
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
46
|
logger.log("creating new test block");
|
|
@@ -52,6 +52,18 @@ async function prepareFileForBrowsingAgent(genConfig) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
exports.prepareFileForBrowsingAgent = prepareFileForBrowsingAgent;
|
|
55
|
+
function newContentsWithTestOnly(existingContents, originalTestBlock, updatedTestBlock, parentDescribeBlock) {
|
|
56
|
+
if (!parentDescribeBlock) {
|
|
57
|
+
const testMarkedAsOnly = updatedTestBlock.replace("test(", "test.only(");
|
|
58
|
+
return existingContents.replace(originalTestBlock, testMarkedAsOnly);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const updatedDescribeBlock = parentDescribeBlock.replace(originalTestBlock, updatedTestBlock);
|
|
62
|
+
// TODO: this should only happen when the describe block has "serial" execution
|
|
63
|
+
const describeMarkedAsOnly = updatedDescribeBlock.replace("test.describe(", "test.describe.only(");
|
|
64
|
+
return existingContents.replace(parentDescribeBlock, describeMarkedAsOnly);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
55
67
|
function createTestBlockForCreateTest(name, steps) {
|
|
56
68
|
return `
|
|
57
69
|
test.only("${name}", async ({page}) => {
|
|
@@ -70,7 +70,7 @@ async function generateTest(testCase, file, options) {
|
|
|
70
70
|
const [prependContent, strippedContent] = await (0, web_1.stripAndPrependImports)(response, testCase?.name);
|
|
71
71
|
let updatedContent = prependContent + contents + `\n\n${strippedContent}`;
|
|
72
72
|
if (isUpdate) {
|
|
73
|
-
const testBlock = (0, web_1.getTypescriptTestBlock)(testCase?.name, contents);
|
|
73
|
+
const { testBlock } = (0, web_1.getTypescriptTestBlock)(testCase?.name, contents);
|
|
74
74
|
contents = contents.replace(testBlock, `\n\n${strippedContent}`);
|
|
75
75
|
updatedContent = prependContent + contents;
|
|
76
76
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import LLMTracing from "./trace";
|
|
3
|
+
export declare function getLLMResult({ messages, trace, tools, tool_choice, }: {
|
|
4
|
+
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
|
5
|
+
trace?: LLMTracing;
|
|
6
|
+
tools?: OpenAI.Chat.Completions.ChatCompletionTool[];
|
|
7
|
+
tool_choice?: OpenAI.Chat.Completions.ChatCompletionToolChoiceOption;
|
|
8
|
+
}): Promise<OpenAI.Chat.Completions.ChatCompletionMessage | undefined>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/ai/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,UAAU,MAAM,SAAS,CAAC;AAEjC,wBAAsB,YAAY,CAAC,EACjC,QAAQ,EACR,KAAK,EACL,KAAK,EACL,WAAW,GACZ,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC;IAC/D,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,8BAA8B,CAAC;CACtE,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAsBrE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getLLMResult = void 0;
|
|
7
|
+
const openai_1 = __importDefault(require("openai"));
|
|
8
|
+
async function getLLMResult({ messages, trace, tools, tool_choice, }) {
|
|
9
|
+
const openai = new openai_1.default();
|
|
10
|
+
const model = "gpt-4o";
|
|
11
|
+
const parameters = {
|
|
12
|
+
temperature: 0.5,
|
|
13
|
+
};
|
|
14
|
+
const generation = trace?.startGeneration({
|
|
15
|
+
name: "get-llm-result",
|
|
16
|
+
model,
|
|
17
|
+
parameters,
|
|
18
|
+
input: messages,
|
|
19
|
+
});
|
|
20
|
+
const completion = await openai.chat.completions.create({
|
|
21
|
+
messages,
|
|
22
|
+
model,
|
|
23
|
+
tools,
|
|
24
|
+
...parameters,
|
|
25
|
+
tool_choice,
|
|
26
|
+
});
|
|
27
|
+
const output = completion.choices[0]?.message;
|
|
28
|
+
generation?.end({ output });
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
exports.getLLMResult = getLLMResult;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
export declare function getPrompt(name: string, vars: any): Promise<OpenAI.Chat.Completions.ChatCompletionMessageParam[]>;
|
|
3
|
+
export declare function getPromptForNextAction({ pageSnapshot, task, previousActions, lastActionErrors, }: {
|
|
4
|
+
pageSnapshot: string;
|
|
5
|
+
task: string;
|
|
6
|
+
previousActions: string[];
|
|
7
|
+
lastActionErrors: string[];
|
|
8
|
+
}): Promise<OpenAI.Chat.Completions.ChatCompletionMessageParam[]>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/ai/prompts/provider/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAS5B,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,GACR,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC,CAI/D;AAGD,wBAAsB,sBAAsB,CAAC,EAC3C,YAAiB,EACjB,IAAS,EACT,eAAoB,EACpB,gBAAqB,GACtB,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,iEASA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPromptForNextAction = exports.getPrompt = void 0;
|
|
4
|
+
const langfuse_1 = require("langfuse");
|
|
5
|
+
const langfuse = new langfuse_1.Langfuse({
|
|
6
|
+
secretKey: "sk-lf-30eee545-9c2c-4646-bfd9-8f38acf2fa8f",
|
|
7
|
+
publicKey: "pk-lf-c5c7cf23-de76-4098-ad81-594bce7cf330",
|
|
8
|
+
baseUrl: "https://us.cloud.langfuse.com",
|
|
9
|
+
flushAt: 1,
|
|
10
|
+
});
|
|
11
|
+
async function getPrompt(name, vars) {
|
|
12
|
+
const prompt = await langfuse.getPrompt(name, undefined, { type: "chat" });
|
|
13
|
+
const compiledPrompt = prompt.compile(vars);
|
|
14
|
+
return compiledPrompt;
|
|
15
|
+
}
|
|
16
|
+
exports.getPrompt = getPrompt;
|
|
17
|
+
// TODO: move this prompt to langfuse
|
|
18
|
+
async function getPromptForNextAction({ pageSnapshot = "", task = "", previousActions = [], lastActionErrors = [], }) {
|
|
19
|
+
const previousActionsStr = previousActions.join("\n\n ---- \n\n");
|
|
20
|
+
const prompt = await getPrompt("browsing-agent-next-action", {
|
|
21
|
+
pageSnapshot,
|
|
22
|
+
previousActionsStr,
|
|
23
|
+
task,
|
|
24
|
+
lastActionErrors,
|
|
25
|
+
});
|
|
26
|
+
return prompt;
|
|
27
|
+
}
|
|
28
|
+
exports.getPromptForNextAction = getPromptForNextAction;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
export default class LLMTracing {
|
|
3
|
+
private trace;
|
|
4
|
+
constructor({ name }: {
|
|
5
|
+
name: string;
|
|
6
|
+
});
|
|
7
|
+
get id(): string;
|
|
8
|
+
get url(): string;
|
|
9
|
+
event({ name, input, metadata, output, }: {
|
|
10
|
+
name: string;
|
|
11
|
+
input?: any;
|
|
12
|
+
metadata?: any;
|
|
13
|
+
output?: any;
|
|
14
|
+
}): void;
|
|
15
|
+
update({ input, output, metadata, }: {
|
|
16
|
+
input?: any;
|
|
17
|
+
output?: any;
|
|
18
|
+
metadata?: any;
|
|
19
|
+
}): void;
|
|
20
|
+
startSpan(name: string, input?: Record<string, any>, metadata?: Record<string, any>): {
|
|
21
|
+
end: ({ output }: {
|
|
22
|
+
output: Record<string, any>;
|
|
23
|
+
}) => void;
|
|
24
|
+
};
|
|
25
|
+
startGeneration({ name, model, parameters, input, }: {
|
|
26
|
+
name: string;
|
|
27
|
+
model: string;
|
|
28
|
+
parameters: Record<string, any>;
|
|
29
|
+
input: any;
|
|
30
|
+
}): {
|
|
31
|
+
end: ({ output, usage, }: {
|
|
32
|
+
output: any;
|
|
33
|
+
usage?: OpenAI.Completions.CompletionUsage | undefined;
|
|
34
|
+
}) => void;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bin/ai/trace/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAe5B,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,OAAO,CAAC,KAAK,CAAsB;gBACvB,EAAE,IAAI,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IAStC,IAAI,EAAE,WAEL;IAED,IAAI,GAAG,WAEN;IAED,KAAK,CAAC,EACJ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,GAAG,CAAC;QACZ,QAAQ,CAAC,EAAE,GAAG,CAAC;QACf,MAAM,CAAC,EAAE,GAAG,CAAC;KACd;IAID,MAAM,CAAC,EACL,KAAU,EACV,MAAW,EACX,QAAa,GACd,EAAE;QACD,KAAK,CAAC,EAAE,GAAG,CAAC;QACZ,MAAM,CAAC,EAAE,GAAG,CAAC;QACb,QAAQ,CAAC,EAAE,GAAG,CAAC;KAChB;IAID,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAC/B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM;;oBAIJ,OAAO,MAAM,EAAE,GAAG,CAAC;;;IAMnD,eAAe,CAAC,EACd,IAAS,EACT,KAAU,EACV,UAAe,EACf,KAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,KAAK,EAAE,GAAG,CAAC;KACZ;;oBAYa,GAAG;;cAET,IAAI;;CAcb"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const langfuse_1 = require("langfuse");
|
|
4
|
+
const session_1 = require("../../session");
|
|
5
|
+
const langfuse = new langfuse_1.Langfuse({
|
|
6
|
+
secretKey: "sk-lf-30eee545-9c2c-4646-bfd9-8f38acf2fa8f",
|
|
7
|
+
publicKey: "pk-lf-c5c7cf23-de76-4098-ad81-594bce7cf330",
|
|
8
|
+
baseUrl: "https://us.cloud.langfuse.com",
|
|
9
|
+
flushAt: 1,
|
|
10
|
+
});
|
|
11
|
+
process.on("exit", async () => await langfuse.flushAsync());
|
|
12
|
+
process.on("SIGINT", async () => await langfuse.flushAsync());
|
|
13
|
+
process.on("SIGTERM", async () => await langfuse.flushAsync());
|
|
14
|
+
class LLMTracing {
|
|
15
|
+
trace;
|
|
16
|
+
constructor({ name }) {
|
|
17
|
+
const sessionDetails = (0, session_1.getSessionDetails)();
|
|
18
|
+
this.trace = langfuse.trace({
|
|
19
|
+
name,
|
|
20
|
+
sessionId: sessionDetails.id,
|
|
21
|
+
release: sessionDetails.version,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
get id() {
|
|
25
|
+
return this.trace.traceId;
|
|
26
|
+
}
|
|
27
|
+
get url() {
|
|
28
|
+
return this.trace.getTraceUrl();
|
|
29
|
+
}
|
|
30
|
+
event({ name, input, metadata, output, }) {
|
|
31
|
+
this.trace.event({ name, input, metadata, output });
|
|
32
|
+
}
|
|
33
|
+
update({ input = {}, output = {}, metadata = {}, }) {
|
|
34
|
+
this.trace.update({ input, output, metadata });
|
|
35
|
+
}
|
|
36
|
+
startSpan(name, input = {}, metadata = {}) {
|
|
37
|
+
const span = this.trace.span({ name, metadata, input });
|
|
38
|
+
return {
|
|
39
|
+
end: ({ output }) => {
|
|
40
|
+
span.end({ output });
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
startGeneration({ name = "", model = "", parameters = {}, input = {}, }) {
|
|
45
|
+
const generation = this.trace.generation({
|
|
46
|
+
name,
|
|
47
|
+
model,
|
|
48
|
+
modelParameters: parameters,
|
|
49
|
+
input,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
end: ({ output, usage, }) => {
|
|
53
|
+
generation.end({
|
|
54
|
+
output,
|
|
55
|
+
usage: usage
|
|
56
|
+
? {
|
|
57
|
+
promptTokens: usage.prompt_tokens,
|
|
58
|
+
completionTokens: usage.completion_tokens,
|
|
59
|
+
totalTokens: usage.total_tokens,
|
|
60
|
+
}
|
|
61
|
+
: undefined,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.default = LLMTracing;
|
package/dist/bin/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/bin/reporter/ci.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,wBAAsB,UAAU,CAAC,SAAS,EAAE,QAAQ,EAAE,uBAerD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.reportOnCI = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
async function reportOnCI(scenarios) {
|
|
9
|
+
if (process.env.CI && process.env.GITHUB_OUTPUT) {
|
|
10
|
+
const testNames = scenarios.map((s) => s.name).join(" ");
|
|
11
|
+
const scenariosOutput = scenarios
|
|
12
|
+
.map((s) => {
|
|
13
|
+
return `**Scenario:** ${s.name} \n\n**Steps:**\n - ${s.steps.join("\n - ")} \n\n**Assert**:\n - ${s.assert}\n\n ----`;
|
|
14
|
+
})
|
|
15
|
+
.join("\n ----- \n");
|
|
16
|
+
const envVars = [
|
|
17
|
+
`summary<<EOF\n${scenariosOutput}\nEOF`,
|
|
18
|
+
`test_names=${testNames}`,
|
|
19
|
+
].join("\n");
|
|
20
|
+
await fs_extra_1.default.appendFile(process.env.GITHUB_OUTPUT, envVars);
|
|
21
|
+
}
|
|
22
|
+
return scenarios;
|
|
23
|
+
}
|
|
24
|
+
exports.reportOnCI = reportOnCI;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/scenarios/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,aAAa,EAAwB,MAAM,aAAa,CAAC;AAkF5E,iBAAe,eAAe,CAC5B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,EAAE,CAAC,CAiC1B;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadTestConfigs = void 0;
|
|
7
|
+
const google_auth_library_1 = require("google-auth-library");
|
|
8
|
+
const slugify_1 = __importDefault(require("slugify"));
|
|
9
|
+
function isValidJSON(str) {
|
|
10
|
+
try {
|
|
11
|
+
JSON.parse(str);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Method to update / add scenarios to the repo.
|
|
20
|
+
* @param path
|
|
21
|
+
* @returns updated paths of scenarios
|
|
22
|
+
*/
|
|
23
|
+
async function loadScenariosFromGsheet(path) {
|
|
24
|
+
const { GoogleSpreadsheet } = await import("google-spreadsheet");
|
|
25
|
+
const url = new URL(path);
|
|
26
|
+
const docId = url.pathname.split("/")[3];
|
|
27
|
+
const searchParams = new URLSearchParams(url.hash.split("#")[1]);
|
|
28
|
+
const sheetId = Number(searchParams.get("gid")) || 0;
|
|
29
|
+
// TODO: use oauth 2
|
|
30
|
+
const serviceAccountAuth = new google_auth_library_1.JWT({
|
|
31
|
+
email: process.env.GOOGLE_SERVICE_EMAIL,
|
|
32
|
+
key: Buffer.from(process.env.GOOGLE_SERVICE_EMAIL_PRIVATE_KEY, "base64").toString(),
|
|
33
|
+
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
|
|
34
|
+
});
|
|
35
|
+
const doc = new GoogleSpreadsheet(docId, serviceAccountAuth);
|
|
36
|
+
await doc.loadInfo();
|
|
37
|
+
const sheet = doc.sheetsById[sheetId];
|
|
38
|
+
const rows = await sheet.getRows();
|
|
39
|
+
const map = new Map();
|
|
40
|
+
rows.forEach((r) => {
|
|
41
|
+
// TODO: fix for case insensitive
|
|
42
|
+
const category = r.get("Category");
|
|
43
|
+
const name = r.get("Scenario");
|
|
44
|
+
const steps = r
|
|
45
|
+
.get("Steps")
|
|
46
|
+
.split("\n")
|
|
47
|
+
.map((s) => s.trim())
|
|
48
|
+
.filter((s) => !!s.length);
|
|
49
|
+
const assert = r.get("Assert");
|
|
50
|
+
const specPath = category
|
|
51
|
+
? `./tests/${category}.spec.ts`
|
|
52
|
+
: `./tests/${(0, slugify_1.default)(name)}.spec.ts`;
|
|
53
|
+
const scenario = {
|
|
54
|
+
steps,
|
|
55
|
+
name,
|
|
56
|
+
assert,
|
|
57
|
+
};
|
|
58
|
+
if (!map.get(specPath)) {
|
|
59
|
+
map.set(specPath, [scenario]);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const scenarios = map.get(specPath);
|
|
63
|
+
scenarios.push(scenario);
|
|
64
|
+
map.set(specPath, scenarios);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const [specPath, scenarios] of map.entries()) {
|
|
69
|
+
results.push({
|
|
70
|
+
specPath,
|
|
71
|
+
scenarios,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
async function loadTestConfigs(scenariosPath) {
|
|
77
|
+
// google sheets generation flow
|
|
78
|
+
// TODO: remove this whenever we are comfortable demoing using dashboard
|
|
79
|
+
if (scenariosPath.startsWith("https://docs.google.com/spreadsheets")) {
|
|
80
|
+
return await loadScenariosFromGsheet(scenariosPath);
|
|
81
|
+
// dev testing flow
|
|
82
|
+
}
|
|
83
|
+
else if (scenariosPath.endsWith(".ts")) {
|
|
84
|
+
return [
|
|
85
|
+
{
|
|
86
|
+
specPath: scenariosPath,
|
|
87
|
+
scenarios: [],
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
// api flow
|
|
91
|
+
}
|
|
92
|
+
else if (isValidJSON(atob(scenariosPath))) {
|
|
93
|
+
const str = atob(scenariosPath);
|
|
94
|
+
const config = JSON.parse(str);
|
|
95
|
+
const specPath = `./tests/${config.group || "index"}.spec.ts`;
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
specPath,
|
|
99
|
+
scenarios: [
|
|
100
|
+
{
|
|
101
|
+
name: config.name,
|
|
102
|
+
steps: config.steps.filter((s) => !!s),
|
|
103
|
+
assert: config.assert,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
options: config.options,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
throw Error("Invalid path for test scenarios");
|
|
111
|
+
}
|
|
112
|
+
exports.loadTestConfigs = loadTestConfigs;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/session/index.ts"],"names":[],"mappings":"AAKA,iBAAS,iBAAiB;;;EAKzB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSessionDetails = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const package_json_1 = __importDefault(require("./../../../package.json"));
|
|
9
|
+
const sessionId = crypto_1.default.randomUUID();
|
|
10
|
+
function getSessionDetails() {
|
|
11
|
+
return {
|
|
12
|
+
id: sessionId,
|
|
13
|
+
version: package_json_1.default.version,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
exports.getSessionDetails = getSessionDetails;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export declare function getTypescriptTestBlock(scenarioName: string, content: string):
|
|
1
|
+
export declare function getTypescriptTestBlock(scenarioName: string, content: string): {
|
|
2
|
+
testBlock: string | undefined;
|
|
3
|
+
parentDescribe: string | undefined;
|
|
4
|
+
};
|
|
2
5
|
export declare function appendToTestBlock(testBlock: string, content: string): string;
|
|
3
6
|
export declare function validateTypescript(filePath: string): string[];
|
|
4
7
|
export declare function stripAndPrependImports(content: string, testName: string): Promise<(string | undefined)[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"AAMA,wBAAgB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"AAMA,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;EAwB3E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAwD7D;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,mCAUjB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAShD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAQhD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,UAE5E;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,iBAMpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,UAcpD"}
|
|
@@ -12,10 +12,17 @@ const typescript_1 = __importDefault(require("typescript"));
|
|
|
12
12
|
function getTypescriptTestBlock(scenarioName, content) {
|
|
13
13
|
const project = new ts_morph_1.Project();
|
|
14
14
|
const sourceFile = project.createSourceFile("test.ts", content);
|
|
15
|
-
const
|
|
15
|
+
const testFunctionNode = sourceFile.getFirstDescendant((node) => !!(node.isKind(ts_morph_1.SyntaxKind.CallExpression) &&
|
|
16
16
|
node.getExpression().getText() === "test" &&
|
|
17
17
|
node.getArguments()[0]?.getText().includes(scenarioName)));
|
|
18
|
-
|
|
18
|
+
// TODO: support files that have 2 describe blocks with the same test name in them
|
|
19
|
+
const describeBlockNode = sourceFile.getFirstDescendant((node) => !!(node.isKind(ts_morph_1.SyntaxKind.CallExpression) &&
|
|
20
|
+
node.getExpression().getText() === "test.describe" &&
|
|
21
|
+
node.getText().includes(scenarioName)));
|
|
22
|
+
return {
|
|
23
|
+
testBlock: testFunctionNode?.getText(),
|
|
24
|
+
parentDescribe: describeBlockNode?.getText(),
|
|
25
|
+
};
|
|
19
26
|
}
|
|
20
27
|
exports.getTypescriptTestBlock = getTypescriptTestBlock;
|
|
21
28
|
function appendToTestBlock(testBlock, content) {
|
|
@@ -77,7 +84,7 @@ exports.validateTypescript = validateTypescript;
|
|
|
77
84
|
async function stripAndPrependImports(content, testName) {
|
|
78
85
|
const importRegexp = /import\s+\{[^}]*\}\s+from\s+["'][^"']+["'];?/g;
|
|
79
86
|
const imports = content.match(importRegexp);
|
|
80
|
-
const strippedContent = getTypescriptTestBlock(testName, content);
|
|
87
|
+
const { testBlock: strippedContent } = getTypescriptTestBlock(testName, content);
|
|
81
88
|
const prependContent = (imports?.join("\n") || "") + "\n\n";
|
|
82
89
|
return [prependContent, strippedContent];
|
|
83
90
|
}
|
|
@@ -109,7 +116,9 @@ function addNewImport(contents, modules, pkg) {
|
|
|
109
116
|
exports.addNewImport = addNewImport;
|
|
110
117
|
async function removeTestOnly(filePath) {
|
|
111
118
|
const contents = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
112
|
-
const updatedContent = contents
|
|
119
|
+
const updatedContent = contents
|
|
120
|
+
.replace("test.only(", "test(")
|
|
121
|
+
.replace("test.describe.only(", "test.describe(");
|
|
113
122
|
return fs_extra_1.default.writeFile(filePath, updatedContent);
|
|
114
123
|
}
|
|
115
124
|
exports.removeTestOnly = removeTestOnly;
|
package/dist/reporter/ci.js
CHANGED
|
@@ -26,7 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.reportOnCI = void 0;
|
|
27
27
|
const core = __importStar(require("@actions/core"));
|
|
28
28
|
async function reportOnCI(testCase) {
|
|
29
|
-
if (
|
|
29
|
+
if (process.env.CI) {
|
|
30
30
|
const scenariosOutput = `**Scenario:** ${testCase.name} \n\n**Steps:**\n - ${testCase.steps.join("\n - ")}`;
|
|
31
31
|
core.setOutput("summary", scenariosOutput);
|
|
32
32
|
core.setOutput("test_names", testCase.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/test-gen",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.9-beta.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -14,10 +14,20 @@
|
|
|
14
14
|
"url": "https://github.com/empirical-run/empirical.git"
|
|
15
15
|
},
|
|
16
16
|
"author": "Empirical Team <hey@empirical.run>",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "tsc --build --watch",
|
|
19
|
+
"build": "tsc --build && node ./../../tools/static-env-vars.js dist",
|
|
20
|
+
"clean": "tsc --build --clean",
|
|
21
|
+
"lint": "eslint .",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest"
|
|
24
|
+
},
|
|
17
25
|
"dependencies": {
|
|
18
26
|
"@actions/core": "^1.10.1",
|
|
19
27
|
"@aws-sdk/client-s3": "^3.614.0",
|
|
20
28
|
"@aws-sdk/s3-request-presigner": "^3.614.0",
|
|
29
|
+
"@empiricalrun/llm": "workspace:^",
|
|
30
|
+
"@empiricalrun/reporter": "workspace:^",
|
|
21
31
|
"@playwright/test": "^1.44.1",
|
|
22
32
|
"@types/sanitize-html": "^2.11.0",
|
|
23
33
|
"commander": "^12.1.0",
|
|
@@ -40,22 +50,12 @@
|
|
|
40
50
|
"slugify": "^1.6.6",
|
|
41
51
|
"ts-morph": "^23.0.0",
|
|
42
52
|
"tsx": "^4.16.2",
|
|
43
|
-
"typescript": "^5.3.3"
|
|
44
|
-
"@empiricalrun/llm": "^0.7.2",
|
|
45
|
-
"@empiricalrun/reporter": "^0.17.4"
|
|
53
|
+
"typescript": "^5.3.3"
|
|
46
54
|
},
|
|
47
55
|
"devDependencies": {
|
|
48
56
|
"@types/detect-port": "^1.3.5",
|
|
49
57
|
"@types/express": "^4.17.21",
|
|
50
58
|
"@types/fs-extra": "^11.0.4",
|
|
51
59
|
"@types/md5": "^2.3.5"
|
|
52
|
-
},
|
|
53
|
-
"scripts": {
|
|
54
|
-
"dev": "tsc --build --watch",
|
|
55
|
-
"build": "tsc --build && node ./../../tools/static-env-vars.js dist",
|
|
56
|
-
"clean": "tsc --build --clean",
|
|
57
|
-
"lint": "eslint .",
|
|
58
|
-
"test": "vitest run",
|
|
59
|
-
"test:watch": "vitest"
|
|
60
60
|
}
|
|
61
61
|
}
|