Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/core/src/Pilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { StepPerformer } from "@/performers/step-performer/StepPerformer";
import { CacheHandler } from "@/common/cacheHandler/CacheHandler";
import { AutoPerformer } from "@/performers/auto-performer/AutoPerformer";
import { AutoPerformerPromptCreator } from "@/performers/auto-performer/AutoPerformerPromptCreator";
import { UserGoalToPilotGoalPromptCreator } from "@/performers/auto-performer/UserGoalToPilotGoalPromptCreator";
import { AutoReport } from "@/types/auto";
import { StepPerformerPromptCreator } from "@/performers/step-performer/StepPerformerPromptCreator";
import { CodeEvaluator } from "@/common/CodeEvaluator";
Expand Down Expand Up @@ -90,6 +91,7 @@ export class Pilot {
this.screenCapturer,
this.cacheHandler,
this.snapshotComparator,
new UserGoalToPilotGoalPromptCreator(),
);
}

Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/common/extract/extractTaggedOutputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const AUTOPILOT_REVIEW_SECTION = {
score: { tag: "SCORE", isRequired: false },
};

const GOAL_MAPPER = {
goal: { tag: "GOAL", isRequired: true },
};

function extractTaggedOutputs<M extends OutputsMapping>({
text,
outputsMapper,
Expand Down Expand Up @@ -63,3 +67,8 @@ export function extractAutoPilotStepOutputs(
const outputsMapper = { ...BASE_AUTOPILOT_STEP, ...reviewSections };
return extractTaggedOutputs({ text, outputsMapper });
}

export function extractGoalOutput(text: string): string {
const result = extractTaggedOutputs({ text, outputsMapper: GOAL_MAPPER });
return result.goal;
}
17 changes: 14 additions & 3 deletions packages/core/src/performers/auto-performer/AutoPerformer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AutoReport,
} from "@/types";
import { StepPerformer } from "../step-performer/StepPerformer";
import { UserGoalToPilotGoalPromptCreator } from "./UserGoalToPilotGoalPromptCreator";
import { AUTOPILOT_REVIEW_DEFAULTS } from "./reviews/reviewDefaults";

const GOAL = "tap button";
Expand Down Expand Up @@ -63,7 +64,10 @@ The review of i18n
<SCORE>
6/10
</SCORE>
</INTERNATIONALIZATION>`;
</INTERNATIONALIZATION>
<GOAL>
tap button
</GOAL>`;

const SNAPSHOT_DATA = "snapshot_data";

Expand All @@ -76,6 +80,7 @@ describe("AutoPerformer", () => {
let mockCaptureResult: ScreenCapturerResult;
let mockCacheHandler: jest.Mocked<CacheHandler>;
let mockSnapshotComparator: jest.Mocked<SnapshotComparator>;
let mockUserGoalToPilotGoalPromptCreator: jest.Mocked<UserGoalToPilotGoalPromptCreator>;

beforeEach(() => {
jest.resetAllMocks();
Expand Down Expand Up @@ -118,6 +123,9 @@ describe("AutoPerformer", () => {
compareSnapshot: jest.fn(),
} as unknown as jest.Mocked<SnapshotComparator>;

mockUserGoalToPilotGoalPromptCreator = {
createPrompt: jest.fn(),
} as unknown as jest.Mocked<UserGoalToPilotGoalPromptCreator>;
// Instantiate PilotPerformer with the mocks
performer = new AutoPerformer(
mockPromptCreator,
Expand All @@ -126,6 +134,7 @@ describe("AutoPerformer", () => {
mockScreenCapturer,
mockCacheHandler,
mockSnapshotComparator,
mockUserGoalToPilotGoalPromptCreator,
);
});

Expand All @@ -152,7 +161,9 @@ describe("AutoPerformer", () => {

// Mock the capture function to return mockCaptureResult
mockScreenCapturer.capture.mockResolvedValue(mockCaptureResult);

mockUserGoalToPilotGoalPromptCreator.createPrompt.mockReturnValue(
GENERATED_PROMPT,
);
mockPromptCreator.createPrompt.mockReturnValue(GENERATED_PROMPT);
mockPromptHandler.runPrompt.mockResolvedValue(promptResult);
mockPromptHandler.isSnapshotImageSupported.mockReturnValue(
Expand Down Expand Up @@ -346,6 +357,7 @@ describe("AutoPerformer", () => {

describe("perform", () => {
it("should perform multiple steps until success is returned", async () => {
setupMocks();
const pilotOutputStep1: AutoStepReport = {
screenDescription: "Screen 1",
plan: {
Expand Down Expand Up @@ -498,7 +510,6 @@ describe("AutoPerformer", () => {
describe("with review types", () => {
it("should perform an intent successfully with snapshot image support with review", async () => {
setupMocks();

const result = await performer.analyseScreenAndCreatePilotStep(
GOAL,
[],
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/performers/auto-performer/AutoPerformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
AutoStepReport,
} from "@/types/auto";
import { PreviousStep, PromptHandler, ScreenCapturerResult } from "@/types";
import { UserGoalToPilotGoalPromptCreator } from "./UserGoalToPilotGoalPromptCreator";
import {
extractAutoPilotReviewOutputs,
extractAutoPilotStepOutputs,
extractGoalOutput,
} from "@/common/extract/extractTaggedOutputs";
import { StepPerformer } from "@/performers/step-performer/StepPerformer";
import { ScreenCapturer } from "@/common/snapshot/ScreenCapturer";
Expand All @@ -28,6 +30,7 @@ export class AutoPerformer {
private screenCapturer: ScreenCapturer,
private cacheHandler: CacheHandler,
private snapshotComparator: SnapshotComparator,
private userGoalToPilotGoalPromptCreator: UserGoalToPilotGoalPromptCreator,
) {}

private extractReviewOutput(text: string): AutoReviewSection {
Expand Down Expand Up @@ -224,6 +227,7 @@ export class AutoPerformer {
goal: string,
reviewSectionTypes?: AutoReviewSectionConfig[],
): Promise<AutoReport> {
goal = await this.getValidGoal(goal);
const maxSteps = 100;
let previousSteps: AutoPreviousStep[] = [];
let pilotSteps: PreviousStep[] = [];
Expand Down Expand Up @@ -322,4 +326,32 @@ export class AutoPerformer {

return matchingEntry?.value as AutoStepReport;
}

private async getValidGoal(goal: string): Promise<string> {
const key = this.cacheHandler.generateCacheKey({ goal });
const runPromptAndExtractGoal = async (): Promise<string> => {
return extractGoalOutput(
await this.promptHandler.runPrompt(
this.userGoalToPilotGoalPromptCreator.createPrompt(goal),
),
);
};

if (this.cacheHandler.isCacheInUse() && key) {
const cachedValues =
this.cacheHandler.getFromPersistentCache<string>(key);
if (cachedValues) {
const matchingEntry =
this.cacheHandler.findMatchingCacheEntry<string>(cachedValues);
if (matchingEntry?.value) {
return matchingEntry.value as string;
}
}
const validGoal = await runPromptAndExtractGoal();
this.cacheHandler.addToTemporaryCache(key, validGoal);
return validGoal;
}

return runPromptAndExtractGoal();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { UserGoalToPilotGoalPromptCreator } from "./UserGoalToPilotGoalPromptCreator";

describe("UserGoalToPilotGoalPromptCreator", () => {
let promptCreator: UserGoalToPilotGoalPromptCreator;
const sampleGoal = "Test that users can log in successfully";

beforeEach(() => {
promptCreator = new UserGoalToPilotGoalPromptCreator();
});

it("should create a prompt with the provided goal and correct sections", () => {
const prompt = promptCreator.createPrompt(sampleGoal);

// Check that the base prompt section is included
expect(prompt).toContain("# General Goal to QA Test Goal Conversion");

// Check that the context section includes the provided goal
expect(prompt).toContain("## Context");
expect(prompt).toContain("### Goal Provided");
expect(prompt).toContain(sampleGoal);

// Check that instructions and examples sections are included
expect(prompt).toContain("## Instructions");
expect(prompt).toContain("## Examples");

// Check that the final instruction is appended
expect(prompt).toContain("Please provide your response below:");

// Snapshot test to catch any unintended changes
expect(prompt).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
export class UserGoalToPilotGoalPromptCreator {
constructor() {}

createPrompt(goal: string): string {
return [
this.createBasePrompt(),
this.createContext(goal),
this.createInstructions(),
this.createExamples(),
"Please provide your response below:",
]
.flat()
.join("\n");
}

private createBasePrompt(): string[] {
return [
"# General Goal to QA Test Goal Conversion",
"",
"You are an AI assistant tasked with:",
"",
"1. Reading a general goal or objective provided.",
"2. Converting it into a **QA/test-oriented goal** in a single line—something a tester would verify or validate.",
"3. Wrapping the final goal in <GOAL>...</GOAL> **with no extra text** outside those tags.",
"4. If the goal is unclear or invalid, throw an informative error in one sentence.",
"",
"Make sure the resulting goal is framed as an instruction or verification task for a tester, rather than from the perspective of a feature creator.",
];
}

private createContext(goal: string): string[] {
return ["## Context", "", "### Goal Provided", "", goal, ""];
}

private createInstructions(): string[] {
return [
"## Instructions",
"",
"1. Extract the **key testing objective** from the provided goal. The final statement should read like a verification or validation instruction (e.g., “Verify that…”, “Ensure that…”, “Make sure…”).",
"2. Keep it concise and imperative",
"3. Output **only** <GOAL> ... </GOAL> with no extra text outside those tags.",
"",
"If you cannot confidently form a concise test goal, respond with:",
"",
"Error: Unable to determine a concise test goal from the given goal.",
"",
];
}

private createExamples(): string[] {
return [
"## Examples",
"",
"### Example 1",
"#### Input",
"",
"Track orders to see shipping updates in real time.",
"",
"#### Correct Output (QA Perspective)",
"",
"<GOAL>Verify that shipping updates are displayed in real time when tracking orders</GOAL>",
"",
"### Example 2",
"#### Input",
"",
"Select from multiple banner layout presets for quick design.",
"",
"#### Correct Output (QA Perspective)",
"",
"<GOAL>Ensure multiple banner layout presets can be selected</GOAL>",
"",
"### Example 3 (Error Case)",
"#### Input",
"",
"Nonsensical goal with no clear objective.",
"",
"#### Correct Output",
"",
"Error: Unable to determine a concise test goal from the given goal.",
"",
"### Example 4 (Longer Goal)",
"#### Input",
"",
"as a user i would like to manage multiple shipping addresses from a single interface, including the ability to edit, delete, and set a default address, so that they can easily handle their delivery preferences without having to navigate to different pages.",
"",
"#### Correct Output (QA Perspective)",
"",
"<GOAL>Verify that users can add, edit, remove, and set a default shipping address from one interface</GOAL>",
"",
"### Example 5 (Longer Goal)",
"#### Input",
"",
"Provide a feature for scheduling promotional email campaigns with custom templates, allowing marketers to target segments, set time zones, and receive detailed send reports for each campaign so they can optimize future mailouts.",
"",
"#### Correct Output (QA Perspective)",
"",
"<GOAL>Ensure that marketers can schedule campaigns with custom templates, target segments, set time zones, and view detailed reports</GOAL>",
"",
"### Example 6 (Longer Goal)",
"#### Input",
"",
"As a user, i would like to have an analytics dashboard that tracks daily active users, session durations, and peak usage times. The goal is to give product managers real-time insights into user engagement patterns and to enable them to make quick, data-driven decisions.",
"",
"#### Correct Output (QA Perspective)",
"",
"<GOAL>Verify the analytics dashboard accurately tracks daily active users, session durations, and peak usage times in real time</GOAL>",
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`UserGoalToPilotGoalPromptCreator should create a prompt with the provided goal and correct sections 1`] = `
"# General Goal to QA Test Goal Conversion

You are an AI assistant tasked with:

1. Reading a general goal or objective provided.
2. Converting it into a **QA/test-oriented goal** in a single line—something a tester would verify or validate.
3. Wrapping the final goal in <GOAL>...</GOAL> **with no extra text** outside those tags.
4. If the goal is unclear or invalid, throw an informative error in one sentence.

Make sure the resulting goal is framed as an instruction or verification task for a tester, rather than from the perspective of a feature creator.
## Context

### Goal Provided

Test that users can log in successfully

## Instructions

1. Extract the **key testing objective** from the provided goal. The final statement should read like a verification or validation instruction (e.g., “Verify that…”, “Ensure that…”, “Make sure…”).
2. Keep it concise and imperative
3. Output **only** <GOAL> ... </GOAL> with no extra text outside those tags.

If you cannot confidently form a concise test goal, respond with:

Error: Unable to determine a concise test goal from the given goal.

## Examples

### Example 1
#### Input

Track orders to see shipping updates in real time.

#### Correct Output (QA Perspective)

<GOAL>Verify that shipping updates are displayed in real time when tracking orders</GOAL>

### Example 2
#### Input

Select from multiple banner layout presets for quick design.

#### Correct Output (QA Perspective)

<GOAL>Ensure multiple banner layout presets can be selected</GOAL>

### Example 3 (Error Case)
#### Input

Nonsensical goal with no clear objective.

#### Correct Output

Error: Unable to determine a concise test goal from the given goal.

### Example 4 (Longer Goal)
#### Input

as a user i would like to manage multiple shipping addresses from a single interface, including the ability to edit, delete, and set a default address, so that they can easily handle their delivery preferences without having to navigate to different pages.

#### Correct Output (QA Perspective)

<GOAL>Verify that users can add, edit, remove, and set a default shipping address from one interface</GOAL>

### Example 5 (Longer Goal)
#### Input

Provide a feature for scheduling promotional email campaigns with custom templates, allowing marketers to target segments, set time zones, and receive detailed send reports for each campaign so they can optimize future mailouts.

#### Correct Output (QA Perspective)

<GOAL>Ensure that marketers can schedule campaigns with custom templates, target segments, set time zones, and view detailed reports</GOAL>

### Example 6 (Longer Goal)
#### Input

As a user, i would like to have an analytics dashboard that tracks daily active users, session durations, and peak usage times. The goal is to give product managers real-time insights into user engagement patterns and to enable them to make quick, data-driven decisions.

#### Correct Output (QA Perspective)

<GOAL>Verify the analytics dashboard accurately tracks daily active users, session durations, and peak usage times in real time</GOAL>
Please provide your response below:"
`;
Loading