whisker-ux 0.1.0
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/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +324 -0
- package/dist/agent.js.map +1 -0
- package/dist/browser.d.ts +16 -0
- package/dist/browser.js +231 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +94 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +104 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.js +81 -0
- package/dist/prompts.js.map +1 -0
- package/dist/report.d.ts +6 -0
- package/dist/report.js +144 -0
- package/dist/report.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +19 -0
- package/dist/ui.js +297 -0
- package/dist/ui.js.map +1 -0
- package/package.json +59 -0
package/dist/browser.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
function mapKeyNames(key) {
|
|
3
|
+
const keyMap = {
|
|
4
|
+
return: "Enter",
|
|
5
|
+
enter: "Enter",
|
|
6
|
+
ctrl: "Control",
|
|
7
|
+
control: "Control",
|
|
8
|
+
super: "Meta",
|
|
9
|
+
cmd: "Meta",
|
|
10
|
+
command: "Meta",
|
|
11
|
+
alt: "Alt",
|
|
12
|
+
shift: "Shift",
|
|
13
|
+
tab: "Tab",
|
|
14
|
+
escape: "Escape",
|
|
15
|
+
esc: "Escape",
|
|
16
|
+
backspace: "Backspace",
|
|
17
|
+
delete: "Delete",
|
|
18
|
+
space: " ",
|
|
19
|
+
arrowup: "ArrowUp",
|
|
20
|
+
arrowdown: "ArrowDown",
|
|
21
|
+
arrowleft: "ArrowLeft",
|
|
22
|
+
arrowright: "ArrowRight",
|
|
23
|
+
up: "ArrowUp",
|
|
24
|
+
down: "ArrowDown",
|
|
25
|
+
left: "ArrowLeft",
|
|
26
|
+
right: "ArrowRight",
|
|
27
|
+
pageup: "PageUp",
|
|
28
|
+
pagedown: "PageDown",
|
|
29
|
+
page_up: "PageUp",
|
|
30
|
+
page_down: "PageDown",
|
|
31
|
+
home: "Home",
|
|
32
|
+
end: "End",
|
|
33
|
+
};
|
|
34
|
+
return key
|
|
35
|
+
.split("+")
|
|
36
|
+
.map((part) => {
|
|
37
|
+
const lower = part.trim().toLowerCase();
|
|
38
|
+
return keyMap[lower] ?? part.trim();
|
|
39
|
+
})
|
|
40
|
+
.join("+");
|
|
41
|
+
}
|
|
42
|
+
export class BrowserManager {
|
|
43
|
+
config;
|
|
44
|
+
browser = null;
|
|
45
|
+
context = null;
|
|
46
|
+
page = null;
|
|
47
|
+
consoleErrors = [];
|
|
48
|
+
networkFailures = [];
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.config = config;
|
|
51
|
+
}
|
|
52
|
+
async launch() {
|
|
53
|
+
this.browser = await chromium.launch({
|
|
54
|
+
headless: false,
|
|
55
|
+
args: [
|
|
56
|
+
`--window-size=${this.config.viewport.width},${this.config.viewport.height + 100}`,
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
this.context = await this.browser.newContext({
|
|
60
|
+
viewport: {
|
|
61
|
+
width: this.config.viewport.width,
|
|
62
|
+
height: this.config.viewport.height,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
this.page = await this.context.newPage();
|
|
66
|
+
// Capture console errors
|
|
67
|
+
this.page.on("console", (msg) => {
|
|
68
|
+
if (msg.type() === "error") {
|
|
69
|
+
this.consoleErrors.push(`[${new Date().toISOString()}] ${msg.text()}`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// Capture unhandled page errors
|
|
73
|
+
this.page.on("pageerror", (err) => {
|
|
74
|
+
this.consoleErrors.push(`[${new Date().toISOString()}] Uncaught: ${err.message}`);
|
|
75
|
+
});
|
|
76
|
+
// Capture network failures (4xx/5xx)
|
|
77
|
+
this.page.on("response", (response) => {
|
|
78
|
+
if (response.status() >= 400) {
|
|
79
|
+
this.networkFailures.push({
|
|
80
|
+
url: response.url(),
|
|
81
|
+
method: response.request().method(),
|
|
82
|
+
status: response.status(),
|
|
83
|
+
statusText: response.statusText(),
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// Capture failed requests (network errors)
|
|
89
|
+
this.page.on("requestfailed", (request) => {
|
|
90
|
+
this.networkFailures.push({
|
|
91
|
+
url: request.url(),
|
|
92
|
+
method: request.method(),
|
|
93
|
+
status: 0,
|
|
94
|
+
statusText: request.failure()?.errorText ?? "Request failed",
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
// Handle popups - switch to new page
|
|
99
|
+
this.context.on("page", (newPage) => {
|
|
100
|
+
this.page = newPage;
|
|
101
|
+
newPage.on("console", (msg) => {
|
|
102
|
+
if (msg.type() === "error") {
|
|
103
|
+
this.consoleErrors.push(`[${new Date().toISOString()}] ${msg.text()}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
newPage.on("pageerror", (err) => {
|
|
107
|
+
this.consoleErrors.push(`[${new Date().toISOString()}] Uncaught: ${err.message}`);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
await this.page.goto(this.config.url, { waitUntil: "domcontentloaded" });
|
|
111
|
+
}
|
|
112
|
+
async takeScreenshot() {
|
|
113
|
+
if (!this.page)
|
|
114
|
+
throw new Error("Browser not launched");
|
|
115
|
+
const buffer = await this.page.screenshot({
|
|
116
|
+
type: "png",
|
|
117
|
+
fullPage: false,
|
|
118
|
+
});
|
|
119
|
+
return buffer.toString("base64");
|
|
120
|
+
}
|
|
121
|
+
async executeAction(action) {
|
|
122
|
+
if (!this.page)
|
|
123
|
+
throw new Error("Browser not launched");
|
|
124
|
+
const page = this.page;
|
|
125
|
+
switch (action.action) {
|
|
126
|
+
case "screenshot":
|
|
127
|
+
// No-op: screenshot is taken separately by the caller
|
|
128
|
+
break;
|
|
129
|
+
case "left_click":
|
|
130
|
+
if (action.coordinate) {
|
|
131
|
+
await page.mouse.click(action.coordinate[0], action.coordinate[1], {
|
|
132
|
+
button: "left",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "right_click":
|
|
137
|
+
if (action.coordinate) {
|
|
138
|
+
await page.mouse.click(action.coordinate[0], action.coordinate[1], {
|
|
139
|
+
button: "right",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
case "middle_click":
|
|
144
|
+
if (action.coordinate) {
|
|
145
|
+
await page.mouse.click(action.coordinate[0], action.coordinate[1], {
|
|
146
|
+
button: "middle",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "double_click":
|
|
151
|
+
if (action.coordinate) {
|
|
152
|
+
await page.mouse.dblclick(action.coordinate[0], action.coordinate[1]);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
case "triple_click":
|
|
156
|
+
if (action.coordinate) {
|
|
157
|
+
await page.mouse.click(action.coordinate[0], action.coordinate[1], {
|
|
158
|
+
clickCount: 3,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case "type":
|
|
163
|
+
if (action.text) {
|
|
164
|
+
await page.keyboard.type(action.text, { delay: 30 });
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case "key":
|
|
168
|
+
if (action.text) {
|
|
169
|
+
const mapped = mapKeyNames(action.text);
|
|
170
|
+
await page.keyboard.press(mapped);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case "mouse_move":
|
|
174
|
+
if (action.coordinate) {
|
|
175
|
+
await page.mouse.move(action.coordinate[0], action.coordinate[1]);
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
case "scroll":
|
|
179
|
+
if (action.coordinate && action.scroll_direction) {
|
|
180
|
+
await page.mouse.move(action.coordinate[0], action.coordinate[1]);
|
|
181
|
+
const amount = (action.scroll_amount ?? 3) * 100;
|
|
182
|
+
const deltaX = action.scroll_direction === "left"
|
|
183
|
+
? -amount
|
|
184
|
+
: action.scroll_direction === "right"
|
|
185
|
+
? amount
|
|
186
|
+
: 0;
|
|
187
|
+
const deltaY = action.scroll_direction === "down"
|
|
188
|
+
? amount
|
|
189
|
+
: action.scroll_direction === "up"
|
|
190
|
+
? -amount
|
|
191
|
+
: 0;
|
|
192
|
+
await page.mouse.wheel(deltaX, deltaY);
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
case "left_click_drag":
|
|
196
|
+
if (action.coordinate) {
|
|
197
|
+
const startX = action.start_coordinate?.[0] ?? 0;
|
|
198
|
+
const startY = action.start_coordinate?.[1] ?? 0;
|
|
199
|
+
await page.mouse.move(startX, startY);
|
|
200
|
+
await page.mouse.down();
|
|
201
|
+
await page.mouse.move(action.coordinate[0], action.coordinate[1]);
|
|
202
|
+
await page.mouse.up();
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case "wait":
|
|
206
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
207
|
+
break;
|
|
208
|
+
default:
|
|
209
|
+
console.warn(` Unknown action type: ${action.action}`);
|
|
210
|
+
}
|
|
211
|
+
// Brief pause after actions to let the page settle
|
|
212
|
+
if (action.action !== "screenshot" && action.action !== "wait") {
|
|
213
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async close() {
|
|
217
|
+
if (this.browser) {
|
|
218
|
+
await this.browser.close();
|
|
219
|
+
this.browser = null;
|
|
220
|
+
this.context = null;
|
|
221
|
+
this.page = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
getConsoleErrors() {
|
|
225
|
+
return [...this.consoleErrors];
|
|
226
|
+
}
|
|
227
|
+
getNetworkFailures() {
|
|
228
|
+
return [...this.networkFailures];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { runSession } from "./agent.js";
|
|
5
|
+
import { writeReport } from "./report.js";
|
|
6
|
+
import { getApiKey, runSetup, getConfigPath, deleteApiKey } from "./config.js";
|
|
7
|
+
import { printBanner, printConfig, printResults, printError } from "./ui.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("whisker")
|
|
11
|
+
.description("AI-powered usability testing CLI using Claude computer use")
|
|
12
|
+
.version("0.1.0");
|
|
13
|
+
// Setup command
|
|
14
|
+
program
|
|
15
|
+
.command("setup")
|
|
16
|
+
.description("Configure your Anthropic API key")
|
|
17
|
+
.action(async () => {
|
|
18
|
+
await runSetup();
|
|
19
|
+
});
|
|
20
|
+
// Logout command
|
|
21
|
+
program
|
|
22
|
+
.command("logout")
|
|
23
|
+
.description("Remove stored API key")
|
|
24
|
+
.action(() => {
|
|
25
|
+
const deleted = deleteApiKey();
|
|
26
|
+
if (deleted) {
|
|
27
|
+
console.log("API key removed from", getConfigPath());
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log("No stored API key found.");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
// Run command (main functionality)
|
|
34
|
+
program
|
|
35
|
+
.command("run", { isDefault: true })
|
|
36
|
+
.description("Run a usability test")
|
|
37
|
+
.argument("<task>", "Task description (e.g., 'Add a product to cart')")
|
|
38
|
+
.requiredOption("-u, --url <url>", "URL of the site to test")
|
|
39
|
+
.option("-p, --persona <persona>", "Persona description for the tester")
|
|
40
|
+
.option("-m, --max-steps <number>", "Maximum number of steps", "50")
|
|
41
|
+
.option("-v, --viewport <WxH>", "Viewport size (e.g., 1280x800)", "1280x800")
|
|
42
|
+
.option("-o, --output <dir>", "Output directory", ".whisker")
|
|
43
|
+
.action(async (task, opts) => {
|
|
44
|
+
// Get API key from config or environment
|
|
45
|
+
const apiKey = getApiKey();
|
|
46
|
+
if (!apiKey) {
|
|
47
|
+
printError(`No Anthropic API key configured.\n\nRun setup: npx tsx src/cli.ts setup\nOr set env: export ANTHROPIC_API_KEY=sk-ant-...\n\nConfig: ${getConfigPath()}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
// Set for the Anthropic SDK to pick up
|
|
51
|
+
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
52
|
+
// Parse viewport
|
|
53
|
+
const viewportParts = opts.viewport.split("x");
|
|
54
|
+
if (viewportParts.length !== 2) {
|
|
55
|
+
printError("Invalid viewport format. Use WxH (e.g., 1280x800)");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const width = parseInt(viewportParts[0], 10);
|
|
59
|
+
const height = parseInt(viewportParts[1], 10);
|
|
60
|
+
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
|
|
61
|
+
printError("Invalid viewport dimensions. Use positive integers.");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// Parse max steps
|
|
65
|
+
const maxSteps = parseInt(opts.maxSteps, 10);
|
|
66
|
+
if (isNaN(maxSteps) || maxSteps <= 0) {
|
|
67
|
+
printError("max-steps must be a positive integer");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const config = {
|
|
71
|
+
task,
|
|
72
|
+
url: opts.url,
|
|
73
|
+
persona: opts.persona,
|
|
74
|
+
maxSteps,
|
|
75
|
+
viewport: { width, height },
|
|
76
|
+
outputDir: opts.output,
|
|
77
|
+
};
|
|
78
|
+
// Print banner and config
|
|
79
|
+
printBanner();
|
|
80
|
+
printConfig(config.task, config.url, config.persona, config.maxSteps, `${config.viewport.width}x${config.viewport.height}`);
|
|
81
|
+
try {
|
|
82
|
+
const { report, sessionLog } = await runSession(config);
|
|
83
|
+
const { markdownPath, jsonPath, screenshotDir } = await writeReport(report, sessionLog, config.outputDir);
|
|
84
|
+
// Print results with the new UI
|
|
85
|
+
const absoluteOutputDir = path.resolve(config.outputDir);
|
|
86
|
+
printResults(report.taskCompleted, report.summary, report.findings, config.outputDir, absoluteOutputDir);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
program.parse();
|
|
94
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as readline from "node:readline";
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".config", "whisker");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
export function getApiKey() {
|
|
8
|
+
// Environment variable takes precedence
|
|
9
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
10
|
+
return process.env.ANTHROPIC_API_KEY;
|
|
11
|
+
}
|
|
12
|
+
// Try reading from config file
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
15
|
+
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
16
|
+
const config = JSON.parse(content);
|
|
17
|
+
return config.anthropicApiKey;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Ignore errors reading config
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function saveApiKey(apiKey) {
|
|
26
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
let config = {};
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
31
|
+
config = JSON.parse(content);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Start fresh if config is corrupted
|
|
36
|
+
}
|
|
37
|
+
config.anthropicApiKey = apiKey;
|
|
38
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
39
|
+
fs.chmodSync(CONFIG_FILE, 0o600); // Read/write only for owner
|
|
40
|
+
}
|
|
41
|
+
export async function runSetup() {
|
|
42
|
+
const rl = readline.createInterface({
|
|
43
|
+
input: process.stdin,
|
|
44
|
+
output: process.stdout,
|
|
45
|
+
});
|
|
46
|
+
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log("Whisker Setup");
|
|
49
|
+
console.log("─────────────");
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log("Whisker needs an Anthropic API key to use Claude for testing.");
|
|
52
|
+
console.log("Get one at: https://console.anthropic.com/settings/keys");
|
|
53
|
+
console.log("");
|
|
54
|
+
const existingKey = getApiKey();
|
|
55
|
+
if (existingKey) {
|
|
56
|
+
console.log(`Current key: ${existingKey.slice(0, 12)}...${existingKey.slice(-4)}`);
|
|
57
|
+
const overwrite = await question("Replace existing key? (y/N): ");
|
|
58
|
+
if (overwrite.toLowerCase() !== "y") {
|
|
59
|
+
console.log("Setup cancelled.");
|
|
60
|
+
rl.close();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const apiKey = await question("Enter your Anthropic API key: ");
|
|
65
|
+
rl.close();
|
|
66
|
+
if (!apiKey.trim()) {
|
|
67
|
+
console.log("No key provided. Setup cancelled.");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!apiKey.startsWith("sk-ant-")) {
|
|
71
|
+
console.log("Warning: Key doesn't look like an Anthropic API key (should start with sk-ant-)");
|
|
72
|
+
}
|
|
73
|
+
saveApiKey(apiKey.trim());
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log(`API key saved to ${CONFIG_FILE}`);
|
|
76
|
+
console.log("You're ready to use Whisker!");
|
|
77
|
+
}
|
|
78
|
+
export function getConfigPath() {
|
|
79
|
+
return CONFIG_FILE;
|
|
80
|
+
}
|
|
81
|
+
export function deleteApiKey() {
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
84
|
+
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
85
|
+
const config = JSON.parse(content);
|
|
86
|
+
if (config.anthropicApiKey) {
|
|
87
|
+
delete config.anthropicApiKey;
|
|
88
|
+
// If config is now empty, delete the file
|
|
89
|
+
if (Object.keys(config).length === 0) {
|
|
90
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Ignore errors
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function getNavigationSystemPrompt(config) {
|
|
2
|
+
const personaSection = config.persona
|
|
3
|
+
? `\n\n## Persona\nYou are acting as the following persona: ${config.persona}\nConsider this persona's technical ability, familiarity with similar products, and expectations when evaluating the experience.`
|
|
4
|
+
: "";
|
|
5
|
+
return `You are an expert usability tester evaluating a web application. Your job is to complete a specific task while carefully observing and narrating the user experience.
|
|
6
|
+
|
|
7
|
+
## Your Task
|
|
8
|
+
${config.task}
|
|
9
|
+
|
|
10
|
+
## Starting URL
|
|
11
|
+
${config.url}
|
|
12
|
+
${personaSection}
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. **Think aloud**: Before each action, describe what you see on screen, what you're trying to do, and any observations about the UX. Be specific and detailed about what you observe.
|
|
17
|
+
|
|
18
|
+
2. **Act like a real user**: Navigate the site naturally. Don't use shortcuts a normal user wouldn't know about. If something is confusing, say so explicitly.
|
|
19
|
+
|
|
20
|
+
3. **Watch for issues** as you navigate:
|
|
21
|
+
- **Bugs**: Broken buttons, errors, incorrect behavior, console errors
|
|
22
|
+
- **UX friction**: Confusing labels, unexpected flows, too many steps, missing feedback
|
|
23
|
+
- **Accessibility**: Missing labels, poor contrast, keyboard navigation issues
|
|
24
|
+
- **Performance**: Slow loads, unresponsive elements, layout shifts
|
|
25
|
+
- **Visual issues**: Overlapping elements, misalignment, truncated text, broken layouts
|
|
26
|
+
- **Copy issues**: Confusing or misleading text, typos, jargon
|
|
27
|
+
|
|
28
|
+
4. **Take a screenshot after each action** to verify the result. If something didn't work as expected, note it clearly and try an alternative approach.
|
|
29
|
+
|
|
30
|
+
5. **Complete the task or explain why you cannot**. If you get stuck after 3 attempts at the same thing, describe what blocked you and stop.
|
|
31
|
+
|
|
32
|
+
6. **When finished**, provide a clear final summary of:
|
|
33
|
+
- Whether the task was completed successfully
|
|
34
|
+
- The overall experience quality
|
|
35
|
+
- The most significant issues encountered
|
|
36
|
+
|
|
37
|
+
Start by taking a screenshot to see the current state of the page.`;
|
|
38
|
+
}
|
|
39
|
+
export function getReportSystemPrompt() {
|
|
40
|
+
return `You are an expert usability analyst. You are given the session log and screenshots from an AI-driven usability test of a web application. Your job is to analyze the session and produce a structured JSON report of findings.
|
|
41
|
+
|
|
42
|
+
Carefully examine each screenshot to identify visual issues, layout problems, confusing UI elements, accessibility concerns, and bugs that may not be obvious from the text observations alone.
|
|
43
|
+
|
|
44
|
+
## Output Format
|
|
45
|
+
|
|
46
|
+
Return ONLY a valid JSON object (no markdown fences, no extra text) matching this exact schema:
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
"summary": "2-3 sentence overview of the test session and overall UX quality",
|
|
50
|
+
"taskCompleted": true or false,
|
|
51
|
+
"taskCompletionNotes": "Explanation of whether and how the task was completed, or what blocked it",
|
|
52
|
+
"findings": [
|
|
53
|
+
{
|
|
54
|
+
"id": "F001",
|
|
55
|
+
"severity": "critical | major | minor | suggestion",
|
|
56
|
+
"category": "bug | ux-friction | accessibility | performance | visual | copy",
|
|
57
|
+
"title": "Short descriptive title",
|
|
58
|
+
"description": "Detailed description of the issue, including what was expected vs what happened",
|
|
59
|
+
"stepsToReproduce": ["Step 1", "Step 2"],
|
|
60
|
+
"screenshotStepNumber": 5,
|
|
61
|
+
"suggestedFix": "Actionable suggestion for how to fix this",
|
|
62
|
+
"grepPatterns": ["patterns to search the codebase for relevant code"]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
## Severity Definitions
|
|
68
|
+
- **critical**: Completely blocks the user from completing the task
|
|
69
|
+
- **major**: Significant friction or confusion that most users would struggle with
|
|
70
|
+
- **minor**: Noticeable issue but doesn't significantly impede the task
|
|
71
|
+
- **suggestion**: Improvement idea that would enhance the experience
|
|
72
|
+
|
|
73
|
+
## Guidelines
|
|
74
|
+
- Assign IDs sequentially: F001, F002, etc.
|
|
75
|
+
- Be specific about which step number demonstrated each issue (screenshotStepNumber)
|
|
76
|
+
- For grepPatterns, suggest patterns that would help find relevant code: CSS class names, button text, component names, API endpoints
|
|
77
|
+
- Only report genuine issues backed by evidence from the session, not speculative concerns
|
|
78
|
+
- Consolidate duplicate or overlapping observations into single findings
|
|
79
|
+
- Order findings by severity (critical first, then major, minor, suggestion)`;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,yBAAyB,CAAC,MAAqB;IAC7D,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO;QACnC,CAAC,CAAC,4DAA4D,MAAM,CAAC,OAAO,kIAAkI;QAC9M,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;EAGP,MAAM,CAAC,IAAI;;;EAGX,MAAM,CAAC,GAAG;EACV,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;mEAyBmD,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6EAuCoE,CAAC;AAC9E,CAAC"}
|
package/dist/report.d.ts
ADDED