@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.
@@ -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
- import { generateReviewHtml } from "./html-generator.ts";
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
- function generatePage(overrides?: Partial<ReviewMetadata>): string {
39
- return generateReviewHtml({ metadata: makeMetadata(overrides) });
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.setContent(generatePage());
96
+ await page.goto(generatePage());
97
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
48
98
 
49
- await page.click('[data-tab="feedback"]');
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.setContent(generatePage());
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.setContent(generatePage());
117
+ await page.goto(generatePage());
118
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
67
119
 
68
- await page.click('[data-tab="feedback"]');
69
- await expect(page.locator("#feedback-list li")).toHaveCount(0);
120
+ await clickTab(page, "feedback");
121
+ await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(0);
70
122
 
71
- await page.click('[data-tab="summary"]');
72
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
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.click('[data-tab="feedback"]');
75
- await expect(page.locator("#feedback-list li")).toHaveCount(1);
76
- await expect(page.locator("#feedback-list li span").first()).toHaveText(
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.setContent(generatePage());
136
+ await page.goto(generatePage());
137
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
85
138
 
86
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
87
- await page.click('.feedback-add-issue[data-issue="Missing edge case test"]');
88
- await page.click('.feedback-add-issue[data-issue="Rename variable for clarity"]');
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.click('[data-tab="feedback"]');
91
- await expect(page.locator("#feedback-list li")).toHaveCount(3);
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.setContent(generatePage());
150
+ await page.goto(generatePage());
151
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
98
152
 
99
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
100
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
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.click('[data-tab="feedback"]');
103
- await expect(page.locator("#feedback-list li")).toHaveCount(1);
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.setContent(generatePage());
163
+ await page.goto(generatePage());
164
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
110
165
 
111
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
112
- await page.click('[data-tab="feedback"]');
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.setContent(generatePage());
177
+ await page.goto(generatePage());
178
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
123
179
 
124
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
125
- await page.click('.feedback-add-issue[data-issue="Missing edge case test"]');
126
- await page.click('[data-tab="feedback"]');
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.setContent(generatePage());
194
+ await page.goto(generatePage());
195
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
139
196
 
140
- await page.click('[data-tab="feedback"]');
141
- await page.fill("#feedback-general", "Overall good work, minor fixes needed");
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.setContent(generatePage());
209
+ await page.goto(generatePage());
210
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
153
211
 
154
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
155
- await page.click('[data-tab="feedback"]');
156
- await page.fill("#feedback-general", "Please address ASAP");
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.setContent(generatePage());
225
+ await page.goto(generatePage());
226
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
168
227
 
169
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
170
- await page.click('.feedback-add-issue[data-issue="Missing edge case test"]');
171
- await page.click('[data-tab="feedback"]');
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("#feedback-list li")).toHaveCount(2);
232
+ await expect(page.locator('[data-testid="feedback-item"]')).toHaveCount(2);
174
233
 
175
- await page.click("#feedback-list .feedback-remove >> nth=0");
234
+ await page.locator('[data-testid="feedback-remove"]').first().click();
176
235
 
177
- await expect(page.locator("#feedback-list li")).toHaveCount(1);
178
- await expect(page.locator("#feedback-list li span").first()).toHaveText(
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.setContent(generatePage());
248
+ await page.goto(generatePage());
249
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
190
250
 
191
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
192
- await page.click('[data-tab="feedback"]');
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")).toHaveText("Copied!");
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.setContent(generatePage());
264
+ await page.goto(generatePage());
265
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
205
266
 
206
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
207
- await page.click('[data-tab="feedback"]');
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")).toHaveText("Copied!");
273
+ await expect(page.locator("#feedback-copy")).toContainText("Copied!");
213
274
 
214
- await expect(page.locator("#feedback-copy")).toHaveText(
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.setContent(generatePage());
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.setContent(generatePage());
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.click('[data-tab="feedback"]');
273
- await expect(page.locator("#feedback-list li")).toHaveCount(1);
274
- await expect(page.locator("#feedback-list li span").first()).toHaveText(
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.setContent(generatePage());
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.click("header");
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.setContent(generatePage({ review: undefined }));
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(".feedback-add-issue")).toHaveCount(0);
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.setContent(generatePage());
381
+ await page.goto(generatePage());
382
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
317
383
 
318
- await page.click('.feedback-add-issue[data-issue="Memory leak in handler"]');
384
+ await page.click('[data-testid="issue-add-feedback"][data-issue="Memory leak in handler"]');
319
385
 
320
- await page.click('[data-tab="feedback"]');
321
- await expect(page.locator("#feedback-list li")).toHaveCount(1);
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.click('[data-tab="demos"]');
325
- await page.click('[data-tab="feedback"]');
390
+ await clickTab(page, "demos");
391
+ await clickTab(page, "feedback");
326
392
 
327
- await expect(page.locator("#feedback-list li")).toHaveCount(1);
328
- await expect(page.locator("#feedback-list li span").first()).toHaveText(
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.setContent(generatePage());
402
+ await page.goto(generatePage());
403
+ await page.waitForSelector("#app .v-application", { timeout: 5000 });
337
404
 
338
- await page.click('[data-tab="feedback"]');
339
- await page.fill("#feedback-general", "Some general notes");
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.click('[data-tab="demos"]');
342
- await page.click('[data-tab="feedback"]');
408
+ await clickTab(page, "demos");
409
+ await clickTab(page, "feedback");
343
410
 
344
- await expect(page.locator("#feedback-general")).toHaveValue(
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";