Ratatoskr/harness/estate/drive.js
Meta Vens 0b7ded9c99 Estate: stewards from the hierarchy's Space child content, joins wait for the store, room finder on search, Create room and Join with address, Elsewhere group, invited DMs
Steward groups read org.gamecult.steward from the m.space.child content the hierarchy
returns, so an invited-only member gets them; root-state stewardships and
GameCultStewardships are deleted. Tapping Join waits for the client store to hold the
room before navigating (no invite-card frame). The header search opens the room finder
on every width. Create room and Join with address are navigator actions; joined non-DM
rooms outside the Space sit under Elsewhere; invited DMs list in Direct with Join.
harness/estate holds the fake-homeserver Playwright pin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 17:46:11 +01:00

76 lines
4.2 KiB
JavaScript

// Drives the built client against fakehs.js on a phone viewport and asserts the estate navigator.
const { chromium } = require('playwright');
const fs = require('fs');
const out = (n) => `/out/${n}`;
const tl = [];
const fails = [];
const check = (ok, what) => { tl.push(`${ok ? 'PASS' : 'FAIL'} ${what}`); if (!ok) fails.push(what); };
(async () => {
const b = await chromium.launch();
const ctx = await b.newContext({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true, deviceScaleFactor: 1 });
const pg = await ctx.newPage();
await pg.goto('http://127.0.0.1:8080/');
await pg.evaluate(() => { localStorage.setItem('cinny_access_token', 'tok'); localStorage.setItem('cinny_device_id', 'DEV'); localStorage.setItem('cinny_user_id', '@t:test'); localStorage.setItem('cinny_hs_base_url', 'http://127.0.0.1:8080'); });
await pg.goto('http://127.0.0.1:8080/#/home/');
await pg.getByText('Create Room').first().waitFor({ timeout: 30000 });
await pg.waitForTimeout(2000);
const text = () => pg.evaluate(() => document.body.innerText);
const snap = async (n) => { await pg.screenshot({ path: out(n + '.png') }); };
const open = async (name) => { const h = pg.getByText(name, { exact: true }); if (await h.count()) { await h.first().click(); await pg.waitForTimeout(300); } };
// Entry points
const t0 = await text();
check(/Create Room/.test(t0) && /Join with Address/.test(t0), 'Create Room and Join with Address are navigator actions');
await pg.getByText('Join with Address').first().click();
await pg.waitForTimeout(400);
check((await pg.locator('input[name="addressInput"]').count()) === 1, 'Join with Address opens its prompt');
await pg.keyboard.press('Escape');
await pg.waitForTimeout(300);
if (await pg.locator('input[name="addressInput"]').count()) await pg.getByText('Cancel').first().click();
// Header search is the room finder, on a phone
await pg.locator('[aria-label="Find a room"]').first().click();
await pg.waitForTimeout(500);
check((await pg.locator('input[placeholder="Search"]').count()) === 1 && !/#\/home\/search/.test(pg.url()), 'header search opens the room finder, not message search');
await snap('finder');
await pg.keyboard.press('Escape');
await pg.waitForTimeout(300);
// Groups for an invitee, from the hierarchy alone; Elsewhere; invited DM
for (const n of ['Projects', 'nibu', 'eureka', 'Nibu', 'Eureka', 'Elsewhere', 'Direct Messages']) await open(n);
await snap('expanded');
const t1 = (await text()).toLowerCase();
check(t1.includes('nibu') && t1.includes('eureka'), 'steward groups render for an invitee who cannot read Space state');
check(t1.includes('elsewhere') && t1.includes('meeting'), 'a joined room outside the Space is listed under Elsewhere');
check(t1.includes('direct messages') && (await pg.locator('[aria-label="Join Void"]').count()) === 1, 'an invited DM is listed with Join');
// Join: no invite card at any frame, then the room
await pg.locator('[aria-label="Join Aetheria"]').first().click();
const t = Date.now();
let card = 0, room = null;
while (Date.now() - t < 6000) {
const body = await text();
if (/Summary card/.test(body)) card++;
if (room === null && /welcome to Aetheria/.test(body)) room = Date.now() - t;
await pg.waitForTimeout(40);
}
await snap('joined');
check(card === 0, `no invite card at any frame after the tap (saw ${card})`);
check(room !== null, `the room opened (after ${room} ms)`);
// Join the DM: lands in the room too
await pg.goto('http://127.0.0.1:8080/#/home/');
await pg.waitForTimeout(3000);
await open('Direct Messages');
if (await pg.locator('[aria-label="Join Void"]').count()) {
await pg.locator('[aria-label="Join Void"]').first().click();
let dmCard = 0; const t2 = Date.now();
while (Date.now() - t2 < 5000) { if (/Summary card/.test(await text())) dmCard++; await pg.waitForTimeout(40); }
check(dmCard === 0 && /\/direct\//.test(pg.url()), 'the invited DM opens in Direct with no invite card');
}
await b.close();
})().catch((e) => { tl.push('ERROR ' + String(e.stack || e)); fails.push('error'); }).finally(() => {
fs.writeFileSync(out('timeline.txt'), tl.join('\n'));
console.log(tl.join('\n'));
process.exitCode = fails.length ? 1 : 0;
});