// Run against a fresh scratch Console with the fixture model already started. // All organization setup and worker actions below use the actual browser UI. import assert from 'node:assert/strict'; import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; import { browser } from './browser.mjs'; const base = process.env.ORG_BROWSER_URL, shots = process.env.ORG_BROWSER_SHOTS; if (!base || !shots) throw Error('ORG_BROWSER_URL and ORG_BROWSER_SHOTS are required'); await mkdir(shots, { recursive: true }); const b = await browser({ machine: false }); const wait = async (expression, timeout = 20000) => { const end = Date.now() + timeout; while (Date.now() < end) { try { if (await b.evaluate(`Boolean(${expression})`)) return; } catch { /* a navigation replaces the page */ } await new Promise(r => setTimeout(r, 50)); } throw Error(`Timed out waiting for ${expression}`); }; const input = (selector, value) => b.evaluate(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});e.value=${JSON.stringify(value)};e.dispatchEvent(new Event('input',{bubbles:true}));e.dispatchEvent(new Event('change',{bubbles:true}));})()`); const click = selector => b.evaluate(`document.querySelector(${JSON.stringify(selector)}).click()`); try { await b.viewport(1440, 900); await b.navigate(`${base.replace(/\/$/, '')}/#/`); await wait('document.querySelector("#held")?.textContent.includes("No organizations yet")'); await b.screenshot(join(shots, '00-empty.png')); await click('#held a[href="#/settings/organizations"]'); await wait('document.querySelector("#org-new-form [data-write]") && !document.querySelector("#org-new-form [data-write]").disabled'); await input('#org-new-id', 'alpha'); await click('#org-new-form [data-write]'); await wait('document.querySelector("#org-editor") && !document.querySelector("#org-editor").hidden'); await b.screenshot(join(shots, '01-create.png')); await click('#org-save'); await wait('document.querySelector("[data-step=queue] [data-risk]")'); await b.screenshot(join(shots, '02-setup.png')); for (const step of ['queue', 'project:work']) { const selector = `[data-step="${step}"]`; await click(`${selector} [data-risk]`); await click(`${selector} [data-run]`); await wait(`document.querySelector(${JSON.stringify(selector + ' .org-pill')})?.textContent === 'Done'`); } await click('[data-step="bus"] [data-run]'); await wait('document.querySelector("[data-step=bus] .org-pill")?.textContent === "Done"', 60000); await b.navigate(`${base.replace(/\/$/, '')}/?org-browser=1#/orgs/alpha/settings/organizations?org=alpha&step=setup`); await wait('document.querySelector("#org-setup") && !document.querySelector("#org-setup").hidden'); assert.match(await b.evaluate('location.hash'), /^#\/orgs\/alpha\/settings\/organizations\?org=alpha&step=setup$/); await click('#org-save'); await wait('document.querySelector("#org-receipt")?.textContent.includes("Changes saved")'); assert.match(await b.evaluate('location.hash'), /^#\/orgs\/alpha\/settings\/organizations\?org=alpha/); await b.navigate(`${base.replace(/\/$/, '')}/?org-browser=2#/orgs/alpha/settings/organizations?org=alpha&step=setup`); await wait('document.querySelector("#org-setup") && !document.querySelector("#org-setup").hidden'); assert.match(await b.evaluate('location.hash'), /^#\/orgs\/alpha\/settings\/organizations\?org=alpha&step=setup$/); await b.screenshot(join(shots, '03-resume.png')); await b.evaluate(`location.hash='#/orgs/alpha/chat'`); await wait('!!document.querySelector("#chat-repo option[value=work]")'); await click('.project-create summary'); await input('#chat-project-name', 'Work'); await input('#chat-repo', 'work'); await b.screenshot(join(shots, '03-linked-project.png')); await click('#chat-project-form button[type=submit]'); await wait('location.hash.match(/\\/projects\\/[0-9a-f-]{36}/i)'); const projectRoute = await b.evaluate('location.hash'); await b.evaluate(`location.hash=${JSON.stringify(projectRoute + '/conversations')}`); await wait('!!document.querySelector("#chat-thread-form")'); await click('.project-new-conversation summary'); await input('#chat-thread-name', 'Trial'); await input('#chat-instance', 'assistant'); await click('#chat-thread-form button[type=submit]'); await wait('!!document.querySelector("#chat-body")', 30000); await input('#chat-body', 'Reply with fixture word'); await click('#chat-send-form button[type=submit]'); await wait('document.querySelector(".chat-log")?.textContent.includes("PINEAPPLE")', 90000); assert.match(await b.evaluate('document.querySelector(".chat-log").textContent'), /PINEAPPLE/); await b.screenshot(join(shots, '04-fixture-reply.png')); // Start the second creation in a fresh page so the chat form state does not // count as an unrelated draft during the organization switch. await b.navigate(`${base.replace(/\/$/, '')}/#/orgs/alpha/settings/organizations`); await wait('document.querySelector("#org-new-form [data-write]") && !document.querySelector("#org-new-form [data-write]").disabled'); await input('#org-new-id', 'beta'); await click('#org-new-form [data-write]'); await wait('document.querySelector("#org-editor") && !document.querySelector("#org-editor").hidden'); await b.evaluate(`(()=>{const e=document.querySelector('#org-json-text'), d=JSON.parse(e.value);d.appearance={theme:'cobalt',mode:'light'};e.value=JSON.stringify(d,null,2);e.dispatchEvent(new Event('input',{bubbles:true}));})()`); await click('#org-save'); await wait('window.MosaicOrg?.id === "beta" && document.querySelector("#org-setup") && !document.querySelector("#org-setup").hidden'); await b.evaluate('window.MosaicAppearance.ready'); assert.match(await b.evaluate('location.hash'), /^#\/orgs\/beta\/settings\/organizations\?org=beta&step=setup/); assert.match(await b.evaluate('document.querySelector("header").textContent'), /beta/); assert.equal(await b.evaluate('document.documentElement.dataset.palette'), 'cobalt'); assert.match(await b.evaluate('document.querySelector("#org-receipt").textContent'), /Organization created.*beta/s); await b.screenshot(join(shots, '05-created-beta.png')); console.log('Browser organization setup and linked project fixture reply passed'); } catch (error) { await b.screenshot(join(shots, 'failure.png')).catch(() => {}); console.error(await b.evaluate('document.body.innerText.slice(0, 5000)').catch(() => 'page unavailable')); throw error; } finally { await b.close(); }