115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
|
|
import { mkdir, writeFile } from "fs/promises";
|
||
|
|
import path from "path";
|
||
|
|
import type { Page } from "playwright";
|
||
|
|
import { getActiveAccountConnectionSettings } from "@/lib/account-connection-settings";
|
||
|
|
|
||
|
|
const DEBUG_DIR = path.join(process.cwd(), "data", "debug-runs");
|
||
|
|
|
||
|
|
export interface DebugRun {
|
||
|
|
id: string;
|
||
|
|
label: string;
|
||
|
|
dir: string;
|
||
|
|
enabled: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function isPlaywrightDebugEnabled(): Promise<boolean> {
|
||
|
|
if (process.env.THREADS_DEBUG === "true") return true;
|
||
|
|
try {
|
||
|
|
const connection = await getActiveAccountConnectionSettings();
|
||
|
|
return connection.playwrightDebug === true;
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function shouldRunHeaded(): Promise<boolean> {
|
||
|
|
if (process.env.PLAYWRIGHT_HEADLESS === "false") return true;
|
||
|
|
if (process.env.PLAYWRIGHT_HEADLESS === "true") return false;
|
||
|
|
return isPlaywrightDebugEnabled();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 發布預設用可見瀏覽器,較不容易被擋、也方便手動完成 Instagram 授權 */
|
||
|
|
export async function shouldRunHeadedForPublish(): Promise<boolean> {
|
||
|
|
if (process.env.PLAYWRIGHT_HEADLESS === "false") return true;
|
||
|
|
if (process.env.PLAYWRIGHT_HEADLESS === "true") return false;
|
||
|
|
try {
|
||
|
|
const connection = await getActiveAccountConnectionSettings();
|
||
|
|
if (connection.playwrightDebug) return true;
|
||
|
|
return connection.publishHeaded ?? true;
|
||
|
|
} catch {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getPlaywrightSlowMo(): number {
|
||
|
|
const raw = process.env.PLAYWRIGHT_SLOW_MO;
|
||
|
|
if (raw) {
|
||
|
|
const parsed = Number(raw);
|
||
|
|
if (!Number.isNaN(parsed) && parsed >= 0) return parsed;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createDebugRun(
|
||
|
|
label: string,
|
||
|
|
options?: { force?: boolean }
|
||
|
|
): Promise<DebugRun | null> {
|
||
|
|
const enabled = options?.force === true || await isPlaywrightDebugEnabled();
|
||
|
|
if (!enabled) return null;
|
||
|
|
|
||
|
|
const id = `${Date.now()}-${label.replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40)}`;
|
||
|
|
const dir = path.join(DEBUG_DIR, id);
|
||
|
|
await mkdir(dir, { recursive: true });
|
||
|
|
await writeFile(
|
||
|
|
path.join(dir, "manifest.json"),
|
||
|
|
JSON.stringify({ id, label, startedAt: new Date().toISOString(), steps: [] }, null, 2)
|
||
|
|
);
|
||
|
|
|
||
|
|
console.log(`[threads-debug] run=${id} label=${label}`);
|
||
|
|
return { id, label, dir, enabled };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function captureDebugStep(
|
||
|
|
page: Page,
|
||
|
|
run: DebugRun | null | undefined,
|
||
|
|
step: string,
|
||
|
|
meta?: Record<string, unknown>
|
||
|
|
) {
|
||
|
|
if (!run?.enabled) return;
|
||
|
|
|
||
|
|
const safeStep = step.replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 80);
|
||
|
|
const timestamp = new Date().toISOString();
|
||
|
|
const shotPath = path.join(run.dir, `${safeStep}.png`);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await page.screenshot({ path: shotPath, fullPage: true });
|
||
|
|
} catch (error) {
|
||
|
|
console.warn(`[threads-debug] screenshot failed (${step})`, error);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const manifestPath = path.join(run.dir, "manifest.json");
|
||
|
|
const { readFile } = await import("fs/promises");
|
||
|
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as {
|
||
|
|
steps: Array<Record<string, unknown>>;
|
||
|
|
};
|
||
|
|
manifest.steps.push({
|
||
|
|
step,
|
||
|
|
at: timestamp,
|
||
|
|
url: page.url(),
|
||
|
|
title: await page.title().catch(() => ""),
|
||
|
|
screenshot: `${safeStep}.png`,
|
||
|
|
...meta,
|
||
|
|
});
|
||
|
|
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
||
|
|
} catch (error) {
|
||
|
|
console.warn(`[threads-debug] manifest update failed (${step})`, error);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[threads-debug] ${run.id} · ${step} · ${page.url()}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function debugDirPath(): string {
|
||
|
|
return DEBUG_DIR;
|
||
|
|
}
|