// 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}/, '')); 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); });