Skip to content

Commit 6b98796

Browse files
Merge pull request #35 from DeDuckProject/fix/playwright-resolve-from-cwd
Added hints injection (allows user to control prompt somewhat
2 parents bc33c15 + f31a423 commit 6b98796

9 files changed

Lines changed: 131 additions & 14 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ export default {
359359
360360
// Option B: Use an existing deploy preview
361361
// previewUrl: 'VERCEL_URL', // env var name or literal URL
362+
363+
// Hint: extra context injected into every LLM prompt.
364+
// Use this to tell the LLM about authentication, test accounts, app quirks, etc.
365+
hint: 'Use email demo@example.com and password "password" to log in.',
362366
},
363367
364368
// ── Trigger ───────────────────────────────────────────────────────────────
@@ -437,6 +441,30 @@ For files outside these conventions, use `routeMap` in the config to explicitly
437441

438442
---
439443

444+
## Passing hints to the LLM
445+
446+
The `app.hint` field lets you inject free-form context into every LLM prompt. The text is included verbatim under an **"App-specific notes"** heading, so the model sees it before generating any Playwright script.
447+
448+
Common uses:
449+
450+
- **Authentication** — tell the LLM which test account to use so it can log in before navigating to the changed page.
451+
- **Seed data** — describe what records exist in the database so the script can interact with realistic content.
452+
- **App quirks** — explain non-obvious UI patterns (e.g. a custom modal, a multi-step wizard) so the model handles them correctly.
453+
454+
```typescript
455+
export default {
456+
app: {
457+
startCommand: 'npm run dev',
458+
// Injected into every prompt sent to the LLM
459+
hint: 'Use email demo@example.com and password "password" to log in before navigating anywhere.',
460+
},
461+
} satisfies GitGlimpseConfig;
462+
```
463+
464+
The hint is applied to both diff-driven scripts and general overview demos, so it is always available regardless of trigger mode.
465+
466+
---
467+
440468
## Retry and fallback behavior
441469

442470
If the generated Playwright script fails (element not found, timeout, etc.), GitGlimpse:

packages/action/dist/check.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53433,7 +53433,9 @@ var AppConfigSchema = external_exports.object({
5343353433
timeout: external_exports.number().default(3e4)
5343453434
}).optional(),
5343553435
previewUrl: external_exports.string().optional(),
53436-
env: external_exports.record(external_exports.string()).optional()
53436+
env: external_exports.record(external_exports.string()).optional(),
53437+
/** Extra context appended to every LLM prompt (e.g. auth instructions, app-specific notes). */
53438+
hint: external_exports.string().optional()
5343753439
});
5343853440
var RecordingConfigSchema = external_exports.object({
5343953441
viewport: external_exports.object({

packages/action/dist/check.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/action/dist/index.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65381,7 +65381,10 @@ function buildScriptGenerationPrompt(options) {
6538165381
- Affected routes:
6538265382
${routeList}
6538365383
- Suggested demo flow: ${options.demoFlow}
65384-
65384+
${options.hint ? `
65385+
## App-specific notes
65386+
${options.hint}
65387+
` : ""}
6538565388
## Diff
6538665389
\`\`\`
6538765390
${truncatedDiff}
@@ -65422,7 +65425,10 @@ Produce a short, visually engaging tour that demonstrates the app is running and
6542265425
## Context
6542365426
- Base URL: ${options.baseUrl}
6542465427
- Viewport: ${options.viewport.width}x${options.viewport.height}
65425-
65428+
${options.hint ? `
65429+
## App-specific notes
65430+
${options.hint}
65431+
` : ""}
6542665432
## Output format
6542765433
Respond with ONLY the TypeScript script, no markdown fences, no explanation:
6542865434

@@ -65486,12 +65492,13 @@ async function generateDemoScript(client, analysis, rawDiff, baseUrl, config, ge
6548665492
routes: analysis.affectedRoutes,
6548765493
demoFlow: analysis.suggestedDemoFlow,
6548865494
maxDuration: recording.maxDuration,
65489-
viewport: recording.viewport
65495+
viewport: recording.viewport,
65496+
hint: config.app.hint
6549065497
};
6549165498
const errors = [];
6549265499
let lastScript = "";
6549365500
for (let attempt = 1; attempt <= MAX_RETRIES + 1; attempt++) {
65494-
const prompt = attempt === 1 ? generalDemo ? buildGeneralDemoPrompt({ baseUrl, maxDuration: recording.maxDuration, viewport: recording.viewport }) : buildScriptGenerationPrompt(promptOptions) : buildRetryPrompt(lastScript, errors[errors.length - 1] ?? "", "", promptOptions);
65501+
const prompt = attempt === 1 ? generalDemo ? buildGeneralDemoPrompt({ baseUrl, maxDuration: recording.maxDuration, viewport: recording.viewport, hint: config.app.hint }) : buildScriptGenerationPrompt(promptOptions) : buildRetryPrompt(lastScript, errors[errors.length - 1] ?? "", "", promptOptions);
6549565502
const response = await client.messages.create({
6549665503
model: llm.model,
6549765504
max_tokens: 4096,
@@ -69948,7 +69955,9 @@ var AppConfigSchema = external_exports.object({
6994869955
timeout: external_exports.number().default(3e4)
6994969956
}).optional(),
6995069957
previewUrl: external_exports.string().optional(),
69951-
env: external_exports.record(external_exports.string()).optional()
69958+
env: external_exports.record(external_exports.string()).optional(),
69959+
/** Extra context appended to every LLM prompt (e.g. auth instructions, app-specific notes). */
69960+
hint: external_exports.string().optional()
6995269961
});
6995369962
var RecordingConfigSchema = external_exports.object({
6995469963
viewport: external_exports.object({

packages/action/dist/index.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/config/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const AppConfigSchema = z.object({
2222
.optional(),
2323
previewUrl: z.string().optional(),
2424
env: z.record(z.string()).optional(),
25+
/** Extra context appended to every LLM prompt (e.g. auth instructions, app-specific notes). */
26+
hint: z.string().optional(),
2527
});
2628

2729
export const RecordingConfigSchema = z.object({

packages/core/src/generator/prompts.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface ScriptPromptOptions {
77
demoFlow: string;
88
maxDuration: number;
99
viewport: { width: number; height: number };
10+
hint?: string;
1011
}
1112

1213
export function buildScriptGenerationPrompt(options: ScriptPromptOptions): string {
@@ -48,7 +49,7 @@ export function buildScriptGenerationPrompt(options: ScriptPromptOptions): strin
4849
- Affected routes:
4950
${routeList}
5051
- Suggested demo flow: ${options.demoFlow}
51-
52+
${options.hint ? `\n## App-specific notes\n${options.hint}\n` : ''}
5253
## Diff
5354
\`\`\`
5455
${truncatedDiff}
@@ -64,7 +65,7 @@ export async function demo(page: Page): Promise<void> {
6465
}`;
6566
}
6667

67-
export function buildGeneralDemoPrompt(options: Pick<ScriptPromptOptions, 'baseUrl' | 'maxDuration' | 'viewport'>): string {
68+
export function buildGeneralDemoPrompt(options: Pick<ScriptPromptOptions, 'baseUrl' | 'maxDuration' | 'viewport' | 'hint'>): string {
6869
return `You are a Playwright script generator. Generate a TypeScript Playwright script that records a general overview demo of a web app — as if showing it to someone for the first time.
6970
7071
## Goal
@@ -90,7 +91,7 @@ Produce a short, visually engaging tour that demonstrates the app is running and
9091
## Context
9192
- Base URL: ${options.baseUrl}
9293
- Viewport: ${options.viewport.width}x${options.viewport.height}
93-
94+
${options.hint ? `\n## App-specific notes\n${options.hint}\n` : ''}
9495
## Output format
9596
Respond with ONLY the TypeScript script, no markdown fences, no explanation:
9697

packages/core/src/generator/script-generator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export async function generateDemoScript(
3030
demoFlow: analysis.suggestedDemoFlow,
3131
maxDuration: recording.maxDuration,
3232
viewport: recording.viewport,
33+
hint: config.app.hint,
3334
};
3435

3536
const errors: string[] = [];
@@ -39,7 +40,7 @@ export async function generateDemoScript(
3940
const prompt =
4041
attempt === 1
4142
? (generalDemo
42-
? buildGeneralDemoPrompt({ baseUrl, maxDuration: recording.maxDuration, viewport: recording.viewport })
43+
? buildGeneralDemoPrompt({ baseUrl, maxDuration: recording.maxDuration, viewport: recording.viewport, hint: config.app.hint })
4344
: buildScriptGenerationPrompt(promptOptions))
4445
: buildRetryPrompt(lastScript, errors[errors.length - 1] ?? '', '', promptOptions);
4546

tests/unit/prompts.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
buildScriptGenerationPrompt,
4+
buildGeneralDemoPrompt,
5+
} from '../../packages/core/src/generator/prompts.js';
6+
import type { ScriptPromptOptions } from '../../packages/core/src/generator/prompts.js';
7+
8+
const BASE_OPTIONS: ScriptPromptOptions = {
9+
baseUrl: 'http://localhost:3000',
10+
diff: 'diff --git a/src/Button.tsx b/src/Button.tsx\n+export function Button() {}',
11+
routes: [{ route: '/', file: 'src/Button.tsx' }],
12+
demoFlow: 'Navigate to home and click the button',
13+
maxDuration: 30,
14+
viewport: { width: 1280, height: 720 },
15+
};
16+
17+
describe('buildScriptGenerationPrompt', () => {
18+
it('includes hint under App-specific notes when provided', () => {
19+
const prompt = buildScriptGenerationPrompt({
20+
...BASE_OPTIONS,
21+
hint: 'Log in with demo@example.com / password',
22+
});
23+
expect(prompt).toContain('## App-specific notes');
24+
expect(prompt).toContain('Log in with demo@example.com / password');
25+
});
26+
27+
it('omits App-specific notes section when hint is not provided', () => {
28+
const prompt = buildScriptGenerationPrompt(BASE_OPTIONS);
29+
expect(prompt).not.toContain('## App-specific notes');
30+
});
31+
32+
it('omits App-specific notes section when hint is empty string', () => {
33+
const prompt = buildScriptGenerationPrompt({ ...BASE_OPTIONS, hint: '' });
34+
expect(prompt).not.toContain('## App-specific notes');
35+
});
36+
37+
it('places hint before the Diff section', () => {
38+
const prompt = buildScriptGenerationPrompt({
39+
...BASE_OPTIONS,
40+
hint: 'my hint',
41+
});
42+
const hintPos = prompt.indexOf('## App-specific notes');
43+
const diffPos = prompt.indexOf('## Diff');
44+
expect(hintPos).toBeGreaterThan(-1);
45+
expect(hintPos).toBeLessThan(diffPos);
46+
});
47+
});
48+
49+
describe('buildGeneralDemoPrompt', () => {
50+
const BASE_GENERAL = {
51+
baseUrl: 'http://localhost:3000',
52+
maxDuration: 30,
53+
viewport: { width: 1280, height: 720 },
54+
};
55+
56+
it('includes hint under App-specific notes when provided', () => {
57+
const prompt = buildGeneralDemoPrompt({
58+
...BASE_GENERAL,
59+
hint: 'Use test account admin / secret',
60+
});
61+
expect(prompt).toContain('## App-specific notes');
62+
expect(prompt).toContain('Use test account admin / secret');
63+
});
64+
65+
it('omits App-specific notes section when hint is not provided', () => {
66+
const prompt = buildGeneralDemoPrompt(BASE_GENERAL);
67+
expect(prompt).not.toContain('## App-specific notes');
68+
});
69+
70+
it('omits App-specific notes section when hint is empty string', () => {
71+
const prompt = buildGeneralDemoPrompt({ ...BASE_GENERAL, hint: '' });
72+
expect(prompt).not.toContain('## App-specific notes');
73+
});
74+
});

0 commit comments

Comments
 (0)