← back to Marketing Command Center
demo/record-tiktok-demo-safe.mjs
79 lines
// SAFE TikTok integration-flow demo — drives the REAL Quick Post wizard through
// Picture → Words → Where(channels), screenshotting each true state. It STOPS at the
// channel-selection step (never selects a channel, never reaches the Check/publish
// step), so NOTHING can be sent to any connected account (FB/IG/TikTok). Frames →
// demo/out/frame-*.png; stitched to mp4 by the system ffmpeg. TK-10519.
import { chromium } from 'playwright';
import fs from 'node:fs';
import path from 'node:path';
const ROOT = 'https://marketing.designerwallcoverings.com';
const envTxt = fs.readFileSync(new URL('../.env', import.meta.url), 'utf8');
const g = (k) => (envTxt.match(new RegExp('^' + k + '=(.*)$', 'm'))?.[1] || '').replace(/^['"]|['"]$/g, '');
const USER = g('MCC_USER') || 'admin', PASS = g('MCC_PASS');
const OUT = path.join(path.dirname(new URL(import.meta.url).pathname), 'out');
fs.rmSync(OUT, { recursive: true, force: true }); fs.mkdirSync(OUT, { recursive: true });
const pause = (ms) => new Promise(r => setTimeout(r, ms));
const browser = await chromium.launch({ headless: true, channel: 'chrome' });
const ctx = await browser.newContext({
httpCredentials: { username: USER, password: PASS },
viewport: { width: 1366, height: 900 },
});
const page = await ctx.newPage();
let n = 0;
async function banner(text) {
await page.evaluate((t) => {
let b = document.getElementById('__demo_banner__');
if (!b) {
b = document.createElement('div'); b.id = '__demo_banner__';
b.style.cssText = 'position:fixed;left:0;right:0;bottom:0;z-index:2147483647;background:rgba(17,17,17,.93);color:#fff;font:600 22px/1.45 -apple-system,Segoe UI,sans-serif;padding:18px 28px;text-align:center;letter-spacing:.2px';
document.body.appendChild(b);
}
b.textContent = t;
}, text).catch(() => {});
}
async function shot(label, holdFrames = 4) {
if (label) await banner(label);
await pause(700);
for (let k = 0; k < holdFrames; k++) {
await page.screenshot({ path: path.join(OUT, `frame-${String(n++).padStart(3, '0')}.png`) }).catch(() => {});
}
}
const next = async () => { await page.locator('#kp-next').click({ timeout: 6000 }).catch(() => {}); await pause(1400); };
try {
await page.goto(ROOT, { waitUntil: 'domcontentloaded', timeout: 45000 }); await pause(3800);
await shot('Designer Wallcoverings — Marketing Command Center · posting to TikTok', 4);
// STEP 1 — Picture: choose one of OUR OWN DW pictures/videos (the content the app posts).
await page.locator('.kp-asset').first().click({ timeout: 8000 }).catch(() => {});
await shot('Step 1 · Picture — the creator picks one of their own DW pictures/videos', 4);
await next();
// STEP 2 — Music (optional). Just show it and continue.
await shot('Step 2 · Music — optional brand-safe track', 3);
await next();
// STEP 3 — Words: the caption sent with the media via TikTok video.upload.
await page.evaluate(() => {
const c = document.querySelector('#kp-caption');
if (c) { c.value = 'New from Designer Wallcoverings — statement wallcoverings for the modern interior. #designerwallcoverings';
c.dispatchEvent(new Event('input', { bubbles: true })); }
});
await shot('Step 3 · Words — a caption is written (sent with the video via video.upload)', 4);
await next();
// STEP 4 — Where: the channel picker. TikTok appears here as a publish target.
await pause(1600); // channels fetch
await page.locator('#kp-channels .kp-channel[data-channel="tiktok"]').first().scrollIntoViewIfNeeded().catch(() => {});
await shot('Step 4 · Where — TikTok is offered as a publish target (Login Kit + Content Posting API)', 5);
await shot('This app requests user.info.basic + video.upload only — no Direct Post. Posts are SELF_ONLY in sandbox. [demo ends before publishing]', 5);
} catch (e) {
console.error('recorder error:', e.message);
} finally {
await ctx.close(); await browser.close();
console.log('FRAMES:', fs.readdirSync(OUT).filter(f => f.startsWith('frame-')).length, 'in', OUT);
}