Skip to content

Commit 46651c0

Browse files
committed
test(producer): add parallel capture regression test
Add a regression fixture that forces workers: 2, ensuring the parallel capture code path (browser-per-worker in BeginFrame mode) is exercised in CI. All existing fixtures pin workers: 1, so this is the first test that would catch a regression in the multi-worker pool isolation fix from PR #1087. The composition is 5s @ 30fps (150 frames), which exceeds both MIN_FRAMES_PER_WORKER * 2 (60) and minParallelFrames (120), so the parallel coordinator will always split work across workers. Baseline output/output.mp4 must be generated inside Dockerfile.test before the fixture can run in CI.
1 parent 9a4a005 commit 46651c0

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "parallel-capture-regression",
3+
"description": "Regression test for multi-worker parallel capture. Exercises the browser-per-worker code path that prevents BeginFrame compositor races. Must render with workers > 1 to be meaningful.",
4+
"tags": ["regression", "parallel", "render-compat"],
5+
"minPsnr": 30,
6+
"maxFrameFailures": 0,
7+
"minAudioCorrelation": 0,
8+
"maxAudioLagWindows": 1,
9+
"renderConfig": {
10+
"fps": 30,
11+
"workers": 2
12+
}
13+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
body {
6+
margin: 0;
7+
background: #0d1117;
8+
width: 1920px;
9+
height: 1080px;
10+
font-family: system-ui, -apple-system, sans-serif;
11+
color: #e6edf3;
12+
overflow: hidden;
13+
}
14+
15+
.stage {
16+
position: absolute;
17+
inset: 0;
18+
display: flex;
19+
flex-direction: column;
20+
align-items: center;
21+
justify-content: center;
22+
gap: 48px;
23+
}
24+
25+
.title {
26+
font-size: 48px;
27+
font-weight: 700;
28+
letter-spacing: 0.04em;
29+
}
30+
31+
.track {
32+
width: 960px;
33+
height: 40px;
34+
background: rgba(255, 255, 255, 0.08);
35+
border-radius: 20px;
36+
overflow: hidden;
37+
position: relative;
38+
}
39+
40+
.bar {
41+
position: absolute;
42+
top: 0;
43+
left: 0;
44+
height: 100%;
45+
background: linear-gradient(90deg, #58a6ff, #3fb950);
46+
border-radius: 20px;
47+
animation: fill 5s linear forwards;
48+
}
49+
50+
.counter {
51+
font-size: 160px;
52+
font-weight: 800;
53+
font-variant-numeric: tabular-nums;
54+
letter-spacing: -0.02em;
55+
background: linear-gradient(135deg, #58a6ff, #bc8cff);
56+
-webkit-background-clip: text;
57+
-webkit-text-fill-color: transparent;
58+
animation: count-up 5s steps(5, end) forwards;
59+
}
60+
61+
.pulse-ring {
62+
width: 280px;
63+
height: 280px;
64+
border: 4px solid rgba(88, 166, 255, 0.3);
65+
border-radius: 50%;
66+
animation: pulse 1s ease-in-out infinite;
67+
position: absolute;
68+
top: 50%;
69+
left: 50%;
70+
transform: translate(-50%, -50%);
71+
pointer-events: none;
72+
}
73+
74+
@keyframes fill {
75+
from { width: 0%; }
76+
to { width: 100%; }
77+
}
78+
79+
@keyframes pulse {
80+
0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); }
81+
50% { opacity: 0.8; transform: translate(-50%, -50%) scale(1.15); }
82+
}
83+
84+
@keyframes count-up {
85+
0% { content: "0"; }
86+
20% { content: "1"; }
87+
40% { content: "2"; }
88+
60% { content: "3"; }
89+
80% { content: "4"; }
90+
100% { content: "5"; }
91+
}
92+
93+
.stripe-bg {
94+
position: absolute;
95+
inset: 0;
96+
background: repeating-linear-gradient(
97+
45deg,
98+
transparent,
99+
transparent 80px,
100+
rgba(88, 166, 255, 0.03) 80px,
101+
rgba(88, 166, 255, 0.03) 160px
102+
);
103+
animation: shift-stripes 2s linear infinite;
104+
}
105+
106+
@keyframes shift-stripes {
107+
from { background-position: 0 0; }
108+
to { background-position: 226px 0; }
109+
}
110+
</style>
111+
</head>
112+
<body>
113+
<div id="root"
114+
data-composition-id="parallel-capture-regression"
115+
data-width="1920"
116+
data-height="1080"
117+
data-start="0"
118+
data-duration="5">
119+
<div class="stage clip" data-start="0" data-duration="5">
120+
<div class="stripe-bg"></div>
121+
<div class="pulse-ring"></div>
122+
<div class="title">Parallel Capture</div>
123+
<div class="counter" id="counter">0</div>
124+
<div class="track">
125+
<div class="bar"></div>
126+
</div>
127+
</div>
128+
</div>
129+
<script>
130+
window.__timelines = window.__timelines || {};
131+
132+
/* Frame counter — updates every frame via rAF so each captured frame
133+
shows a unique number, making PSNR comparison meaningful across the
134+
full 150-frame (5s @ 30fps) duration. */
135+
(function () {
136+
var el = document.getElementById("counter");
137+
var duration = 5;
138+
var startTime = null;
139+
140+
function tick(ts) {
141+
if (startTime === null) startTime = ts;
142+
var elapsed = (ts - startTime) / 1000;
143+
var pct = Math.min(elapsed / duration, 1);
144+
el.textContent = Math.floor(pct * 150);
145+
if (pct < 1) requestAnimationFrame(tick);
146+
}
147+
148+
requestAnimationFrame(tick);
149+
})();
150+
</script>
151+
</body>
152+
</html>

0 commit comments

Comments
 (0)