@canivel/ralph 0.2.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/.agents/ralph/PROMPT_build.md +126 -0
- package/.agents/ralph/agents.sh +15 -0
- package/.agents/ralph/config.sh +25 -0
- package/.agents/ralph/log-activity.sh +15 -0
- package/.agents/ralph/loop.sh +1001 -0
- package/.agents/ralph/references/CONTEXT_ENGINEERING.md +126 -0
- package/.agents/ralph/references/GUARDRAILS.md +174 -0
- package/AGENTS.md +20 -0
- package/README.md +266 -0
- package/bin/ralph +766 -0
- package/diagram.svg +55 -0
- package/examples/commands.md +46 -0
- package/package.json +39 -0
- package/ralph.webp +0 -0
- package/skills/commit/SKILL.md +219 -0
- package/skills/commit/references/commit_examples.md +292 -0
- package/skills/dev-browser/SKILL.md +211 -0
- package/skills/dev-browser/bun.lock +443 -0
- package/skills/dev-browser/package-lock.json +2988 -0
- package/skills/dev-browser/package.json +31 -0
- package/skills/dev-browser/references/scraping.md +155 -0
- package/skills/dev-browser/scripts/start-relay.ts +32 -0
- package/skills/dev-browser/scripts/start-server.ts +117 -0
- package/skills/dev-browser/server.sh +24 -0
- package/skills/dev-browser/src/client.ts +474 -0
- package/skills/dev-browser/src/index.ts +287 -0
- package/skills/dev-browser/src/relay.ts +731 -0
- package/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
- package/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
- package/skills/dev-browser/src/snapshot/index.ts +14 -0
- package/skills/dev-browser/src/snapshot/inject.ts +13 -0
- package/skills/dev-browser/src/types.ts +34 -0
- package/skills/dev-browser/tsconfig.json +36 -0
- package/skills/dev-browser/vitest.config.ts +12 -0
- package/skills/prd/SKILL.md +235 -0
- package/tests/agent-loops.mjs +79 -0
- package/tests/agent-ping.mjs +39 -0
- package/tests/audit.md +56 -0
- package/tests/cli-smoke.mjs +47 -0
- package/tests/real-agents.mjs +127 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import type { Browser, BrowserContext, Page } from "playwright";
|
|
3
|
+
import { beforeAll, afterAll, beforeEach, afterEach, describe, test, expect } from "vitest";
|
|
4
|
+
import { getSnapshotScript, clearSnapshotScriptCache } from "../browser-script";
|
|
5
|
+
|
|
6
|
+
let browser: Browser;
|
|
7
|
+
let context: BrowserContext;
|
|
8
|
+
let page: Page;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
browser = await chromium.launch();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await browser.close();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
context = await browser.newContext();
|
|
20
|
+
page = await context.newPage();
|
|
21
|
+
clearSnapshotScriptCache(); // Start fresh for each test
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
await context.close();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
async function setContent(html: string): Promise<void> {
|
|
29
|
+
await page.setContent(html, { waitUntil: "domcontentloaded" });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getSnapshot(): Promise<string> {
|
|
33
|
+
const script = getSnapshotScript();
|
|
34
|
+
return await page.evaluate((s: string) => {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
const w = globalThis as any;
|
|
37
|
+
if (!w.__devBrowser_getAISnapshot) {
|
|
38
|
+
// eslint-disable-next-line no-eval
|
|
39
|
+
eval(s);
|
|
40
|
+
}
|
|
41
|
+
return w.__devBrowser_getAISnapshot();
|
|
42
|
+
}, script);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function selectRef(ref: string): Promise<unknown> {
|
|
46
|
+
return await page.evaluate((refId: string) => {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
const w = globalThis as any;
|
|
49
|
+
const element = w.__devBrowser_selectSnapshotRef(refId);
|
|
50
|
+
return {
|
|
51
|
+
tagName: element.tagName,
|
|
52
|
+
textContent: element.textContent?.trim(),
|
|
53
|
+
};
|
|
54
|
+
}, ref);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("ARIA Snapshot", () => {
|
|
58
|
+
test("generates snapshot for simple page", async () => {
|
|
59
|
+
await setContent(`
|
|
60
|
+
<html>
|
|
61
|
+
<body>
|
|
62
|
+
<h1>Hello World</h1>
|
|
63
|
+
<button>Click me</button>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
`);
|
|
67
|
+
|
|
68
|
+
const snapshot = await getSnapshot();
|
|
69
|
+
|
|
70
|
+
expect(snapshot).toContain("heading");
|
|
71
|
+
expect(snapshot).toContain("Hello World");
|
|
72
|
+
expect(snapshot).toContain("button");
|
|
73
|
+
expect(snapshot).toContain("Click me");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("assigns refs to interactive elements", async () => {
|
|
77
|
+
await setContent(`
|
|
78
|
+
<html>
|
|
79
|
+
<body>
|
|
80
|
+
<button id="btn1">Button 1</button>
|
|
81
|
+
<button id="btn2">Button 2</button>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
|
84
|
+
`);
|
|
85
|
+
|
|
86
|
+
const snapshot = await getSnapshot();
|
|
87
|
+
|
|
88
|
+
// Should have refs
|
|
89
|
+
expect(snapshot).toMatch(/\[ref=e\d+\]/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("refs persist on window.__devBrowserRefs", async () => {
|
|
93
|
+
await setContent(`
|
|
94
|
+
<html>
|
|
95
|
+
<body>
|
|
96
|
+
<button>Test Button</button>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
99
|
+
`);
|
|
100
|
+
|
|
101
|
+
await getSnapshot();
|
|
102
|
+
|
|
103
|
+
// Check that refs are stored
|
|
104
|
+
const hasRefs = await page.evaluate(() => {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
const w = globalThis as any;
|
|
107
|
+
return typeof w.__devBrowserRefs === "object" && Object.keys(w.__devBrowserRefs).length > 0;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(hasRefs).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("selectSnapshotRef returns element for valid ref", async () => {
|
|
114
|
+
await setContent(`
|
|
115
|
+
<html>
|
|
116
|
+
<body>
|
|
117
|
+
<button>My Button</button>
|
|
118
|
+
</body>
|
|
119
|
+
</html>
|
|
120
|
+
`);
|
|
121
|
+
|
|
122
|
+
const snapshot = await getSnapshot();
|
|
123
|
+
|
|
124
|
+
// Extract a ref from the snapshot
|
|
125
|
+
const refMatch = snapshot.match(/\[ref=(e\d+)\]/);
|
|
126
|
+
expect(refMatch).toBeTruthy();
|
|
127
|
+
expect(refMatch![1]).toBeDefined();
|
|
128
|
+
const ref = refMatch![1] as string;
|
|
129
|
+
|
|
130
|
+
// Select the element by ref
|
|
131
|
+
const result = (await selectRef(ref)) as { tagName: string; textContent: string };
|
|
132
|
+
expect(result.tagName).toBe("BUTTON");
|
|
133
|
+
expect(result.textContent).toBe("My Button");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("includes links with URLs", async () => {
|
|
137
|
+
await setContent(`
|
|
138
|
+
<html>
|
|
139
|
+
<body>
|
|
140
|
+
<a href="https://example.com">Example Link</a>
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
143
|
+
`);
|
|
144
|
+
|
|
145
|
+
const snapshot = await getSnapshot();
|
|
146
|
+
|
|
147
|
+
expect(snapshot).toContain("link");
|
|
148
|
+
expect(snapshot).toContain("Example Link");
|
|
149
|
+
// URL should be included as a prop
|
|
150
|
+
expect(snapshot).toContain("/url:");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("includes form elements", async () => {
|
|
154
|
+
await setContent(`
|
|
155
|
+
<html>
|
|
156
|
+
<body>
|
|
157
|
+
<input type="text" placeholder="Enter name" />
|
|
158
|
+
<input type="checkbox" />
|
|
159
|
+
<select>
|
|
160
|
+
<option>Option 1</option>
|
|
161
|
+
<option>Option 2</option>
|
|
162
|
+
</select>
|
|
163
|
+
</body>
|
|
164
|
+
</html>
|
|
165
|
+
`);
|
|
166
|
+
|
|
167
|
+
const snapshot = await getSnapshot();
|
|
168
|
+
|
|
169
|
+
expect(snapshot).toContain("textbox");
|
|
170
|
+
expect(snapshot).toContain("checkbox");
|
|
171
|
+
expect(snapshot).toContain("combobox");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("renders nested structure correctly", async () => {
|
|
175
|
+
await setContent(`
|
|
176
|
+
<html>
|
|
177
|
+
<body>
|
|
178
|
+
<nav>
|
|
179
|
+
<ul>
|
|
180
|
+
<li><a href="/home">Home</a></li>
|
|
181
|
+
<li><a href="/about">About</a></li>
|
|
182
|
+
</ul>
|
|
183
|
+
</nav>
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|
|
186
|
+
`);
|
|
187
|
+
|
|
188
|
+
const snapshot = await getSnapshot();
|
|
189
|
+
|
|
190
|
+
expect(snapshot).toContain("navigation");
|
|
191
|
+
expect(snapshot).toContain("list");
|
|
192
|
+
expect(snapshot).toContain("listitem");
|
|
193
|
+
expect(snapshot).toContain("link");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("handles disabled elements", async () => {
|
|
197
|
+
await setContent(`
|
|
198
|
+
<html>
|
|
199
|
+
<body>
|
|
200
|
+
<button disabled>Disabled Button</button>
|
|
201
|
+
</body>
|
|
202
|
+
</html>
|
|
203
|
+
`);
|
|
204
|
+
|
|
205
|
+
const snapshot = await getSnapshot();
|
|
206
|
+
|
|
207
|
+
expect(snapshot).toContain("[disabled]");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("handles checked checkboxes", async () => {
|
|
211
|
+
await setContent(`
|
|
212
|
+
<html>
|
|
213
|
+
<body>
|
|
214
|
+
<input type="checkbox" checked />
|
|
215
|
+
</body>
|
|
216
|
+
</html>
|
|
217
|
+
`);
|
|
218
|
+
|
|
219
|
+
const snapshot = await getSnapshot();
|
|
220
|
+
|
|
221
|
+
expect(snapshot).toContain("[checked]");
|
|
222
|
+
});
|
|
223
|
+
});
|