clipwise 0.1.2 → 0.2.1
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/README.ko.md +51 -9
- package/README.md +49 -9
- package/dist/cli/index.js +110 -19
- package/dist/index.d.ts +413 -0
- package/dist/index.js +109 -18
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -31,12 +31,13 @@ function interpolatePath(from, to, steps) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// src/core/screenshot.ts
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
var DEFAULT_ELEMENT_TIMEOUT = 5e3;
|
|
35
|
+
async function getElementCenter(page, selector, timeout) {
|
|
36
|
+
if (/[\x00-\x1f\x7f;`\\{}]/.test(selector) || selector.length === 0) {
|
|
36
37
|
throw new Error(`Invalid selector: ${selector}`);
|
|
37
38
|
}
|
|
38
39
|
const element = page.locator(selector).first();
|
|
39
|
-
await element.waitFor({ state: "visible", timeout:
|
|
40
|
+
await element.waitFor({ state: "visible", timeout: timeout ?? DEFAULT_ELEMENT_TIMEOUT });
|
|
40
41
|
const box = await element.boundingBox();
|
|
41
42
|
if (!box) {
|
|
42
43
|
throw new Error(
|
|
@@ -77,6 +78,7 @@ var ClipwiseRecorder = class {
|
|
|
77
78
|
targetFps = 30;
|
|
78
79
|
cursorSpeed = "fast";
|
|
79
80
|
firstContentTimestamp = 0;
|
|
81
|
+
pendingResponsePromises = /* @__PURE__ */ new Map();
|
|
80
82
|
/**
|
|
81
83
|
* Launch the browser and create a page with the scenario viewport.
|
|
82
84
|
*/
|
|
@@ -161,8 +163,9 @@ var ClipwiseRecorder = class {
|
|
|
161
163
|
for (let si = 0; si < scenario.steps.length; si++) {
|
|
162
164
|
const step = scenario.steps[si];
|
|
163
165
|
this.currentStepIndex = si;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
this.preRegisterResponseListeners(step.actions);
|
|
167
|
+
for (let ai = 0; ai < step.actions.length; ai++) {
|
|
168
|
+
await this.executeAction(step.actions[ai], ai);
|
|
166
169
|
}
|
|
167
170
|
if (step.captureDelay > 0) {
|
|
168
171
|
await this.waitWithRepaints(step.captureDelay);
|
|
@@ -228,11 +231,33 @@ var ClipwiseRecorder = class {
|
|
|
228
231
|
}
|
|
229
232
|
}
|
|
230
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Pre-register waitForResponse listeners at the start of each step.
|
|
236
|
+
* This ensures the listener is active before any preceding action
|
|
237
|
+
* (e.g. click) triggers the request, preventing race conditions
|
|
238
|
+
* where the response arrives before the listener is set up.
|
|
239
|
+
*/
|
|
240
|
+
preRegisterResponseListeners(actions) {
|
|
241
|
+
this.pendingResponsePromises.clear();
|
|
242
|
+
if (!this.page) return;
|
|
243
|
+
for (let i = 0; i < actions.length; i++) {
|
|
244
|
+
const action = actions[i];
|
|
245
|
+
if (action.action === "waitForResponse") {
|
|
246
|
+
this.pendingResponsePromises.set(
|
|
247
|
+
i,
|
|
248
|
+
this.page.waitForResponse(
|
|
249
|
+
(response) => response.url().includes(action.url) && (action.status === void 0 || response.status() === action.status),
|
|
250
|
+
{ timeout: action.timeout }
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
231
256
|
/**
|
|
232
257
|
* Execute a single action. CDP screencast captures frames continuously
|
|
233
258
|
* in the background while actions are performed.
|
|
234
259
|
*/
|
|
235
|
-
async executeAction(action) {
|
|
260
|
+
async executeAction(action, actionIndex = 0) {
|
|
236
261
|
if (!this.page) {
|
|
237
262
|
throw new Error("Page not initialized. Call init() first.");
|
|
238
263
|
}
|
|
@@ -250,7 +275,7 @@ var ClipwiseRecorder = class {
|
|
|
250
275
|
break;
|
|
251
276
|
}
|
|
252
277
|
case "click": {
|
|
253
|
-
const target = await getElementCenter(this.page, action.selector);
|
|
278
|
+
const target = await getElementCenter(this.page, action.selector, action.timeout);
|
|
254
279
|
await this.moveCursorSmooth(target);
|
|
255
280
|
this.clickTimeline.push({
|
|
256
281
|
position: { ...target },
|
|
@@ -264,7 +289,8 @@ var ClipwiseRecorder = class {
|
|
|
264
289
|
case "type": {
|
|
265
290
|
const inputTarget = await getElementCenter(
|
|
266
291
|
this.page,
|
|
267
|
-
action.selector
|
|
292
|
+
action.selector,
|
|
293
|
+
action.timeout
|
|
268
294
|
);
|
|
269
295
|
await this.moveCursorSmooth(inputTarget);
|
|
270
296
|
this.clickTimeline.push({
|
|
@@ -282,7 +308,7 @@ var ClipwiseRecorder = class {
|
|
|
282
308
|
break;
|
|
283
309
|
}
|
|
284
310
|
case "scroll": {
|
|
285
|
-
const scrollTarget = action.selector ? await getElementCenter(this.page, action.selector) : null;
|
|
311
|
+
const scrollTarget = action.selector ? await getElementCenter(this.page, action.selector, action.timeout) : null;
|
|
286
312
|
await this.page.evaluate(
|
|
287
313
|
({ x, y, smooth, selector }) => {
|
|
288
314
|
const target = selector ? document.querySelector(selector) : window;
|
|
@@ -326,7 +352,8 @@ var ClipwiseRecorder = class {
|
|
|
326
352
|
case "hover": {
|
|
327
353
|
const hoverTarget = await getElementCenter(
|
|
328
354
|
this.page,
|
|
329
|
-
action.selector
|
|
355
|
+
action.selector,
|
|
356
|
+
action.timeout
|
|
330
357
|
);
|
|
331
358
|
await this.moveCursorSmooth(hoverTarget);
|
|
332
359
|
await this.page.hover(action.selector);
|
|
@@ -336,6 +363,33 @@ var ClipwiseRecorder = class {
|
|
|
336
363
|
await this.waitWithRepaints(100);
|
|
337
364
|
break;
|
|
338
365
|
}
|
|
366
|
+
case "waitForSelector": {
|
|
367
|
+
const locator = this.page.locator(action.selector).first();
|
|
368
|
+
await locator.waitFor({ state: action.state, timeout: action.timeout });
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
case "waitForNavigation": {
|
|
372
|
+
await this.page.waitForLoadState(action.waitUntil, { timeout: action.timeout });
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case "waitForURL": {
|
|
376
|
+
await this.page.waitForURL(action.url, { timeout: action.timeout });
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case "waitForFunction": {
|
|
380
|
+
await this.page.waitForFunction(action.expression, void 0, {
|
|
381
|
+
polling: action.polling,
|
|
382
|
+
timeout: action.timeout
|
|
383
|
+
});
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
case "waitForResponse": {
|
|
387
|
+
const pending = this.pendingResponsePromises.get(actionIndex);
|
|
388
|
+
if (pending) {
|
|
389
|
+
await pending;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
339
393
|
}
|
|
340
394
|
await this.waitWithRepaints(ACTION_GAP_MS);
|
|
341
395
|
}
|
|
@@ -1438,9 +1492,9 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
1438
1492
|
|
|
1439
1493
|
// src/script/types.ts
|
|
1440
1494
|
import { z } from "zod";
|
|
1441
|
-
var SafeSelectorSchema = z.string().regex(
|
|
1442
|
-
/^[
|
|
1443
|
-
"Selector contains invalid characters"
|
|
1495
|
+
var SafeSelectorSchema = z.string().min(1, "Selector must not be empty").regex(
|
|
1496
|
+
/^[^\x00-\x1f\x7f;`\\{}]+$/,
|
|
1497
|
+
"Selector contains invalid characters (control chars, semicolons, backticks, or backslashes are not allowed)"
|
|
1444
1498
|
);
|
|
1445
1499
|
var NavigateActionSchema = z.object({
|
|
1446
1500
|
action: z.literal("navigate"),
|
|
@@ -1450,20 +1504,23 @@ var NavigateActionSchema = z.object({
|
|
|
1450
1504
|
var ClickActionSchema = z.object({
|
|
1451
1505
|
action: z.literal("click"),
|
|
1452
1506
|
selector: SafeSelectorSchema,
|
|
1453
|
-
delay: z.number().optional()
|
|
1507
|
+
delay: z.number().optional(),
|
|
1508
|
+
timeout: z.number().min(0).optional()
|
|
1454
1509
|
});
|
|
1455
1510
|
var TypeActionSchema = z.object({
|
|
1456
1511
|
action: z.literal("type"),
|
|
1457
1512
|
selector: SafeSelectorSchema,
|
|
1458
1513
|
text: z.string(),
|
|
1459
|
-
delay: z.number().default(50)
|
|
1514
|
+
delay: z.number().default(50),
|
|
1515
|
+
timeout: z.number().min(0).optional()
|
|
1460
1516
|
});
|
|
1461
1517
|
var ScrollActionSchema = z.object({
|
|
1462
1518
|
action: z.literal("scroll"),
|
|
1463
1519
|
selector: SafeSelectorSchema.optional(),
|
|
1464
1520
|
y: z.number().default(0),
|
|
1465
1521
|
x: z.number().default(0),
|
|
1466
|
-
smooth: z.boolean().default(true)
|
|
1522
|
+
smooth: z.boolean().default(true),
|
|
1523
|
+
timeout: z.number().min(0).optional()
|
|
1467
1524
|
});
|
|
1468
1525
|
var WaitActionSchema = z.object({
|
|
1469
1526
|
action: z.literal("wait"),
|
|
@@ -1471,13 +1528,42 @@ var WaitActionSchema = z.object({
|
|
|
1471
1528
|
});
|
|
1472
1529
|
var HoverActionSchema = z.object({
|
|
1473
1530
|
action: z.literal("hover"),
|
|
1474
|
-
selector: SafeSelectorSchema
|
|
1531
|
+
selector: SafeSelectorSchema,
|
|
1532
|
+
timeout: z.number().min(0).optional()
|
|
1475
1533
|
});
|
|
1476
1534
|
var ScreenshotActionSchema = z.object({
|
|
1477
1535
|
action: z.literal("screenshot"),
|
|
1478
1536
|
name: z.string().optional(),
|
|
1479
1537
|
fullPage: z.boolean().default(false)
|
|
1480
1538
|
});
|
|
1539
|
+
var WaitForSelectorActionSchema = z.object({
|
|
1540
|
+
action: z.literal("waitForSelector"),
|
|
1541
|
+
selector: SafeSelectorSchema,
|
|
1542
|
+
state: z.enum(["visible", "attached", "hidden"]).default("visible"),
|
|
1543
|
+
timeout: z.number().min(0).default(15e3)
|
|
1544
|
+
});
|
|
1545
|
+
var WaitForNavigationActionSchema = z.object({
|
|
1546
|
+
action: z.literal("waitForNavigation"),
|
|
1547
|
+
waitUntil: z.enum(["load", "domcontentloaded", "networkidle"]).default("networkidle"),
|
|
1548
|
+
timeout: z.number().min(0).default(15e3)
|
|
1549
|
+
});
|
|
1550
|
+
var WaitForURLActionSchema = z.object({
|
|
1551
|
+
action: z.literal("waitForURL"),
|
|
1552
|
+
url: z.string().min(1),
|
|
1553
|
+
timeout: z.number().min(0).default(15e3)
|
|
1554
|
+
});
|
|
1555
|
+
var WaitForFunctionActionSchema = z.object({
|
|
1556
|
+
action: z.literal("waitForFunction"),
|
|
1557
|
+
expression: z.string().min(1),
|
|
1558
|
+
polling: z.union([z.literal("raf"), z.number().min(0)]).default("raf"),
|
|
1559
|
+
timeout: z.number().min(0).default(3e4)
|
|
1560
|
+
});
|
|
1561
|
+
var WaitForResponseActionSchema = z.object({
|
|
1562
|
+
action: z.literal("waitForResponse"),
|
|
1563
|
+
url: z.string().min(1),
|
|
1564
|
+
status: z.number().min(100).max(599).optional(),
|
|
1565
|
+
timeout: z.number().min(0).default(3e4)
|
|
1566
|
+
});
|
|
1481
1567
|
var StepActionSchema = z.discriminatedUnion("action", [
|
|
1482
1568
|
NavigateActionSchema,
|
|
1483
1569
|
ClickActionSchema,
|
|
@@ -1485,7 +1571,12 @@ var StepActionSchema = z.discriminatedUnion("action", [
|
|
|
1485
1571
|
ScrollActionSchema,
|
|
1486
1572
|
WaitActionSchema,
|
|
1487
1573
|
HoverActionSchema,
|
|
1488
|
-
ScreenshotActionSchema
|
|
1574
|
+
ScreenshotActionSchema,
|
|
1575
|
+
WaitForSelectorActionSchema,
|
|
1576
|
+
WaitForNavigationActionSchema,
|
|
1577
|
+
WaitForURLActionSchema,
|
|
1578
|
+
WaitForFunctionActionSchema,
|
|
1579
|
+
WaitForResponseActionSchema
|
|
1489
1580
|
]);
|
|
1490
1581
|
var AutoZoomConfigSchema = z.object({
|
|
1491
1582
|
followCursor: z.boolean().default(true),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clipwise",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Scriptable cinematic screen recorder for product demos — YAML in, polished MP4 out",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,20 +50,20 @@
|
|
|
50
50
|
"node": ">=18"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"
|
|
54
|
-
"yaml": "^2.7.0",
|
|
55
|
-
"sharp": "^0.33.0",
|
|
53
|
+
"chalk": "^5.4.0",
|
|
56
54
|
"commander": "^13.0.0",
|
|
55
|
+
"gifenc": "^1.0.3",
|
|
57
56
|
"ora": "^8.0.0",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
57
|
+
"playwright": "^1.50.0",
|
|
58
|
+
"sharp": "^0.33.0",
|
|
59
|
+
"yaml": "^2.7.0",
|
|
60
|
+
"zod": "^3.24.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"typescript": "^5.7.0",
|
|
64
|
-
"tsup": "^8.0.0",
|
|
65
|
-
"vitest": "^3.0.0",
|
|
66
63
|
"@types/node": "^22.0.0",
|
|
67
|
-
"eslint": "^
|
|
64
|
+
"eslint": "^10.0.2",
|
|
65
|
+
"tsup": "^8.0.0",
|
|
66
|
+
"typescript": "^5.7.0",
|
|
67
|
+
"vitest": "^3.0.0"
|
|
68
68
|
}
|
|
69
69
|
}
|