@demon-utils/playwright 0.1.6 → 0.1.7
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/dist/bin/demon-demo-init.js +56 -0
- package/dist/bin/demon-demo-init.js.map +10 -0
- package/dist/bin/demon-demo-review.js +187 -523
- package/dist/bin/demon-demo-review.js.map +7 -7
- package/dist/bin/demoon.js +1445 -0
- package/dist/bin/demoon.js.map +22 -0
- package/dist/bin/review-template.html +62 -0
- package/dist/github-issue.js +749 -0
- package/dist/github-issue.js.map +16 -0
- package/dist/index.js +1320 -867
- package/dist/index.js.map +16 -8
- package/dist/orchestrator.js +1421 -0
- package/dist/orchestrator.js.map +20 -0
- package/dist/review-generator.js +424 -0
- package/dist/review-generator.js.map +12 -0
- package/dist/review-template.html +62 -0
- package/package.json +11 -2
- package/src/bin/demon-demo-init.ts +59 -0
- package/src/bin/demon-demo-review.ts +19 -97
- package/src/bin/demoon.ts +140 -0
- package/src/feedback-server.ts +138 -0
- package/src/git-context.test.ts +68 -2
- package/src/git-context.ts +48 -9
- package/src/github-issue.test.ts +188 -0
- package/src/github-issue.ts +139 -0
- package/src/html-generator.e2e.test.ts +361 -80
- package/src/index.ts +9 -3
- package/src/orchestrator.test.ts +183 -0
- package/src/orchestrator.ts +341 -0
- package/src/review-generator.ts +221 -0
- package/src/review-types.ts +3 -0
- package/src/review.ts +13 -7
- package/src/html-generator.test.ts +0 -561
- package/src/html-generator.ts +0 -461
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
import { test, expect } from "@playwright/test";
|
|
1
|
+
import { test, expect, type Page } from "@playwright/test";
|
|
2
|
+
import { readFileSync, writeFileSync, mkdtempSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
2
6
|
|
|
3
7
|
import type { CodeReview, ReviewMetadata } from "./review-types.ts";
|
|
4
|
-
|
|
8
|
+
|
|
9
|
+
interface ReviewAppData {
|
|
10
|
+
metadata: ReviewMetadata;
|
|
11
|
+
title: string;
|
|
12
|
+
videos: Record<string, string>;
|
|
13
|
+
logs?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
17
|
+
const currentDir = dirname(currentFile);
|
|
18
|
+
const templatePath = join(currentDir, "..", "dist", "review-template.html");
|
|
19
|
+
const tempDir = mkdtempSync(join(tmpdir(), "review-test-"));
|
|
20
|
+
|
|
21
|
+
function getTemplate(): string {
|
|
22
|
+
return readFileSync(templatePath, "utf-8");
|
|
23
|
+
}
|
|
5
24
|
|
|
6
25
|
function makeReview(overrides?: Partial<CodeReview>): CodeReview {
|
|
7
26
|
return {
|
|
@@ -23,6 +42,7 @@ function makeMetadata(overrides?: Partial<ReviewMetadata>): ReviewMetadata {
|
|
|
23
42
|
demos: [
|
|
24
43
|
{
|
|
25
44
|
file: "login-flow.webm",
|
|
45
|
+
type: "web-ux",
|
|
26
46
|
summary: "Shows the login flow end to end",
|
|
27
47
|
steps: [
|
|
28
48
|
{ timestampSeconds: 0, text: "Page loads" },
|
|
@@ -35,8 +55,37 @@ function makeMetadata(overrides?: Partial<ReviewMetadata>): ReviewMetadata {
|
|
|
35
55
|
};
|
|
36
56
|
}
|
|
37
57
|
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
interface GeneratePageOptions {
|
|
59
|
+
metadataOverrides?: Partial<ReviewMetadata>;
|
|
60
|
+
logs?: Record<string, string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function generatePage(options: GeneratePageOptions | Partial<ReviewMetadata> = {}): string {
|
|
64
|
+
// Support legacy signature (just metadata overrides)
|
|
65
|
+
const isLegacy = !('metadataOverrides' in options) && !('logs' in options);
|
|
66
|
+
const metadataOverrides = isLegacy ? options as Partial<ReviewMetadata> : (options as GeneratePageOptions).metadataOverrides;
|
|
67
|
+
const logs = isLegacy ? undefined : (options as GeneratePageOptions).logs;
|
|
68
|
+
|
|
69
|
+
const appData: ReviewAppData = {
|
|
70
|
+
metadata: makeMetadata(metadataOverrides),
|
|
71
|
+
title: "Demo Review",
|
|
72
|
+
videos: {},
|
|
73
|
+
logs,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const template = getTemplate();
|
|
77
|
+
const html = template.replace('"{{__INJECT_REVIEW_DATA__}}"', JSON.stringify(appData));
|
|
78
|
+
|
|
79
|
+
// Write to temp file and return file URL
|
|
80
|
+
const tempFile = join(tempDir, `test-${Date.now()}.html`);
|
|
81
|
+
writeFileSync(tempFile, html);
|
|
82
|
+
return `file://${tempFile}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Helper to click tab via JavaScript (Vuetify tabs need JS click for proper event handling)
|
|
86
|
+
async function clickTab(page: Page, tabId: string) {
|
|
87
|
+
await page.locator(`[data-tab="${tabId}"]`).evaluate(el => (el as HTMLElement).click());
|
|
88
|
+
await page.waitForTimeout(100);
|
|
40
89
|
}
|
|
41
90
|
|
|
42
91
|
test.describe("feedback tab e2e", () => {
|
|
@@ -44,9 +93,10 @@ test.describe("feedback tab e2e", () => {
|
|
|
44
93
|
test("clicking Feedback tab shows feedback panel and hides others", async ({
|
|
45
94
|
page,
|
|
46
95
|
}) => {
|
|
47
|
-
await page.
|
|
96
|
+
await page.goto(generatePage());
|
|
97
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
48
98
|
|
|
49
|
-
await page
|
|
99
|
+
await clickTab(page, "feedback");
|
|
50
100
|
|
|
51
101
|
await expect(page.locator("#tab-feedback")).toBeVisible();
|
|
52
102
|
await expect(page.locator("#tab-summary")).not.toBeVisible();
|
|
@@ -54,7 +104,8 @@ test.describe("feedback tab e2e", () => {
|
|
|
54
104
|
});
|
|
55
105
|
|
|
56
106
|
test("feedback panel is not visible by default", async ({ page }) => {
|
|
57
|
-
await page.
|
|
107
|
+
await page.goto(generatePage());
|
|
108
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
58
109
|
|
|
59
110
|
await expect(page.locator("#tab-feedback")).not.toBeVisible();
|
|
60
111
|
await expect(page.locator("#tab-summary")).toBeVisible();
|
|
@@ -63,17 +114,18 @@ test.describe("feedback tab e2e", () => {
|
|
|
63
114
|
|
|
64
115
|
test.describe("issue + buttons", () => {
|
|
65
116
|
test("clicking + button adds issue to feedback list", async ({ page }) => {
|
|
66
|
-
await page.
|
|
117
|
+
await page.goto(generatePage());
|
|
118
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
67
119
|
|
|
68
|
-
await page
|
|
69
|
-
await expect(page.locator("
|
|
120
|
+
await clickTab(page, "feedback");
|
|
121
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(0);
|
|
70
122
|
|
|
71
|
-
await page
|
|
72
|
-
await page.click('
|
|
123
|
+
await clickTab(page, "summary");
|
|
124
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
73
125
|
|
|
74
|
-
await page
|
|
75
|
-
await expect(page.locator("
|
|
76
|
-
await expect(page.locator("
|
|
126
|
+
await clickTab(page, "feedback");
|
|
127
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(1);
|
|
128
|
+
await expect(page.locator('[data-testid="feedback-item"]').first()).toContainText(
|
|
77
129
|
"Memory leak in handler",
|
|
78
130
|
);
|
|
79
131
|
});
|
|
@@ -81,35 +133,38 @@ test.describe("feedback tab e2e", () => {
|
|
|
81
133
|
test("clicking multiple + buttons adds multiple items", async ({
|
|
82
134
|
page,
|
|
83
135
|
}) => {
|
|
84
|
-
await page.
|
|
136
|
+
await page.goto(generatePage());
|
|
137
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
85
138
|
|
|
86
|
-
await page.click('
|
|
87
|
-
await page.click('
|
|
88
|
-
await page.click('
|
|
139
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
140
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Missing edge case test"]');
|
|
141
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Rename variable for clarity"]');
|
|
89
142
|
|
|
90
|
-
await page
|
|
91
|
-
await expect(page.locator("
|
|
143
|
+
await clickTab(page, "feedback");
|
|
144
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(3);
|
|
92
145
|
});
|
|
93
146
|
|
|
94
147
|
test("clicking same + button twice does not duplicate", async ({
|
|
95
148
|
page,
|
|
96
149
|
}) => {
|
|
97
|
-
await page.
|
|
150
|
+
await page.goto(generatePage());
|
|
151
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
98
152
|
|
|
99
|
-
await page.click('
|
|
100
|
-
await page.click('
|
|
153
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
154
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
101
155
|
|
|
102
|
-
await page
|
|
103
|
-
await expect(page.locator("
|
|
156
|
+
await clickTab(page, "feedback");
|
|
157
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(1);
|
|
104
158
|
});
|
|
105
159
|
});
|
|
106
160
|
|
|
107
161
|
test.describe("feedback preview", () => {
|
|
108
162
|
test("preview updates when items are added", async ({ page }) => {
|
|
109
|
-
await page.
|
|
163
|
+
await page.goto(generatePage());
|
|
164
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
110
165
|
|
|
111
|
-
await page.click('
|
|
112
|
-
await page
|
|
166
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
167
|
+
await clickTab(page, "feedback");
|
|
113
168
|
|
|
114
169
|
await expect(page.locator("#feedback-preview")).toContainText(
|
|
115
170
|
"1. Address: Memory leak in handler",
|
|
@@ -119,11 +174,12 @@ test.describe("feedback tab e2e", () => {
|
|
|
119
174
|
test("preview shows numbered list for multiple items", async ({
|
|
120
175
|
page,
|
|
121
176
|
}) => {
|
|
122
|
-
await page.
|
|
177
|
+
await page.goto(generatePage());
|
|
178
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
123
179
|
|
|
124
|
-
await page.click('
|
|
125
|
-
await page.click('
|
|
126
|
-
await page
|
|
180
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
181
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Missing edge case test"]');
|
|
182
|
+
await clickTab(page, "feedback");
|
|
127
183
|
|
|
128
184
|
const preview = page.locator("#feedback-preview");
|
|
129
185
|
await expect(preview).toContainText("1. Address: Memory leak in handler");
|
|
@@ -135,10 +191,11 @@ test.describe("feedback tab e2e", () => {
|
|
|
135
191
|
test("preview includes general feedback from textarea", async ({
|
|
136
192
|
page,
|
|
137
193
|
}) => {
|
|
138
|
-
await page.
|
|
194
|
+
await page.goto(generatePage());
|
|
195
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
139
196
|
|
|
140
|
-
await page
|
|
141
|
-
await page.
|
|
197
|
+
await clickTab(page, "feedback");
|
|
198
|
+
await page.locator('[data-testid="feedback-general"] textarea:not([readonly])').fill("Overall good work, minor fixes needed");
|
|
142
199
|
|
|
143
200
|
await expect(page.locator("#feedback-preview")).toContainText(
|
|
144
201
|
"General feedback:",
|
|
@@ -149,11 +206,12 @@ test.describe("feedback tab e2e", () => {
|
|
|
149
206
|
});
|
|
150
207
|
|
|
151
208
|
test("preview combines items and general feedback", async ({ page }) => {
|
|
152
|
-
await page.
|
|
209
|
+
await page.goto(generatePage());
|
|
210
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
153
211
|
|
|
154
|
-
await page.click('
|
|
155
|
-
await page
|
|
156
|
-
await page.
|
|
212
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
213
|
+
await clickTab(page, "feedback");
|
|
214
|
+
await page.locator('[data-testid="feedback-general"] textarea:not([readonly])').fill("Please address ASAP");
|
|
157
215
|
|
|
158
216
|
const preview = page.locator("#feedback-preview");
|
|
159
217
|
await expect(preview).toContainText("1. Address: Memory leak in handler");
|
|
@@ -164,18 +222,19 @@ test.describe("feedback tab e2e", () => {
|
|
|
164
222
|
|
|
165
223
|
test.describe("remove feedback items", () => {
|
|
166
224
|
test("clicking X removes item from list and preview", async ({ page }) => {
|
|
167
|
-
await page.
|
|
225
|
+
await page.goto(generatePage());
|
|
226
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
168
227
|
|
|
169
|
-
await page.click('
|
|
170
|
-
await page.click('
|
|
171
|
-
await page
|
|
228
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
229
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Missing edge case test"]');
|
|
230
|
+
await clickTab(page, "feedback");
|
|
172
231
|
|
|
173
|
-
await expect(page.locator("
|
|
232
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(2);
|
|
174
233
|
|
|
175
|
-
await page.
|
|
234
|
+
await page.locator('[data-testid="feedback-remove"]').first().click();
|
|
176
235
|
|
|
177
|
-
await expect(page.locator("
|
|
178
|
-
await expect(page.locator("
|
|
236
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(1);
|
|
237
|
+
await expect(page.locator('[data-testid="feedback-item"]').first()).toContainText(
|
|
179
238
|
"Missing edge case test",
|
|
180
239
|
);
|
|
181
240
|
await expect(page.locator("#feedback-preview")).not.toContainText(
|
|
@@ -186,32 +245,34 @@ test.describe("feedback tab e2e", () => {
|
|
|
186
245
|
|
|
187
246
|
test.describe("copy button", () => {
|
|
188
247
|
test("copy button changes text to Copied! on click", async ({ page }) => {
|
|
189
|
-
await page.
|
|
248
|
+
await page.goto(generatePage());
|
|
249
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
190
250
|
|
|
191
|
-
await page.click('
|
|
192
|
-
await page
|
|
251
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
252
|
+
await clickTab(page, "feedback");
|
|
193
253
|
|
|
194
254
|
// Grant clipboard permissions
|
|
195
255
|
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
196
256
|
|
|
197
257
|
await page.click("#feedback-copy");
|
|
198
|
-
await expect(page.locator("#feedback-copy")).
|
|
258
|
+
await expect(page.locator("#feedback-copy")).toContainText("Copied!");
|
|
199
259
|
});
|
|
200
260
|
|
|
201
261
|
test("copy button reverts to original text after delay", async ({
|
|
202
262
|
page,
|
|
203
263
|
}) => {
|
|
204
|
-
await page.
|
|
264
|
+
await page.goto(generatePage());
|
|
265
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
205
266
|
|
|
206
|
-
await page.click('
|
|
207
|
-
await page
|
|
267
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
268
|
+
await clickTab(page, "feedback");
|
|
208
269
|
|
|
209
270
|
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
210
271
|
|
|
211
272
|
await page.click("#feedback-copy");
|
|
212
|
-
await expect(page.locator("#feedback-copy")).
|
|
273
|
+
await expect(page.locator("#feedback-copy")).toContainText("Copied!");
|
|
213
274
|
|
|
214
|
-
await expect(page.locator("#feedback-copy")).
|
|
275
|
+
await expect(page.locator("#feedback-copy")).toContainText(
|
|
215
276
|
"Copy to clipboard",
|
|
216
277
|
{ timeout: 3000 },
|
|
217
278
|
);
|
|
@@ -222,7 +283,8 @@ test.describe("feedback tab e2e", () => {
|
|
|
222
283
|
test("floating button appears when selecting text in summary tab", async ({
|
|
223
284
|
page,
|
|
224
285
|
}) => {
|
|
225
|
-
await page.
|
|
286
|
+
await page.goto(generatePage());
|
|
287
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
226
288
|
|
|
227
289
|
await expect(page.locator("#feedback-selection-btn")).not.toBeVisible();
|
|
228
290
|
|
|
@@ -247,7 +309,8 @@ test.describe("feedback tab e2e", () => {
|
|
|
247
309
|
test("clicking floating button adds selected text to feedback", async ({
|
|
248
310
|
page,
|
|
249
311
|
}) => {
|
|
250
|
-
await page.
|
|
312
|
+
await page.goto(generatePage());
|
|
313
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
251
314
|
|
|
252
315
|
// Select the summary text
|
|
253
316
|
const summaryText = page.locator(".review-body p").first();
|
|
@@ -269,15 +332,16 @@ test.describe("feedback tab e2e", () => {
|
|
|
269
332
|
|
|
270
333
|
await expect(page.locator("#feedback-selection-btn")).not.toBeVisible();
|
|
271
334
|
|
|
272
|
-
await page
|
|
273
|
-
await expect(page.locator("
|
|
274
|
-
await expect(page.locator("
|
|
335
|
+
await clickTab(page, "feedback");
|
|
336
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(1);
|
|
337
|
+
await expect(page.locator('[data-testid="feedback-item"]').first()).toContainText(
|
|
275
338
|
"Good changes overall",
|
|
276
339
|
);
|
|
277
340
|
});
|
|
278
341
|
|
|
279
342
|
test("floating button hides when clicking elsewhere", async ({ page }) => {
|
|
280
|
-
await page.
|
|
343
|
+
await page.goto(generatePage());
|
|
344
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
281
345
|
|
|
282
346
|
const summaryText = page.locator(".review-body p").first();
|
|
283
347
|
await summaryText.evaluate((el) => {
|
|
@@ -294,7 +358,7 @@ test.describe("feedback tab e2e", () => {
|
|
|
294
358
|
});
|
|
295
359
|
|
|
296
360
|
// Click elsewhere
|
|
297
|
-
await page.
|
|
361
|
+
await page.locator('[data-testid="review-header"]').click({ force: true });
|
|
298
362
|
|
|
299
363
|
await expect(page.locator("#feedback-selection-btn")).not.toBeVisible();
|
|
300
364
|
});
|
|
@@ -302,30 +366,32 @@ test.describe("feedback tab e2e", () => {
|
|
|
302
366
|
|
|
303
367
|
test.describe("no review", () => {
|
|
304
368
|
test("no feedback elements when review is absent", async ({ page }) => {
|
|
305
|
-
await page.
|
|
369
|
+
await page.goto(generatePage({ review: undefined }));
|
|
370
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
306
371
|
|
|
307
372
|
await expect(page.locator('[data-tab="feedback"]')).toHaveCount(0);
|
|
308
373
|
await expect(page.locator("#tab-feedback")).toHaveCount(0);
|
|
309
374
|
await expect(page.locator("#feedback-selection-btn")).toHaveCount(0);
|
|
310
|
-
await expect(page.locator("
|
|
375
|
+
await expect(page.locator('[data-testid="issue-add-feedback"]')).toHaveCount(0);
|
|
311
376
|
});
|
|
312
377
|
});
|
|
313
378
|
|
|
314
379
|
test.describe("feedback persists across tab switches", () => {
|
|
315
380
|
test("feedback items survive switching tabs", async ({ page }) => {
|
|
316
|
-
await page.
|
|
381
|
+
await page.goto(generatePage());
|
|
382
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
317
383
|
|
|
318
|
-
await page.click('
|
|
384
|
+
await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
|
|
319
385
|
|
|
320
|
-
await page
|
|
321
|
-
await expect(page.locator("
|
|
386
|
+
await clickTab(page, "feedback");
|
|
387
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(1);
|
|
322
388
|
|
|
323
389
|
// Switch to demos and back
|
|
324
|
-
await page
|
|
325
|
-
await page
|
|
390
|
+
await clickTab(page, "demos");
|
|
391
|
+
await clickTab(page, "feedback");
|
|
326
392
|
|
|
327
|
-
await expect(page.locator("
|
|
328
|
-
await expect(page.locator("
|
|
393
|
+
await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(1);
|
|
394
|
+
await expect(page.locator('[data-testid="feedback-item"]').first()).toContainText(
|
|
329
395
|
"Memory leak in handler",
|
|
330
396
|
);
|
|
331
397
|
});
|
|
@@ -333,17 +399,232 @@ test.describe("feedback tab e2e", () => {
|
|
|
333
399
|
test("general feedback text persists across tab switches", async ({
|
|
334
400
|
page,
|
|
335
401
|
}) => {
|
|
336
|
-
await page.
|
|
402
|
+
await page.goto(generatePage());
|
|
403
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
337
404
|
|
|
338
|
-
await page
|
|
339
|
-
await page.
|
|
405
|
+
await clickTab(page, "feedback");
|
|
406
|
+
await page.locator('[data-testid="feedback-general"] textarea:not([readonly])').fill("Some general notes");
|
|
340
407
|
|
|
341
|
-
await page
|
|
342
|
-
await page
|
|
408
|
+
await clickTab(page, "demos");
|
|
409
|
+
await clickTab(page, "feedback");
|
|
343
410
|
|
|
344
|
-
await expect(page.locator("
|
|
411
|
+
await expect(page.locator('[data-testid="feedback-general"] textarea:not([readonly])')).toHaveValue(
|
|
345
412
|
"Some general notes",
|
|
346
413
|
);
|
|
347
414
|
});
|
|
348
415
|
});
|
|
349
416
|
});
|
|
417
|
+
|
|
418
|
+
test.describe("log-based demos e2e", () => {
|
|
419
|
+
const mockLogContent = [
|
|
420
|
+
'{"timestamp":"2024-01-15T10:30:00.123Z","level":"info","message":"Starting migration..."}',
|
|
421
|
+
'{"timestamp":"2024-01-15T10:30:01.001Z","level":"info","message":"Applied migration 001","demon__highlight":true}',
|
|
422
|
+
'{"timestamp":"2024-01-15T10:30:02.001Z","level":"warn","message":"Skipping migration 002"}',
|
|
423
|
+
'{"timestamp":"2024-01-15T10:30:03.001Z","level":"error","message":"Migration failed"}',
|
|
424
|
+
'{"timestamp":"2024-01-15T10:30:04.001Z","level":"info","message":"Complete","demon__highlight":"This is the commentary text"}',
|
|
425
|
+
].join("\n");
|
|
426
|
+
|
|
427
|
+
function generateLogPage() {
|
|
428
|
+
return generatePage({
|
|
429
|
+
metadataOverrides: {
|
|
430
|
+
demos: [
|
|
431
|
+
{
|
|
432
|
+
file: "db-migration.jsonl",
|
|
433
|
+
type: "log-based",
|
|
434
|
+
summary: "Database migration script execution",
|
|
435
|
+
steps: [],
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
logs: {
|
|
440
|
+
"db-migration.jsonl": mockLogContent,
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function generateMixedPage() {
|
|
446
|
+
return generatePage({
|
|
447
|
+
metadataOverrides: {
|
|
448
|
+
demos: [
|
|
449
|
+
{
|
|
450
|
+
file: "login-flow.webm",
|
|
451
|
+
type: "web-ux",
|
|
452
|
+
summary: "Shows the login flow end to end",
|
|
453
|
+
steps: [
|
|
454
|
+
{ timestampSeconds: 0, text: "Page loads" },
|
|
455
|
+
{ timestampSeconds: 5, text: "User types credentials" },
|
|
456
|
+
],
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
file: "db-migration.jsonl",
|
|
460
|
+
type: "log-based",
|
|
461
|
+
summary: "Database migration script execution",
|
|
462
|
+
steps: [],
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
},
|
|
466
|
+
logs: {
|
|
467
|
+
"db-migration.jsonl": mockLogContent,
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
test.describe("log viewer rendering", () => {
|
|
473
|
+
test("displays log viewer for log-based demo", async ({ page }) => {
|
|
474
|
+
await page.goto(generateLogPage());
|
|
475
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
476
|
+
|
|
477
|
+
await clickTab(page, "demos");
|
|
478
|
+
|
|
479
|
+
await expect(page.locator('[data-testid="log-viewer"]')).toBeVisible();
|
|
480
|
+
await expect(page.locator('[data-testid="video-player"]')).not.toBeVisible();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test("displays log lines with line numbers", async ({ page }) => {
|
|
484
|
+
await page.goto(generateLogPage());
|
|
485
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
486
|
+
|
|
487
|
+
await clickTab(page, "demos");
|
|
488
|
+
|
|
489
|
+
const logViewer = page.locator('[data-testid="log-viewer"]');
|
|
490
|
+
await expect(logViewer).toBeVisible();
|
|
491
|
+
|
|
492
|
+
// Check that log lines are present
|
|
493
|
+
const logLines = logViewer.locator(".log-line");
|
|
494
|
+
await expect(logLines).toHaveCount(5);
|
|
495
|
+
|
|
496
|
+
// Check line numbers
|
|
497
|
+
await expect(logLines.nth(0).locator(".line-number")).toContainText("1");
|
|
498
|
+
await expect(logLines.nth(4).locator(".line-number")).toContainText("5");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("displays log messages", async ({ page }) => {
|
|
502
|
+
await page.goto(generateLogPage());
|
|
503
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
504
|
+
|
|
505
|
+
await clickTab(page, "demos");
|
|
506
|
+
|
|
507
|
+
const logViewer = page.locator('[data-testid="log-viewer"]');
|
|
508
|
+
await expect(logViewer).toContainText("Starting migration...");
|
|
509
|
+
await expect(logViewer).toContainText("Applied migration 001");
|
|
510
|
+
await expect(logViewer).toContainText("Migration failed");
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("displays level chips with correct colors", async ({ page }) => {
|
|
514
|
+
await page.goto(generateLogPage());
|
|
515
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
516
|
+
|
|
517
|
+
await clickTab(page, "demos");
|
|
518
|
+
|
|
519
|
+
const logViewer = page.locator('[data-testid="log-viewer"]');
|
|
520
|
+
|
|
521
|
+
// Check for level chips
|
|
522
|
+
await expect(logViewer.locator(".level-chip").filter({ hasText: "INFO" })).toHaveCount(3);
|
|
523
|
+
await expect(logViewer.locator(".level-chip").filter({ hasText: "WARN" })).toHaveCount(1);
|
|
524
|
+
await expect(logViewer.locator(".level-chip").filter({ hasText: "ERROR" })).toHaveCount(1);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("highlights lines with demon__highlight: true", async ({ page }) => {
|
|
528
|
+
await page.goto(generateLogPage());
|
|
529
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
530
|
+
|
|
531
|
+
await clickTab(page, "demos");
|
|
532
|
+
|
|
533
|
+
const logViewer = page.locator('[data-testid="log-viewer"]');
|
|
534
|
+
const highlightedLines = logViewer.locator(".log-line--highlighted");
|
|
535
|
+
|
|
536
|
+
// Two lines have demon__highlight
|
|
537
|
+
await expect(highlightedLines).toHaveCount(2);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("displays inline commentary for string highlights", async ({ page }) => {
|
|
541
|
+
await page.goto(generateLogPage());
|
|
542
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
543
|
+
|
|
544
|
+
await clickTab(page, "demos");
|
|
545
|
+
|
|
546
|
+
const logViewer = page.locator('[data-testid="log-viewer"]');
|
|
547
|
+
const commentary = logViewer.locator(".commentary");
|
|
548
|
+
|
|
549
|
+
await expect(commentary).toHaveCount(1);
|
|
550
|
+
await expect(commentary).toContainText("This is the commentary text");
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test.describe("steps list visibility", () => {
|
|
555
|
+
test("hides steps list for log-based demo", async ({ page }) => {
|
|
556
|
+
await page.goto(generateLogPage());
|
|
557
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
558
|
+
|
|
559
|
+
await clickTab(page, "demos");
|
|
560
|
+
|
|
561
|
+
await expect(page.locator('[data-testid="log-viewer"]')).toBeVisible();
|
|
562
|
+
await expect(page.locator('[data-testid="steps-list"]')).not.toBeVisible();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("shows steps list for web-ux demo", async ({ page }) => {
|
|
566
|
+
await page.goto(generateMixedPage());
|
|
567
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
568
|
+
|
|
569
|
+
await clickTab(page, "demos");
|
|
570
|
+
|
|
571
|
+
// First demo is web-ux
|
|
572
|
+
await expect(page.locator('[data-testid="video-player"]')).toBeVisible();
|
|
573
|
+
await expect(page.locator('[data-testid="steps-list"]')).toBeVisible();
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test.describe("switching between demo types", () => {
|
|
578
|
+
test("switches from web-ux to log-based demo", async ({ page }) => {
|
|
579
|
+
await page.goto(generateMixedPage());
|
|
580
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
581
|
+
|
|
582
|
+
await clickTab(page, "demos");
|
|
583
|
+
|
|
584
|
+
// Initially shows video player (first demo is web-ux)
|
|
585
|
+
await expect(page.locator('[data-testid="video-player"]')).toBeVisible();
|
|
586
|
+
await expect(page.locator('[data-testid="log-viewer"]')).not.toBeVisible();
|
|
587
|
+
|
|
588
|
+
// Click on the second demo (log-based)
|
|
589
|
+
await page.locator('[data-testid="demo-list"] [data-testid="demo-item"]').nth(1).click();
|
|
590
|
+
|
|
591
|
+
// Should now show log viewer
|
|
592
|
+
await expect(page.locator('[data-testid="log-viewer"]')).toBeVisible();
|
|
593
|
+
await expect(page.locator('[data-testid="video-player"]')).not.toBeVisible();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test("switches from log-based to web-ux demo", async ({ page }) => {
|
|
597
|
+
await page.goto(generateMixedPage());
|
|
598
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
599
|
+
|
|
600
|
+
await clickTab(page, "demos");
|
|
601
|
+
|
|
602
|
+
// Click on the second demo (log-based) first
|
|
603
|
+
await page.locator('[data-testid="demo-list"] [data-testid="demo-item"]').nth(1).click();
|
|
604
|
+
await expect(page.locator('[data-testid="log-viewer"]')).toBeVisible();
|
|
605
|
+
|
|
606
|
+
// Click back to first demo (web-ux)
|
|
607
|
+
await page.locator('[data-testid="demo-list"] [data-testid="demo-item"]').nth(0).click();
|
|
608
|
+
|
|
609
|
+
// Should show video player again
|
|
610
|
+
await expect(page.locator('[data-testid="video-player"]')).toBeVisible();
|
|
611
|
+
await expect(page.locator('[data-testid="log-viewer"]')).not.toBeVisible();
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("updates summary text when switching demos", async ({ page }) => {
|
|
615
|
+
await page.goto(generateMixedPage());
|
|
616
|
+
await page.waitForSelector("#app .v-application", { timeout: 5000 });
|
|
617
|
+
|
|
618
|
+
await clickTab(page, "demos");
|
|
619
|
+
|
|
620
|
+
// Check initial summary
|
|
621
|
+
await expect(page.locator("#summary-text")).toContainText("Shows the login flow");
|
|
622
|
+
|
|
623
|
+
// Switch to log-based demo
|
|
624
|
+
await page.locator('[data-testid="demo-list"] [data-testid="demo-item"]').nth(1).click();
|
|
625
|
+
|
|
626
|
+
// Check summary updated
|
|
627
|
+
await expect(page.locator("#summary-text")).toContainText("Database migration");
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -8,8 +8,14 @@ export type { InvokeClaudeOptions, SpawnFn, LlmReviewResponse, BuildReviewPrompt
|
|
|
8
8
|
export { getRepoContext } from "./git-context.ts";
|
|
9
9
|
export type { ExecFn, ReadFileFn, RepoContext, GetRepoContextOptions } from "./git-context.ts";
|
|
10
10
|
|
|
11
|
-
export { generateReviewHtml } from "./html-generator.ts";
|
|
12
|
-
export type { GenerateReviewHtmlOptions } from "./html-generator.ts";
|
|
13
|
-
|
|
14
11
|
export { DemoRecorder } from "./recorder.ts";
|
|
15
12
|
export type { DemoStep } from "./recorder.ts";
|
|
13
|
+
|
|
14
|
+
export { generateReview, discoverDemoFiles, generateReviewHtml, getReviewTemplate } from "./review-generator.ts";
|
|
15
|
+
export type { GenerateReviewOptions, GenerateReviewResult, ReviewAppData, DemoFile } from "./review-generator.ts";
|
|
16
|
+
|
|
17
|
+
export { runReviewOrchestration } from "./orchestrator.ts";
|
|
18
|
+
export type { OrchestratorOptions, OrchestratorResult } from "./orchestrator.ts";
|
|
19
|
+
|
|
20
|
+
export { startFeedbackServer } from "./feedback-server.ts";
|
|
21
|
+
export type { FeedbackPayload, FeedbackServerResult } from "./feedback-server.ts";
|