demo-composer 0.1.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.md +262 -0
- package/dist/bin/demo-composer.d.ts +2 -0
- package/dist/bin/demo-composer.js +47 -0
- package/dist/bin/demo-composer.js.map +1 -0
- package/dist/composer.d.ts +17 -0
- package/dist/composer.js +511 -0
- package/dist/composer.js.map +1 -0
- package/dist/configuration.d.ts +89 -0
- package/dist/configuration.js +104 -0
- package/dist/configuration.js.map +1 -0
- package/dist/cucumber-config.d.ts +4 -0
- package/dist/cucumber-config.js +17 -0
- package/dist/cucumber-config.js.map +1 -0
- package/dist/effects.d.ts +6 -0
- package/dist/effects.js +248 -0
- package/dist/effects.js.map +1 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +57 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/narration.d.ts +17 -0
- package/dist/narration.js +109 -0
- package/dist/narration.js.map +1 -0
- package/dist/overlay.d.ts +2 -0
- package/dist/overlay.js +16 -0
- package/dist/overlay.js.map +1 -0
- package/dist/runner.d.ts +6 -0
- package/dist/runner.js +144 -0
- package/dist/runner.js.map +1 -0
- package/dist/templates.d.ts +20 -0
- package/dist/templates.js +140 -0
- package/dist/templates.js.map +1 -0
- package/dist/tts.d.ts +25 -0
- package/dist/tts.js +106 -0
- package/dist/tts.js.map +1 -0
- package/dist/world.d.ts +50 -0
- package/dist/world.js +245 -0
- package/dist/world.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { mkdtemp } from 'node:fs/promises';
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
const demoJsonSchema = z.object({
|
|
7
|
+
width: z.number().int().positive().optional(),
|
|
8
|
+
height: z.number().int().positive().optional(),
|
|
9
|
+
outputDir: z.string().optional(),
|
|
10
|
+
featuresGlob: z.string().optional(),
|
|
11
|
+
musicFile: z.string().optional(),
|
|
12
|
+
musicVolume: z.number().min(0).max(1).optional(),
|
|
13
|
+
musicFadeSeconds: z.number().min(0).optional(),
|
|
14
|
+
stepDelayMs: z.number().min(0).optional(),
|
|
15
|
+
stepSettleMs: z.number().min(0).optional(),
|
|
16
|
+
scenarioEndPauseMs: z.number().min(0).optional(),
|
|
17
|
+
ttsModel: z.string().optional(),
|
|
18
|
+
ttsVoice: z.string().optional(),
|
|
19
|
+
ttsStylePreamble: z.string().optional(),
|
|
20
|
+
introLabel: z.string().optional(),
|
|
21
|
+
deviceScaleFactor: z.number().int().min(1).max(3).optional(),
|
|
22
|
+
fps: z.number().int().min(1).max(120).optional(),
|
|
23
|
+
crossfadeDurationSeconds: z.number().min(0).optional(),
|
|
24
|
+
}).strict();
|
|
25
|
+
const CONVENTION_DEFAULTS = {
|
|
26
|
+
width: 1920,
|
|
27
|
+
height: 1080,
|
|
28
|
+
outputDir: resolve(process.cwd(), 'demos'),
|
|
29
|
+
featuresGlob: 'features/**/*.feature',
|
|
30
|
+
musicFile: '',
|
|
31
|
+
musicVolume: 0.15,
|
|
32
|
+
musicFadeSeconds: 2,
|
|
33
|
+
stepDelayMs: 500,
|
|
34
|
+
stepSettleMs: 500,
|
|
35
|
+
scenarioEndPauseMs: 2000,
|
|
36
|
+
ttsModel: 'gemini-2.5-pro-tts',
|
|
37
|
+
ttsVoice: 'Kore',
|
|
38
|
+
ttsStylePreamble: 'Read the following in a calm, steady, professional tone at a moderate pace. ' +
|
|
39
|
+
'Speak as a friendly and composed presenter narrating a product demo video. ' +
|
|
40
|
+
'Keep the energy level consistent and even throughout — never rushed, never overly excited:',
|
|
41
|
+
introLabel: 'Demo',
|
|
42
|
+
deviceScaleFactor: 1,
|
|
43
|
+
fps: 60,
|
|
44
|
+
crossfadeDurationSeconds: 0.5,
|
|
45
|
+
};
|
|
46
|
+
function loadDemoJson(cwd) {
|
|
47
|
+
const configPath = join(cwd, 'demo.json');
|
|
48
|
+
if (!existsSync(configPath))
|
|
49
|
+
return {};
|
|
50
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
51
|
+
return demoJsonSchema.parse(JSON.parse(raw));
|
|
52
|
+
}
|
|
53
|
+
function resolveConventionMusicFile(cwd) {
|
|
54
|
+
const candidate = join(cwd, 'demo-music.mp3');
|
|
55
|
+
return existsSync(candidate) ? candidate : '';
|
|
56
|
+
}
|
|
57
|
+
export function loadConfiguration(cwd) {
|
|
58
|
+
const root = cwd ?? process.cwd();
|
|
59
|
+
const json = loadDemoJson(root);
|
|
60
|
+
const conventionMusic = resolveConventionMusicFile(root);
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
appBaseUrl: process.env.APP_BASE_URL ?? '',
|
|
63
|
+
width: json.width ?? CONVENTION_DEFAULTS.width,
|
|
64
|
+
height: json.height ?? CONVENTION_DEFAULTS.height,
|
|
65
|
+
outputDir: json.outputDir ? resolve(root, json.outputDir) : CONVENTION_DEFAULTS.outputDir,
|
|
66
|
+
featuresGlob: json.featuresGlob ?? CONVENTION_DEFAULTS.featuresGlob,
|
|
67
|
+
musicFile: json.musicFile ? resolve(root, json.musicFile) : conventionMusic,
|
|
68
|
+
musicVolume: json.musicVolume ?? CONVENTION_DEFAULTS.musicVolume,
|
|
69
|
+
musicFadeSeconds: json.musicFadeSeconds ?? CONVENTION_DEFAULTS.musicFadeSeconds,
|
|
70
|
+
stepDelayMs: json.stepDelayMs ?? CONVENTION_DEFAULTS.stepDelayMs,
|
|
71
|
+
stepSettleMs: json.stepSettleMs ?? CONVENTION_DEFAULTS.stepSettleMs,
|
|
72
|
+
scenarioEndPauseMs: json.scenarioEndPauseMs ?? CONVENTION_DEFAULTS.scenarioEndPauseMs,
|
|
73
|
+
ttsModel: json.ttsModel ?? CONVENTION_DEFAULTS.ttsModel,
|
|
74
|
+
ttsVoice: json.ttsVoice ?? CONVENTION_DEFAULTS.ttsVoice,
|
|
75
|
+
ttsStylePreamble: json.ttsStylePreamble ?? CONVENTION_DEFAULTS.ttsStylePreamble,
|
|
76
|
+
introLabel: json.introLabel ?? CONVENTION_DEFAULTS.introLabel,
|
|
77
|
+
deviceScaleFactor: json.deviceScaleFactor ?? CONVENTION_DEFAULTS.deviceScaleFactor,
|
|
78
|
+
fps: json.fps ?? CONVENTION_DEFAULTS.fps,
|
|
79
|
+
crossfadeDurationSeconds: json.crossfadeDurationSeconds ?? CONVENTION_DEFAULTS.crossfadeDurationSeconds,
|
|
80
|
+
googleCloudProject: process.env.GOOGLE_CLOUD_PROJECT ?? '',
|
|
81
|
+
googleCloudRegion: process.env.GOOGLE_CLOUD_REGION ?? 'us-central1',
|
|
82
|
+
narrationTimingFile: process.env.DEMO_NARRATION_TIMING_FILE ?? '',
|
|
83
|
+
manifestDir: process.env.DEMO_MANIFEST_DIR ?? join(tmpdir(), 'demo-composer-manifest'),
|
|
84
|
+
browserHeadless: process.env.BROWSER_HEADLESS !== 'false',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
let _config;
|
|
88
|
+
export function initConfiguration(cwd) {
|
|
89
|
+
_config = loadConfiguration(cwd);
|
|
90
|
+
return _config;
|
|
91
|
+
}
|
|
92
|
+
export function getConfiguration() {
|
|
93
|
+
if (!_config) {
|
|
94
|
+
_config = loadConfiguration();
|
|
95
|
+
}
|
|
96
|
+
return _config;
|
|
97
|
+
}
|
|
98
|
+
export function resetConfiguration() {
|
|
99
|
+
_config = undefined;
|
|
100
|
+
}
|
|
101
|
+
export async function createTempDir(prefix) {
|
|
102
|
+
return mkdtemp(join(tmpdir(), `demo-composer-${prefix}-`));
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=configuration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration.js","sourceRoot":"","sources":["../src/configuration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
function discoverImports() {
|
|
4
|
+
const ownDir = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const hooksPath = resolve(ownDir, 'hooks.js');
|
|
6
|
+
return [hooksPath, 'features/**/*.steps.ts'];
|
|
7
|
+
}
|
|
8
|
+
export function createCucumberConfig() {
|
|
9
|
+
return {
|
|
10
|
+
failFast: true,
|
|
11
|
+
format: ['summary'],
|
|
12
|
+
paths: ['features/**/*.feature'],
|
|
13
|
+
import: discoverImports(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export default createCucumberConfig();
|
|
17
|
+
//# sourceMappingURL=cucumber-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cucumber-config.js","sourceRoot":"","sources":["../src/cucumber-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,SAAS,eAAe;IACpB,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,OAAO,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB;IAChC,OAAO;QACH,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,CAAC,SAAS,CAAC;QACnB,KAAK,EAAE,CAAC,uBAAuB,CAAC;QAChC,MAAM,EAAE,eAAe,EAAE;KAC5B,CAAC;AACN,CAAC;AAED,eAAe,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BrowserContext, Locator, Page } from 'playwright';
|
|
2
|
+
export declare function injectDemoEffects(context: BrowserContext, cssZoom?: number): Promise<void>;
|
|
3
|
+
export declare function highlightElement(page: Page, locator: Locator): Promise<void>;
|
|
4
|
+
export declare function unhighlightElement(page: Page, locator: Locator): Promise<void>;
|
|
5
|
+
export declare function zoomToElement(page: Page, locator: Locator, scale?: number): Promise<void>;
|
|
6
|
+
export declare function resetZoom(page: Page): Promise<void>;
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { getConfiguration } from './configuration.js';
|
|
2
|
+
const CURSOR_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="28" viewBox="0 0 24 28">
|
|
3
|
+
<path d="M2 1 L2 22 L7.5 16.5 L12.5 26 L15.5 24.5 L10.5 15 L18 15 Z"
|
|
4
|
+
fill="white" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
|
|
5
|
+
</svg>`;
|
|
6
|
+
export async function injectDemoEffects(context, cssZoom = 1) {
|
|
7
|
+
await context.addInitScript(({ cursorSvgMarkup, zoom }) => {
|
|
8
|
+
const RIPPLE_COLOR = 'rgba(0, 102, 255, 0.5)';
|
|
9
|
+
const HIGHLIGHT_OUTLINE = '2px solid rgba(0, 102, 255, 0.5)';
|
|
10
|
+
const INTERACTIVE_SELECTOR = 'button, a, input, select, textarea, [role="button"], [role="link"], [role="menuitem"], [tabindex]';
|
|
11
|
+
let cursor;
|
|
12
|
+
let style;
|
|
13
|
+
let highlightedEl = null;
|
|
14
|
+
let prevOutline = '';
|
|
15
|
+
let prevOutlineOffset = '';
|
|
16
|
+
// Cursor lerp state for smooth frame-by-frame interpolation
|
|
17
|
+
let targetX = 0;
|
|
18
|
+
let targetY = 0;
|
|
19
|
+
let currentX = 0;
|
|
20
|
+
let currentY = 0;
|
|
21
|
+
let lerpInitialized = false;
|
|
22
|
+
const LERP_FACTOR = 0.18;
|
|
23
|
+
function getBodyTransform() {
|
|
24
|
+
const t = document.body?.style.transform;
|
|
25
|
+
if (!t)
|
|
26
|
+
return { tx: 0, ty: 0, s: 1 };
|
|
27
|
+
const m = t.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)\s*scale\(([-\d.]+)\)/);
|
|
28
|
+
return m
|
|
29
|
+
? { tx: parseFloat(m[1]), ty: parseFloat(m[2]), s: parseFloat(m[3]) }
|
|
30
|
+
: { tx: 0, ty: 0, s: 1 };
|
|
31
|
+
}
|
|
32
|
+
function ensureElements() {
|
|
33
|
+
document.documentElement.style.zoom = String(zoom);
|
|
34
|
+
if (cursor && document.body.contains(cursor))
|
|
35
|
+
return;
|
|
36
|
+
if (!style || !document.head.contains(style)) {
|
|
37
|
+
style = document.createElement('style');
|
|
38
|
+
style.textContent = `
|
|
39
|
+
@keyframes demo-ripple {
|
|
40
|
+
0% { transform: translate(-50%, -50%) scale(0.3); opacity: 1; }
|
|
41
|
+
100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
|
|
42
|
+
}
|
|
43
|
+
.demo-cursor {
|
|
44
|
+
position: fixed;
|
|
45
|
+
width: 24px;
|
|
46
|
+
height: 28px;
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
z-index: 2147483647;
|
|
49
|
+
transform: scale(1);
|
|
50
|
+
transition: transform 80ms ease;
|
|
51
|
+
will-change: left, top, transform;
|
|
52
|
+
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
|
|
53
|
+
}
|
|
54
|
+
.demo-cursor--pressed {
|
|
55
|
+
transform: scale(0.85);
|
|
56
|
+
}
|
|
57
|
+
.demo-ripple {
|
|
58
|
+
position: fixed;
|
|
59
|
+
width: 80px;
|
|
60
|
+
height: 80px;
|
|
61
|
+
border-radius: 50%;
|
|
62
|
+
background: ${RIPPLE_COLOR};
|
|
63
|
+
border: 2px solid ${RIPPLE_COLOR};
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
z-index: 2147483646;
|
|
66
|
+
animation: demo-ripple 600ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
document.head.appendChild(style);
|
|
70
|
+
}
|
|
71
|
+
const cx = window.innerWidth / 2 / zoom;
|
|
72
|
+
const cy = window.innerHeight / 2 / zoom;
|
|
73
|
+
cursor = document.createElement('div');
|
|
74
|
+
cursor.className = 'demo-cursor';
|
|
75
|
+
cursor.innerHTML = cursorSvgMarkup;
|
|
76
|
+
cursor.style.left = `${cx}px`;
|
|
77
|
+
cursor.style.top = `${cy}px`;
|
|
78
|
+
document.body.appendChild(cursor);
|
|
79
|
+
if (!lerpInitialized) {
|
|
80
|
+
currentX = cx;
|
|
81
|
+
currentY = cy;
|
|
82
|
+
targetX = cx;
|
|
83
|
+
targetY = cy;
|
|
84
|
+
lerpInitialized = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (document.body) {
|
|
88
|
+
ensureElements();
|
|
89
|
+
}
|
|
90
|
+
document.addEventListener('DOMContentLoaded', () => ensureElements());
|
|
91
|
+
// rAF loop: interpolate cursor position each frame for smooth movement
|
|
92
|
+
(function animateCursor() {
|
|
93
|
+
if (document.body && (!cursor || !document.body.contains(cursor))) {
|
|
94
|
+
ensureElements();
|
|
95
|
+
}
|
|
96
|
+
if (cursor && lerpInitialized) {
|
|
97
|
+
const dx = targetX - currentX;
|
|
98
|
+
const dy = targetY - currentY;
|
|
99
|
+
if (Math.abs(dx) > 0.1 || Math.abs(dy) > 0.1) {
|
|
100
|
+
currentX += dx * LERP_FACTOR;
|
|
101
|
+
currentY += dy * LERP_FACTOR;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
currentX = targetX;
|
|
105
|
+
currentY = targetY;
|
|
106
|
+
}
|
|
107
|
+
cursor.style.left = `${currentX}px`;
|
|
108
|
+
cursor.style.top = `${currentY}px`;
|
|
109
|
+
}
|
|
110
|
+
requestAnimationFrame(animateCursor);
|
|
111
|
+
})();
|
|
112
|
+
document.addEventListener('mousemove', (e) => {
|
|
113
|
+
ensureElements();
|
|
114
|
+
const bt = getBodyTransform();
|
|
115
|
+
targetX = (e.clientX / zoom - bt.tx) / bt.s;
|
|
116
|
+
targetY = (e.clientY / zoom - bt.ty) / bt.s;
|
|
117
|
+
}, true);
|
|
118
|
+
document.addEventListener('mousedown', (e) => {
|
|
119
|
+
ensureElements();
|
|
120
|
+
cursor.classList.add('demo-cursor--pressed');
|
|
121
|
+
const ripple = document.createElement('div');
|
|
122
|
+
ripple.className = 'demo-ripple';
|
|
123
|
+
const bt = getBodyTransform();
|
|
124
|
+
ripple.style.left = `${(e.clientX / zoom - bt.tx) / bt.s}px`;
|
|
125
|
+
ripple.style.top = `${(e.clientY / zoom - bt.ty) / bt.s}px`;
|
|
126
|
+
document.body.appendChild(ripple);
|
|
127
|
+
ripple.addEventListener('animationend', () => ripple.remove());
|
|
128
|
+
}, true);
|
|
129
|
+
document.addEventListener('mouseup', () => {
|
|
130
|
+
if (cursor)
|
|
131
|
+
cursor.classList.remove('demo-cursor--pressed');
|
|
132
|
+
}, true);
|
|
133
|
+
document.addEventListener('mouseover', (e) => {
|
|
134
|
+
const target = e.target;
|
|
135
|
+
if (!target)
|
|
136
|
+
return;
|
|
137
|
+
const interactive = target.closest(INTERACTIVE_SELECTOR);
|
|
138
|
+
if (interactive && interactive !== highlightedEl) {
|
|
139
|
+
clearHighlight();
|
|
140
|
+
highlightedEl = interactive;
|
|
141
|
+
const el = interactive;
|
|
142
|
+
prevOutline = el.style.outline;
|
|
143
|
+
prevOutlineOffset = el.style.outlineOffset;
|
|
144
|
+
el.style.outline = HIGHLIGHT_OUTLINE;
|
|
145
|
+
el.style.outlineOffset = '2px';
|
|
146
|
+
}
|
|
147
|
+
}, true);
|
|
148
|
+
document.addEventListener('mouseout', (e) => {
|
|
149
|
+
const target = e.target;
|
|
150
|
+
if (!target)
|
|
151
|
+
return;
|
|
152
|
+
if (highlightedEl && (target === highlightedEl || highlightedEl.contains(target))) {
|
|
153
|
+
const related = e.relatedTarget;
|
|
154
|
+
if (!related || !highlightedEl.contains(related)) {
|
|
155
|
+
clearHighlight();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}, true);
|
|
159
|
+
function clearHighlight() {
|
|
160
|
+
if (highlightedEl) {
|
|
161
|
+
const el = highlightedEl;
|
|
162
|
+
el.style.outline = prevOutline;
|
|
163
|
+
el.style.outlineOffset = prevOutlineOffset;
|
|
164
|
+
highlightedEl = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}, { cursorSvgMarkup: CURSOR_SVG, zoom: cssZoom });
|
|
168
|
+
}
|
|
169
|
+
export async function highlightElement(page, locator) {
|
|
170
|
+
const element = await locator.elementHandle();
|
|
171
|
+
if (!element)
|
|
172
|
+
return;
|
|
173
|
+
await page.evaluate((el) => {
|
|
174
|
+
if (!document.getElementById('demo-highlight-style')) {
|
|
175
|
+
const s = document.createElement('style');
|
|
176
|
+
s.id = 'demo-highlight-style';
|
|
177
|
+
s.textContent = `
|
|
178
|
+
@keyframes demo-highlight-pulse {
|
|
179
|
+
0%, 100% { box-shadow: 0 0 8px 4px rgba(0, 102, 255, 0.6); }
|
|
180
|
+
50% { box-shadow: 0 0 16px 8px rgba(0, 102, 255, 0.3); }
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
document.head.appendChild(s);
|
|
184
|
+
}
|
|
185
|
+
el.style.outline = '3px solid rgba(0, 102, 255, 0.8)';
|
|
186
|
+
el.style.outlineOffset = '3px';
|
|
187
|
+
el.style.animation = 'demo-highlight-pulse 800ms ease-in-out infinite';
|
|
188
|
+
el.dataset.demoHighlighted = '1';
|
|
189
|
+
}, element);
|
|
190
|
+
}
|
|
191
|
+
export async function unhighlightElement(page, locator) {
|
|
192
|
+
const element = await locator.elementHandle();
|
|
193
|
+
if (!element)
|
|
194
|
+
return;
|
|
195
|
+
await page.evaluate((el) => {
|
|
196
|
+
el.style.outline = '';
|
|
197
|
+
el.style.outlineOffset = '';
|
|
198
|
+
el.style.animation = '';
|
|
199
|
+
delete el.dataset.demoHighlighted;
|
|
200
|
+
}, element);
|
|
201
|
+
}
|
|
202
|
+
export async function zoomToElement(page, locator, scale = 2) {
|
|
203
|
+
const box = await locator.boundingBox();
|
|
204
|
+
if (!box)
|
|
205
|
+
return;
|
|
206
|
+
const viewport = page.viewportSize();
|
|
207
|
+
if (!viewport)
|
|
208
|
+
return;
|
|
209
|
+
const dpr = getConfiguration().deviceScaleFactor;
|
|
210
|
+
// Read the current body transform so we can reverse it mathematically
|
|
211
|
+
const cur = await page.evaluate(() => {
|
|
212
|
+
const t = document.body.style.transform;
|
|
213
|
+
if (!t)
|
|
214
|
+
return { tx: 0, ty: 0, s: 1 };
|
|
215
|
+
const m = t.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)\s*scale\(([-\d.]+)\)/);
|
|
216
|
+
return m
|
|
217
|
+
? { tx: parseFloat(m[1]), ty: parseFloat(m[2]), s: parseFloat(m[3]) }
|
|
218
|
+
: { tx: 0, ty: 0, s: 1 };
|
|
219
|
+
});
|
|
220
|
+
// boundingBox is in viewport pixels; convert to CSS coords then undo current transform
|
|
221
|
+
const centerX = ((box.x + box.width / 2) / dpr - cur.tx) / cur.s;
|
|
222
|
+
const centerY = ((box.y + box.height / 2) / dpr - cur.ty) / cur.s;
|
|
223
|
+
const tx = viewport.width / dpr / 2 - centerX * scale;
|
|
224
|
+
const ty = viewport.height / dpr / 2 - centerY * scale;
|
|
225
|
+
await page.evaluate(({ tx, ty, s }) => {
|
|
226
|
+
const body = document.body;
|
|
227
|
+
body.style.transition = 'transform 1200ms ease';
|
|
228
|
+
body.style.transformOrigin = '0 0';
|
|
229
|
+
body.style.transform = `translate(${tx}px, ${ty}px) scale(${s})`;
|
|
230
|
+
}, { tx, ty, s: scale });
|
|
231
|
+
await page.waitForTimeout(1200);
|
|
232
|
+
}
|
|
233
|
+
export async function resetZoom(page) {
|
|
234
|
+
await page.evaluate(() => {
|
|
235
|
+
const body = document.body;
|
|
236
|
+
body.style.transition = 'transform 1200ms ease';
|
|
237
|
+
body.style.transformOrigin = '0 0';
|
|
238
|
+
body.style.transform = 'translate(0px, 0px) scale(1)';
|
|
239
|
+
});
|
|
240
|
+
await page.waitForTimeout(1200);
|
|
241
|
+
await page.evaluate(() => {
|
|
242
|
+
const body = document.body;
|
|
243
|
+
body.style.transition = '';
|
|
244
|
+
body.style.transform = '';
|
|
245
|
+
body.style.transformOrigin = '';
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=effects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.js","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Before, After, BeforeStep, AfterStep, setWorldConstructor, AfterAll, setDefaultTimeout } from '@cucumber/cucumber';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { DemoWorld } from './world.js';
|
|
5
|
+
import { getConfiguration } from './configuration.js';
|
|
6
|
+
DemoWorld.recordingEnabled = true;
|
|
7
|
+
setDefaultTimeout(-1);
|
|
8
|
+
setWorldConstructor(DemoWorld);
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
let narrationTimings;
|
|
13
|
+
async function loadNarrationTimings() {
|
|
14
|
+
if (narrationTimings !== undefined)
|
|
15
|
+
return narrationTimings;
|
|
16
|
+
const config = getConfiguration();
|
|
17
|
+
const timingFile = config.narrationTimingFile;
|
|
18
|
+
if (!timingFile)
|
|
19
|
+
return undefined;
|
|
20
|
+
const content = await readFile(timingFile, 'utf-8');
|
|
21
|
+
narrationTimings = JSON.parse(content);
|
|
22
|
+
return narrationTimings;
|
|
23
|
+
}
|
|
24
|
+
Before(async function (scenario) {
|
|
25
|
+
await this.init();
|
|
26
|
+
this.scenarioName = scenario.pickle.name;
|
|
27
|
+
const featureUri = scenario.gherkinDocument.uri;
|
|
28
|
+
this.featureFilePath = featureUri ? resolve(featureUri) : '';
|
|
29
|
+
this.featureName = scenario.gherkinDocument.feature?.name ?? '';
|
|
30
|
+
});
|
|
31
|
+
BeforeStep(async function ({ pickleStep }) {
|
|
32
|
+
const config = getConfiguration();
|
|
33
|
+
const timings = await loadNarrationTimings();
|
|
34
|
+
const stepIndex = this.stepTimings.length;
|
|
35
|
+
const stepDurations = timings?.[this.featureFilePath]?.[this.scenarioName];
|
|
36
|
+
const narrationDurationMs = stepDurations?.[stepIndex] != null
|
|
37
|
+
? stepDurations[stepIndex] * 1000
|
|
38
|
+
: 0;
|
|
39
|
+
const delay = narrationDurationMs + config.stepDelayMs;
|
|
40
|
+
await sleep(delay);
|
|
41
|
+
this.recordStepStart(pickleStep.text, pickleStep.type ?? '');
|
|
42
|
+
});
|
|
43
|
+
AfterStep(async function () {
|
|
44
|
+
const config = getConfiguration();
|
|
45
|
+
this.recordStepEnd();
|
|
46
|
+
await sleep(config.stepSettleMs);
|
|
47
|
+
});
|
|
48
|
+
After(async function () {
|
|
49
|
+
const config = getConfiguration();
|
|
50
|
+
await sleep(config.scenarioEndPauseMs);
|
|
51
|
+
await this.cleanup();
|
|
52
|
+
});
|
|
53
|
+
AfterAll(async function () {
|
|
54
|
+
await DemoWorld.writeManifest();
|
|
55
|
+
await DemoWorld.closeBrowser();
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { DemoWorld } from './world.js';
|
|
2
|
+
export type { DemoConfiguration, DemoJson } from './configuration.js';
|
|
3
|
+
export { run, type RunOptions } from './runner.js';
|
|
4
|
+
export { createCucumberConfig } from './cucumber-config.js';
|
|
5
|
+
export type { TemplateOverrides } from './templates.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,GAAG,EAAmB,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface StepNarration {
|
|
2
|
+
gherkin: string;
|
|
3
|
+
narration: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ScenarioNarration {
|
|
6
|
+
name: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
steps: StepNarration[];
|
|
9
|
+
}
|
|
10
|
+
export interface FeatureNarration {
|
|
11
|
+
feature: string;
|
|
12
|
+
description: string;
|
|
13
|
+
introNarration: string;
|
|
14
|
+
conclusionNarration: string;
|
|
15
|
+
scenarios: ScenarioNarration[];
|
|
16
|
+
}
|
|
17
|
+
export declare function generateNarration(featureFilePath: string): Promise<FeatureNarration>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { GoogleGenAI, Type } from '@google/genai';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { getConfiguration } from './configuration.js';
|
|
4
|
+
const responseJsonSchema = {
|
|
5
|
+
type: Type.OBJECT,
|
|
6
|
+
properties: {
|
|
7
|
+
feature: { type: Type.STRING },
|
|
8
|
+
description: { type: Type.STRING },
|
|
9
|
+
introNarration: { type: Type.STRING },
|
|
10
|
+
conclusionNarration: { type: Type.STRING },
|
|
11
|
+
scenarios: {
|
|
12
|
+
type: Type.ARRAY,
|
|
13
|
+
items: {
|
|
14
|
+
type: Type.OBJECT,
|
|
15
|
+
properties: {
|
|
16
|
+
name: { type: Type.STRING },
|
|
17
|
+
summary: { type: Type.STRING },
|
|
18
|
+
steps: {
|
|
19
|
+
type: Type.ARRAY,
|
|
20
|
+
items: {
|
|
21
|
+
type: Type.OBJECT,
|
|
22
|
+
properties: {
|
|
23
|
+
gherkin: { type: Type.STRING },
|
|
24
|
+
narration: { type: Type.STRING },
|
|
25
|
+
},
|
|
26
|
+
required: ['gherkin', 'narration'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['name', 'summary', 'steps'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ['feature', 'description', 'introNarration', 'conclusionNarration', 'scenarios'],
|
|
35
|
+
};
|
|
36
|
+
function buildNarrationPrompt(introLabel) {
|
|
37
|
+
return `You are narrating a product demo video for a web application.
|
|
38
|
+
You will be given a Gherkin feature file. Your job is to produce narration text that will be
|
|
39
|
+
read aloud by a text-to-speech engine as voiceover for a demo video showing the app in action.
|
|
40
|
+
The narration is spoken BEFORE each action happens on screen, so the viewer knows what to watch for.
|
|
41
|
+
|
|
42
|
+
IMPORTANT — Tone and style consistency:
|
|
43
|
+
- Maintain a calm, steady, professional, and friendly tone throughout ALL narration text.
|
|
44
|
+
- Every sentence should feel like it comes from the same speaker in the same recording session.
|
|
45
|
+
- Keep all narration sentences between 10 and 20 words.
|
|
46
|
+
- Do NOT use exclamation marks.
|
|
47
|
+
- Do NOT vary the energy level between steps — avoid alternating between enthusiastic and subdued phrasing.
|
|
48
|
+
- Use a measured, even cadence. Avoid dramatic pauses or build-ups in the text.
|
|
49
|
+
|
|
50
|
+
Produce the following:
|
|
51
|
+
|
|
52
|
+
1. An "introNarration" — a welcoming 2-3 sentence overview of the feature, suitable to be read over
|
|
53
|
+
an intro title card at the very beginning of the demo video. Briefly describe what the feature is
|
|
54
|
+
about and preview the scenarios that will be demonstrated. Speak as a presenter opening a demo.
|
|
55
|
+
|
|
56
|
+
2. A "conclusionNarration" — a brief 1-2 sentence wrap-up, suitable to be read over a closing card
|
|
57
|
+
at the end of the demo video. Summarize what was demonstrated and close on a positive note.
|
|
58
|
+
|
|
59
|
+
3. For each scenario, produce:
|
|
60
|
+
a. A "summary" — a friendly 1-2 sentence introduction of what the scenario demonstrates, suitable to be read over a title card.
|
|
61
|
+
b. For each step, a "narration" — a natural, conversational sentence that anticipates what is about to happen.
|
|
62
|
+
- For actions: use anticipatory phrasing like "Now let's...", "Next, we'll...", "Watch as...".
|
|
63
|
+
- For observations/assertions: use phrasing like "We should now see...", "Notice that...".
|
|
64
|
+
- Do NOT use Gherkin keywords (Given/When/Then/And).
|
|
65
|
+
- Speak as if you are a presenter walking someone through the app.
|
|
66
|
+
- Be concise but descriptive.
|
|
67
|
+
- Use present tense.
|
|
68
|
+
|
|
69
|
+
Here is an example of the desired narration style for a scenario:
|
|
70
|
+
|
|
71
|
+
Scenario summary: "In this scenario, we will see how a new user creates an account and sets up their profile."
|
|
72
|
+
Step narrations:
|
|
73
|
+
- "Let's start by navigating to the registration page."
|
|
74
|
+
- "Now we'll fill in the required fields with our account details."
|
|
75
|
+
- "Next, we'll click the submit button to create the account."
|
|
76
|
+
- "We should now see a confirmation message indicating the account was created."
|
|
77
|
+
- "Notice that the user is automatically redirected to the profile setup page."
|
|
78
|
+
|
|
79
|
+
Notice how each sentence above uses a similar length, calm phrasing, and consistent energy.
|
|
80
|
+
Follow this same pattern for all narration you produce.
|
|
81
|
+
|
|
82
|
+
Return the result as structured JSON matching the provided schema.`;
|
|
83
|
+
}
|
|
84
|
+
export async function generateNarration(featureFilePath) {
|
|
85
|
+
const config = getConfiguration();
|
|
86
|
+
const featureContent = await readFile(featureFilePath, 'utf-8');
|
|
87
|
+
const ai = new GoogleGenAI({
|
|
88
|
+
vertexai: true,
|
|
89
|
+
project: config.googleCloudProject,
|
|
90
|
+
location: config.googleCloudRegion,
|
|
91
|
+
});
|
|
92
|
+
const response = await ai.models.generateContent({
|
|
93
|
+
model: 'gemini-2.5-flash-lite',
|
|
94
|
+
contents: [
|
|
95
|
+
buildNarrationPrompt(config.introLabel),
|
|
96
|
+
`Here is the feature file:\n\n${featureContent}`,
|
|
97
|
+
],
|
|
98
|
+
config: {
|
|
99
|
+
responseMimeType: 'application/json',
|
|
100
|
+
responseSchema: responseJsonSchema,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const text = response.text;
|
|
104
|
+
if (!text) {
|
|
105
|
+
throw new Error('Gemini returned no text for narration generation');
|
|
106
|
+
}
|
|
107
|
+
return JSON.parse(text);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=narration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration.js","sourceRoot":"","sources":["../src/narration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAqBtD,MAAM,kBAAkB,GAAG;IACvB,IAAI,EAAE,IAAI,CAAC,MAAM;IACjB,UAAU,EAAE;QACR,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;QAC9B,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;QAClC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;QACrC,mBAAmB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;QAC1C,SAAS,EAAE;YACP,IAAI,EAAE,IAAI,CAAC,KAAK;YAChB,KAAK,EAAE;gBACH,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,UAAU,EAAE;oBACR,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;oBAC3B,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;oBAC9B,KAAK,EAAE;wBACH,IAAI,EAAE,IAAI,CAAC,KAAK;wBAChB,KAAK,EAAE;4BACH,IAAI,EAAE,IAAI,CAAC,MAAM;4BACjB,UAAU,EAAE;gCACR,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;gCAC9B,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;6BACnC;4BACD,QAAQ,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC;yBACrC;qBACJ;iBACJ;gBACD,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;aACzC;SACJ;KACJ;IACD,QAAQ,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,WAAW,CAAC;CAC7F,CAAC;AAEF,SAAS,oBAAoB,CAAC,UAAkB;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEA6CwD,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,eAAuB;IAC3D,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAEhE,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC;QACvB,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,MAAM,CAAC,kBAAkB;QAClC,QAAQ,EAAE,MAAM,CAAC,iBAAiB;KACrC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC;QAC7C,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE;YACN,oBAAoB,CAAC,MAAM,CAAC,UAAU,CAAC;YACvC,gCAAgC,cAAc,EAAE;SACnD;QACD,MAAM,EAAE;YACJ,gBAAgB,EAAE,kBAAkB;YACpC,cAAc,EAAE,kBAAkB;SACrC;KACJ,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAqB,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { overlayHtml as defaultOverlayHtml } from './templates.js';
|
|
2
|
+
export declare function renderOverlayImages(featureName: string, scenarios: string[], width: number, height: number, renderFn: (html: string, outputPath: string, width: number, height: number) => Promise<void>, overlayHtmlFn?: typeof defaultOverlayHtml): Promise<string[]>;
|