__tests__/postwatch-extract.test.js
Rama main · réplica de lectura del repositorio original.
/**
* The extractor against REAL Facebook pages, saved as gzipped fixtures (captured with the watcher's
* probe, `html:true`). Every DOM fact learned on the live post is pinned here: comment/reply articles
* by aria-label, author + reply target from the label with the time suffix stripped, the Follow badge
* kept out of the text, the hidden duplicate copy ignored, the post body found and not a sidebar
* suggestion. Runs the page in a real Chromium (layout matters: visibility is part of the rules).
* Skips cleanly where the fixtures or a launchable Chromium are not present (the VPS host has no
* browser deps; the pod image and CI do).
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import zlib from 'node:zlib';
import { extractInPage } from '../src/postWatch.js';
const FIX = path.join(__dirname, 'fixtures', 'fb');
const have = (f) => fs.existsSync(path.join(FIX, f + '.gz'));
const read = (f) => zlib.gunzipSync(fs.readFileSync(path.join(FIX, f + '.gz'))).toString('utf8');
let chromium = null, browser = null;
try { chromium = (await import('playwright')).chromium; } catch { chromium = null; }
if (chromium && have('post-page.html')) {
try { browser = await chromium.launch({ args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu'] }); }
catch (e) { browser = null; console.warn(`[postwatch-extract] no launchable Chromium here (${String(e.message).split('\n')[0]}) — skipping; runs in the pod image / CI`); }
}
describe.skipIf(!browser)('post watcher extractor on real pages', () => {
let page;
beforeAll(async () => { page = await browser.newPage({ viewport: { width: 1280, height: 2400 } }); await page.route('**/*', (r) => r.abort()).catch(() => {}); }, 60000);
afterAll(async () => { try { await browser.close(); } catch { /* gone */ } });
const load = async (file, url) => {
await page.goto(url, { waitUntil: 'commit' }).catch(() => {}); // location.href carries the ids
await page.setContent(read(file), { waitUntil: 'domcontentloaded' });
return page.evaluate(extractInPage);
};
it('post page: root comments with clean authors, no time suffix, no Follow badge in the text, the post itself', async () => {
const t = await load('post-page.html', 'https://www.facebook.com/groups/vibecodinglife/posts/2137606046827921/');
expect(t.postId).toBe('2137606046827921');
expect(t.nodes.length).toBeGreaterThan(5);
for (const n of t.nodes) {
expect(n.author).not.toMatch(/geleden|ago|\d+\s*u$/i);
expect(n.text).not.toMatch(/^volgen$|^follow$/i);
expect(n.author).not.toMatch(/ op (het antwoord|de opmerking) van /i);
}
expect(t.postText).toMatch(/70% wall/i); // the post, not a sidebar suggestion
expect(new Set(t.nodes.map((n) => n.id)).size).toBe(t.nodes.length); // one node per id
}, 60000);
it.skipIf(!have('reply-page.html'))('reply page: author AND reply target from the label, replies-to-replies present, owner recognised', async () => {
const t = await load('reply-page.html', 'https://www.facebook.com/groups/vibecodinglife/posts/2137606046827921/?comment_id=2137644956824030&reply_comment_id=2138317006756825');
const replies = t.nodes.filter((n) => n.isReply);
expect(replies.length).toBeGreaterThan(0);
expect(replies.some((n) => n.replyTo && n.replyTo.length > 2)).toBe(true);
expect(t.nodes.some((n) => /wesley stoep/i.test(n.author))).toBe(true);
for (const n of replies) { expect(n.cid).toBeTruthy(); expect(n.rid || n.id).toBeTruthy(); }
}, 60000);
});