// Scrape MoySklad REST API docs for sections relevant to the money module. // Headless-Chromium нужен потому что doc SPA рендерит разделы JS-ом. // Запуск: cd tests/e2e && npx tsx scrape-moysklad.ts import { chromium } from "playwright"; import { writeFile, mkdir } from "node:fs/promises"; import { dirname } from "node:path"; const targets: { slug: string; label: string }[] = [ { slug: "#/documents/payment-in", label: "Входящий платёж (безнал)" }, { slug: "#/documents/payment-out", label: "Исходящий платёж (безнал)" }, { slug: "#/documents/cashin", label: "Приходный ордер (наличные)" }, { slug: "#/documents/cashout", label: "Расходный ордер (наличные)" }, { slug: "#/documents/retaildrawercashin", label: "Внесение денег в кассу" }, { slug: "#/documents/retaildrawercashout", label: "Выплата денег из кассы" }, { slug: "#/documents/retailshift", label: "Розничная смена" }, { slug: "#/documents/counterpartyadjustment", label: "Корректировка взаиморасчётов" }, { slug: "#/reports/report-money", label: "Отчёт «Деньги»" }, { slug: "#/reports/report-counterparty", label: "Показатели контрагентов" }, { slug: "#/dictionaries/organization", label: "Юрлицо / счета юрлица" }, ]; const BASE = "https://dev.moysklad.ru/doc/api/remap/1.2/"; const OUT = "/tmp/moysklad-dump"; async function dumpOne(page: import("playwright").Page, slug: string, label: string): Promise { const url = BASE + slug; await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45_000 }); // SPA рендер — ждём, пока в DOM появятся заголовки раздела. await page.waitForTimeout(2500); // Снимаем контент самой длинной «статьи» (у них разметка меняется от раздела). const text = await page.evaluate(() => { // Пытаемся найти самую большую семантическую зону — main/article или просто body. const candidates = Array.from(document.querySelectorAll("main, article, section, .markdown-section, .content, body")); let best: Element = document.body; let bestLen = 0; for (const el of candidates) { const len = (el as HTMLElement).innerText?.length ?? 0; if (len > bestLen) { bestLen = len; best = el; } } return (best as HTMLElement).innerText; }); return `=== ${label} (${slug}) ===\nURL: ${url}\n\n${text}\n\n`; } async function main() { const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 }, locale: "ru-RU" }); const page = await ctx.newPage(); await mkdir(OUT, { recursive: true }); for (const t of targets) { const safe = t.slug.replace(/[^a-z0-9]+/gi, "_"); console.log("→", t.slug); try { const dump = await dumpOne(page, t.slug, t.label); const path = `${OUT}/${safe}.txt`; await mkdir(dirname(path), { recursive: true }); await writeFile(path, dump, "utf8"); console.log(" wrote", path, dump.length, "chars"); } catch (e: any) { console.error(" FAILED", t.slug, e?.message); } } await browser.close(); } main().catch((e) => { console.error(e); process.exit(1); });