← 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);
}