In organization mode #/ is the cross-organization dashboard (sample 02), #/projects lists every organization's projects with sort, filters and the shared By org / Flat grouping (sample 04), and #/inbox lists decisions across organizations with per-organization decide paths and terminal commands carrying --org and the full id (sample 05). The per-organization board moves to #/board and the organization's own project list to #/chat, which keeps the project surface. Reads go through workspace-data.js; a read an organization could not make says not read, never zero. Polling repaints in place and keeps focus, caret and scroll.
91 lines
6.4 KiB
JavaScript
91 lines
6.4 KiB
JavaScript
// 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(); }
|