Manifest
File, title, description, audience, visibility, and optional wall-clock target are explicit inputs.
Interactive demo · zero uploads
Replay validation, audience, visibility, scheduling, verification, and resume state against synthetic fixtures. YouTube Studio is never opened.
60-second deterministic replay
The mock adapter preserves the live manifest contract and produces evidence for every item without loading a browser client.
Problem
Studio changes the number of wizard steps by channel, disables controls during processing, and accepts wall-clock dates in the channel timezone. A premature click can leave a private draft; an unchecked field can schedule the wrong moment.
The scheduler models the batch independently from Studio, makes audience and visibility explicit, reads date and time back before finalization, and persists completed keys for safe resume.
Architecture
Only the injected live adapter knows selectors or CDP. Validation, sequencing, failure behavior, and reporting stay testable without YouTube.
File, title, description, audience, visibility, and optional wall-clock target are explicit inputs.
Stable keys drive order, deduplication, pacing, stop-on-error behavior, and event evidence.
Owns CDP file injection, variable wizard steps, enabled-state waits, and native controls.
Requested date and time are compared with Studio readback before the final button is allowed.
Workflow
The adapter moves forward only when the previous platform state is observable.
aria-disabled is polled before Next or Done. This prevents a silent private draft while processing is incomplete.
The manifest carries a wall-clock value interpreted in the configured channel timezone; the adapter does not silently convert it.
A readback mismatch throws before Done, directly preventing publication at an unintended time.
Real, linked code
These excerpts are taken from the public implementation.
while (Date.now() - start < timeoutMs) {
if (await el.count()) {
const aria = await el.getAttribute('aria-disabled');
if (aria !== 'true') {
await el.click();
return;
}
}
await sleep(1000);
}The automation waits for Studio's real control state instead of assuming that a visible button is actionable.
const rb = await page.evaluate(() => ({
date: document.querySelector('#datepicker-trigger')
?.innerText.trim(),
time: document.querySelector(
'tp-yt-paper-input#textbox input'
)?.value
}));
if (rb.date !== dateLabel || rb.time !== timeLabel) {
throw new Error('schedule mismatch');
}Finalization is impossible when the platform does not echo the exact requested wall-clock target.
if (doneSet.has(key)) {
results.push({ key, status: 'skipped', steps: [] });
onEvent({ type: 'skipped', key, done: [...doneSet] });
continue;
}A completed item never reaches the upload adapter on a resumed run.
Execution boundary
Demo mode is a separate import graph, backed by a local mock and static evidence.
npm run demonpm run batchQuality and limitations
Run locally
No login or API key is required for demo mode.
npm install
npm run demo
npm test
npm run test:site
# equivalent explicit CLI
node src/demo.mjs \
--fixture manifest.example.json \
--output site