67b3fd23d1
gates / gates (push) Failing after 13m1s
Part A: claim table documentation/audits/website-refresh-2026-10-08/claims.md; "100% open source" corrected from the licence read; unbacked claims cut (firewall, RAID, snapshots, new-machine restore, self-managed mode, household VPN, "never lost"). Part B: dawarich, docmost, grimmory, homebox, karakeep, mealie, metube, radicale, recipe-importer, sparkyfitness cards; plant-it and wger cut (not offered). Part C: /en/ twins, nav language switch, hreflang both ways, sitemap with xhtml:link, og-image-en.png. Part D: site_gates.py twins/lang/same-apps/no-Hungarian/FAQ-JSON-LD/tail. Operator ruling 9 + choice B in 10-localisation.md §11, §10.7. Register: R-902 (contact mailer source in no repo); R-813, R-784, R-559 annotated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
126 lines
8.1 KiB
JavaScript
126 lines
8.1 KiB
JavaScript
// Homebox screenshots for the website card: on scratch guest 9202, headless Chrome through the box's own traefik
|
|
// (every name mapped to it), passing the setup gate as the household (the dashboard session vouches). An invented
|
|
// demo account and a few invented things are created through Homebox's own API in the page, then its own UI is shot.
|
|
const puppeteer = require('puppeteer');
|
|
const fs = require('fs');
|
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
const S = JSON.parse(fs.readFileSync('/out/secrets.json'));
|
|
const D = process.env.DOMAIN, URL = `https://${process.env.SUB}.${D}`;
|
|
(async () => {
|
|
const b = await puppeteer.launch({args: ['--no-sandbox', '--ignore-certificate-errors', '--lang=hu-HU',
|
|
'--host-resolver-rules=MAP * ' + process.env.TRAEFIK]});
|
|
const p = await b.newPage();
|
|
await p.setCookie({name: 'felhom_session', value: S.session, domain: `felhom.${D}`, path: '/', secure: true});
|
|
await p.setViewport({width: 1280, height: 800});
|
|
await p.goto(URL + '/', {waitUntil: 'networkidle2'});
|
|
await sleep(1500);
|
|
console.log('landed', p.url().replace(/\?.*/, ''));
|
|
const r = await p.evaluate(async (S) => {
|
|
const j = (m, u, body, tok) => fetch(u, {method: m, headers: Object.assign({'Content-Type': 'application/json'},
|
|
tok ? {Authorization: tok} : {}), body: body ? JSON.stringify(body) : undefined}).then(async x => [x.status, await x.text()]);
|
|
const out = [];
|
|
if (S.skipseed) return out;
|
|
const jp = t => { try { return JSON.parse(t); } catch (e) { return {}; } };
|
|
out.push(['register', ...(await j('POST', '/api/v1/users/register', {name: S.name, email: S.user, password: S.pw})).map(x => String(x).slice(0, 80))]);
|
|
const [lc, lb] = await j('POST', '/api/v1/users/login', {username: S.user, password: S.pw, stayLoggedIn: true});
|
|
out.push(['login', lc]);
|
|
let tok;
|
|
try { tok = JSON.parse(lb).token; } catch (e) { return out.concat([['loginbody', lb.slice(0, 80)]]); }
|
|
const [tc, tt] = await j('GET', '/api/v1/entity-types', null, tok);
|
|
const types = jp(tt); out.push(['types', tc, tt.slice(0, 300)]);
|
|
const tl = (Array.isArray(types) ? types : (types.items || [])).find(x => x.isLocation) || {};
|
|
const ti = (Array.isArray(types) ? types : (types.items || [])).find(x => !x.isLocation) || {};
|
|
const loc = {};
|
|
for (const [n, d] of [['Nappali', 'Földszint'], ['Konyha', 'Földszint'], ['Garázs', 'Udvar'], ['Dolgozószoba', 'Emelet']]) {
|
|
const [c, t] = await j('POST', '/api/v1/entities', {name: n, description: d, entityTypeId: tl.id}, tok); loc[n] = jp(t).id; out.push(['loc', c, c >= 300 ? t.slice(0, 80) : '']);
|
|
}
|
|
const lab = {};
|
|
for (const [n, col] of [['Elektronika', '#3b82f6'], ['Garanciális', '#16a34a'], ['Szerszám', '#ea580c']]) {
|
|
const [c, t] = await j('POST', '/api/v1/tags', {name: n, color: col}, tok); lab[n] = jp(t).id; out.push(['label', c, c >= 300 ? t.slice(0, 80) : '']);
|
|
}
|
|
const items = [
|
|
['Mosogatógép', 'Beépített, 60 cm', 'Konyha', ['Elektronika', 'Garanciális']],
|
|
['Televízió 55"', 'Nappali fali konzolon', 'Nappali', ['Elektronika', 'Garanciális']],
|
|
['Akkus fúró-csavarozó', '18 V, két akkumulátorral', 'Garázs', ['Szerszám']],
|
|
['Létra, 3 fokos', 'Alumínium', 'Garázs', ['Szerszám']],
|
|
['Laptop', 'Munkához', 'Dolgozószoba', ['Elektronika']],
|
|
['Kávéfőző', 'Kapszulás', 'Konyha', ['Elektronika']],
|
|
['Porszívó', 'Vezeték nélküli', 'Nappali', ['Elektronika', 'Garanciális']],
|
|
['Fűnyíró', 'Elektromos', 'Garázs', ['Szerszám', 'Garanciális']],
|
|
];
|
|
for (const [n, d, l, ls] of items) {
|
|
const [c, t] = await j('POST', '/api/v1/entities', {name: n, description: d, entityTypeId: ti.id, parentId: loc[l], tagIds: ls.map(x => lab[x])}, tok);
|
|
out.push(['item', c, c >= 300 ? t.slice(0, 120) : '']);
|
|
}
|
|
return out;
|
|
}, S);
|
|
console.log(JSON.stringify(r));
|
|
await p.goto(URL + '/', {waitUntil: 'networkidle2'});
|
|
await sleep(1500);
|
|
await p.screenshot({path: '/out/login.png'});
|
|
if (process.env.STOP) { await b.close(); return; }
|
|
if (await p.$('input[type=password]')) { // the seed's API sign-in normally leaves the browser signed in already
|
|
const email = await p.$('input[type=email], input[name=email], input[type=text]');
|
|
if (email) await email.type(S.user);
|
|
await p.type('input[type=password]', S.pw);
|
|
await p.keyboard.press('Enter');
|
|
await p.waitForNavigation({waitUntil: 'networkidle2', timeout: 30000}).catch(() => {});
|
|
}
|
|
await sleep(2000);
|
|
// Homebox's own language preference (its Profile page writes it to localStorage): Hungarian
|
|
console.log('prefs before', await p.evaluate(() => JSON.stringify(Object.keys(localStorage))));
|
|
await p.evaluate(() => {
|
|
const k = 'homebox/preferences/location';
|
|
let v = {}; try { v = JSON.parse(localStorage.getItem(k) || '{}'); } catch (e) {}
|
|
v.language = 'hu'; localStorage.setItem(k, JSON.stringify(v));
|
|
});
|
|
if (S.cleanup) {
|
|
// Homebox's own default English locations and tags (empty) removed so the demo reads in one language; the
|
|
// collection's currency set to forint; invented prices and quantities on the invented things.
|
|
const r = await p.evaluate(async () => {
|
|
const out = [];
|
|
const J = async (m, u, b) => { const x = await fetch(u, {method: m, headers: {'Content-Type': 'application/json'}, body: b ? JSON.stringify(b) : undefined}); return [x.status, await x.text()]; };
|
|
const mine = ['Nappali', 'Konyha', 'Garázs', 'Dolgozószoba', 'Elektronika', 'Garanciális', 'Szerszám'];
|
|
const tree = JSON.parse((await J('GET', '/api/v1/entities/tree'))[1]);
|
|
for (const n of tree) if (!mine.includes(n.name) && !(n.children || []).length) out.push(['del loc ' + n.name, (await J('DELETE', '/api/v1/entities/' + n.id))[0]]);
|
|
const tags = JSON.parse((await J('GET', '/api/v1/tags'))[1]);
|
|
for (const t of (Array.isArray(tags) ? tags : tags.items || [])) if (!mine.includes(t.name)) out.push(['del tag ' + t.name, (await J('DELETE', '/api/v1/tags/' + t.id))[0]]);
|
|
const g = JSON.parse((await J('GET', '/api/v1/groups'))[1]);
|
|
out.push(['currency', (await J('PUT', '/api/v1/groups', {name: g.name, currency: 'HUF'}))[0]]);
|
|
const price = {'Mosogatógép': 189990, 'Televízió 55"': 249990, 'Akkus fúró-csavarozó': 42990, 'Létra, 3 fokos': 12990,
|
|
'Laptop': 329990, 'Kávéfőző': 34990, 'Porszívó': 89990, 'Fűnyíró': 64990};
|
|
const list = JSON.parse((await J('GET', '/api/v1/entities?pageSize=100'))[1]);
|
|
for (const it of (list.items || [])) {
|
|
if (!(it.name in price)) continue;
|
|
const [c, full] = await J('GET', '/api/v1/entities/' + it.id);
|
|
const o = JSON.parse(full);
|
|
o.purchasePrice = price[it.name]; o.quantity = 1; o.insured = /Televízió|Laptop|Mosogatógép/.test(it.name);
|
|
o.tagIds = (o.tags || []).map(t => t.id); o.parentId = o.parent ? o.parent.id : o.parentId;
|
|
const [pc, pt] = await J('PUT', '/api/v1/entities/' + it.id, o);
|
|
out.push(['price ' + it.name, pc, pc >= 300 ? pt.slice(0, 120) : '']);
|
|
}
|
|
return out;
|
|
});
|
|
console.log(JSON.stringify(r));
|
|
}
|
|
await p.reload({waitUntil: 'networkidle2'});
|
|
await sleep(3000);
|
|
console.log('after login', p.url());
|
|
await p.screenshot({path: '/out/1.png'});
|
|
await p.click('::-p-text(Fűnyíró)').catch(e => console.log('no tv row', e.message));
|
|
await sleep(3000);
|
|
console.log('shot 2 at', p.url().replace(/[0-9a-f-]{36}/, '<id>'));
|
|
await p.screenshot({path: '/out/2.png'});
|
|
await p.setViewport({width: 390, height: 844, deviceScaleFactor: 2});
|
|
await p.goto(URL + '/home', {waitUntil: 'networkidle2'}).catch(() => {});
|
|
await p.evaluate(() => { // a full navigation put the language back to English: set it again, as on the desktop
|
|
const k = 'homebox/preferences/location';
|
|
let v = {}; try { v = JSON.parse(localStorage.getItem(k) || '{}'); } catch (e) {}
|
|
v.language = 'hu'; localStorage.setItem(k, JSON.stringify(v));
|
|
});
|
|
await p.reload({waitUntil: 'networkidle2'});
|
|
await sleep(3000);
|
|
await p.screenshot({path: '/out/3.png'});
|
|
await b.close();
|
|
})().catch(e => { console.error(e); process.exit(1); });
|