Files
Hare db4c8ba97f
Reference site / build (push) Canceled after 0s
feat: add highlighted protocol views and inline operation forms
Expose Rust Text View tokens and safely highlight signatures and JSON results. Add animated Interface and inline Run disclosures with a shared 10ms line cadence, typed argument inputs, and explicit execution.

Update documentation and browser regressions for the new controls and explicit counter-value responses.
2026-10-26 10:12:22 +09:00

525 lines
34 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test';
import { readFile } from 'node:fs/promises';
test.use({ javaScriptEnabled: true, reducedMotion: 'reduce' });
const panel = (page: Page) => page.locator('[data-protocol-panel]');
const protocolURL = (base: string, route = '') => { const url = new URL(route, base); url.searchParams.set('mode', 'protocol'); return url.href; };
async function ready(page: Page) {
await expect(panel(page)).toBeVisible();
await expect(panel(page)).toHaveAttribute('data-state', 'ready');
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'manual');
await expect(page.locator('[data-selected-content]')).toBeHidden();
}
async function descriptor(page: Page) {
await expect(page.getByLabel('Object Text View')).toContainText('object {');
await page.locator('[data-interface-toggle]').first().click();
await expect(page.getByLabel('Interface Text View')).toContainText('interface');
await page.getByRole('button', { name: 'Run read', exact: true }).click();
}
async function traceObserves(page: Page) {
await page.route('**/wip-wasm/wip_browser.js?instance=protocol', async route => {
const response = await route.fetch();
await route.fulfill({ response, body: `${await response.text()}\nconst originalObserve = BrowserSession.prototype.observe; BrowserSession.prototype.observe = function(path, depth) { (window.__observed ||= []).push({ path, depth }); return originalObserve.call(this, path, depth); };` });
});
}
const observedCalls = (page: Page) => page.evaluate(() => (window as Window & { __observed?: { path: string; depth: number }[] }).__observed ?? []);
for (const [route, path, name] of [
['', '/', ''], ['book/', '/book', 'book'],
['book/1.1-interaction-abstraction/', '/book/1-concept.md/1.1-interaction-abstraction.md', '1.1-interaction-abstraction.md'],
['sources/', '/sources', 'sources'], ['packages/', '/packages', 'packages'],
]) {
test(`Protocol opens the selected Object immediately: ${path}`, async ({ page }, info) => {
await traceObserves(page);
// No tree click or Observe action is needed on any of these entry paths.
await page.goto(protocolURL(info.project.use.baseURL!, route)); await ready(page);
const object = page.getByLabel('Object Text View');
await expect(object).toBeVisible();
await expect(object).toContainText(`name: ${JSON.stringify(name)}`);
await expect(panel(page)).toHaveAttribute('data-path', path);
await expect(panel(page)).toHaveAttribute('data-observes', '1');
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(page.locator('[data-protocol-status]')).toBeHidden();
expect(await observedCalls(page)).toEqual([{ path, depth: 1 }]);
await page.getByRole('button', { name: 'Read', exact: true }).click();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
await page.getByRole('button', { name: 'Protocol', exact: true }).click(); await ready(page);
await expect(object).toBeVisible();
await expect(object).toContainText(`name: ${JSON.stringify(name)}`);
expect(await observedCalls(page)).toEqual([{ path, depth: 1 }, { path, depth: 1 }]);
await expect(panel(page)).toHaveAttribute('data-observes', '1'); // Fresh Client cache.
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await page.reload(); await ready(page);
await expect(object).toBeVisible();
await expect(object).toContainText(`name: ${JSON.stringify(name)}`);
await expect(page.locator('[data-protocol-status]')).toBeHidden();
expect(await observedCalls(page)).toEqual([{ path, depth: 1 }]);
});
}
for (const colorScheme of ['light', 'dark'] as const) {
test(`Interface buttons use a text-width TUI background in ${colorScheme} mode`, async ({ page }, info) => {
await page.emulateMedia({ colorScheme });
await page.goto(protocolURL(info.project.use.baseURL!, 'playground/')); await ready(page);
const button = page.getByRole('button', { name: '? /::collection', exact: true });
const toggle = page.locator('[data-interface-toggle]').filter({ hasText: '/::collection' });
await expect(button).toBeVisible();
const background = await button.evaluate(element => {
const probe = document.createElement('span'); probe.style.backgroundColor = 'var(--code-bg)';
element.append(probe); const color = getComputedStyle(probe).backgroundColor; probe.remove(); return color;
});
expect(background).not.toBe('rgba(0, 0, 0, 0)');
for (const state of ['?', 'v', '>']) {
await page.mouse.move(0, 0);
await page.getByRole('button', { name: 'Read', exact: true }).focus();
await expect(toggle).toHaveText(`${state} /::collection`);
await expect(toggle).toHaveCSS('background-color', background);
await expect(toggle).toHaveCSS('border-radius', '0px');
await expect(toggle).toHaveCSS('border-top-width', '0px');
await expect(toggle).toHaveCSS('padding-top', '0px');
expect(await toggle.evaluate(element => element.getBoundingClientRect().width < element.parentElement!.getBoundingClientRect().width)).toBe(true);
await toggle.hover();
await expect(toggle).not.toHaveCSS('background-color', background);
if (state !== '>') await toggle.click();
}
await page.mouse.move(0, 0); await toggle.focus(); await page.keyboard.press('Enter');
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
await expect(toggle).toHaveCSS('outline-width', '2px');
await expect(page.getByLabel('Interface Text View').filter({ visible: true })).toContainText('/::collection');
await page.screenshot({ path: info.outputPath(`interface-background-${colorScheme}.png`), fullPage: true });
});
}
test('Object selection shows an unboxed Text View without Observe forms or step panels', async ({ page }, info) => {
await page.goto(protocolURL(info.project.use.baseURL!, 'sources/')); await ready(page);
const object = page.getByLabel('Object Text View');
await expect(object).toBeVisible();
await expect(object).toContainText('object {');
await expect(object).toContainText('name: "sources"');
await expect(object).toHaveCSS('border-top-width', '0px');
await expect(object).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
await expect(panel(page).locator('fieldset, legend, h2, h3, [data-protocol-path], [data-protocol-depth], [data-protocol-observe], [data-protocol-stats], [data-protocol-children]')).toHaveCount(0);
await expect(page.locator('[data-protocol-status]')).toBeHidden();
await expect(page.locator('[data-protocol-operation-controls], [data-protocol-operation], [data-protocol-arguments], [data-operation-form], [data-protocol-call]')).toHaveCount(0);
await expect(page.getByLabel('Operation result')).toBeHidden();
await expect(page.getByRole('button', { name: '? /::document', exact: true })).toBeVisible();
await page.screenshot({ path: info.outputPath('protocol-object.png'), fullPage: true });
await page.getByRole('button', { name: '? /::document', exact: true }).click();
await expect(page.getByLabel('Interface Text View')).toBeVisible();
await expect(page.getByRole('button', { name: 'Run read', exact: true })).toBeEnabled();
await expect(page.locator('[data-protocol-operation-controls], [data-protocol-operation], [data-protocol-arguments], [data-protocol-call]')).toHaveCount(0);
await page.getByRole('button', { name: 'Run read', exact: true }).click();
await expect(page.getByRole('form', { name: 'read arguments', exact: true })).toHaveAttribute('data-operation-form', 'read');
await expect(page.getByRole('button', { name: 'Execute read', exact: true })).toBeEnabled();
await expect(page.getByLabel('Operation result')).toBeHidden();
await page.getByRole('button', { name: 'v /::document', exact: true }).click();
await expect(page.locator('[data-protocol-operation-controls], [data-protocol-operation], [data-protocol-arguments], [data-operation-form], [data-protocol-call]')).toHaveCount(0);
await expect(object).toBeVisible();
});
test('tree selection automatically observes Depth 1 only, including root, nested paths and reselection', async ({ page }, info) => {
await traceObserves(page);
await page.goto(protocolURL(info.project.use.baseURL!)); await ready(page);
const expected: { path: string; depth: number }[] = [{ path: '/', depth: 1 }];
for (const [display, canonicalPath] of [['/', '/'], ['/book', '/book'], ['/1-concept.md', '/book/1-concept.md'], ['/sources', '/sources'], ['/packages', '/packages']]) {
await page.locator(`.demo-node[data-path="${display}"]`).click();
await ready(page);
await expect(panel(page)).toHaveAttribute('data-path', canonicalPath);
await expect(page.getByLabel('Object Text View')).toContainText('object {');
await expect(page.getByRole('button', { name: '? /::document', exact: true })).toBeEnabled();
await expect(page.getByLabel('Interface Text View')).toBeHidden();
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(page.locator('[data-operation-form], [data-protocol-call]')).toHaveCount(0);
expected.push({ path: canonicalPath, depth: 1 });
expect(await observedCalls(page)).toEqual(expected);
}
await page.locator('[data-interface-toggle]').first().click();
await page.getByRole('button', { name: 'Run read', exact: true }).click();
await page.locator('[data-protocol-call]').click();
await expect(panel(page)).toHaveAttribute('data-calls', '1');
// Selecting the current Object resets the previous Interface and result.
await page.locator('.demo-node[data-path="/packages"]').click();
await expect(panel(page)).toHaveAttribute('data-path', '/packages');
await expect(page.getByLabel('Object Text View')).toContainText('name: "packages"');
await expect(page.getByRole('button', { name: '> /::document', exact: true })).toBeVisible();
await expect(page.getByLabel('Interface Text View')).toBeHidden();
await expect(page.getByLabel('Operation result')).toBeHidden();
await expect(panel(page)).toHaveAttribute('data-calls', '1');
expected.push({ path: '/packages', depth: 1 });
expect(await observedCalls(page)).toEqual(expected);
await page.goBack(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-path', '/sources');
await expect(page.getByLabel('Object Text View')).toContainText('name: "sources"');
expected.push({ path: '/sources', depth: 1 });
expect(await observedCalls(page)).toEqual(expected);
});
test('pending tree observations wait for WASM and only the latest selection runs', async ({ page }, info) => {
await traceObserves(page);
let release!: () => void;
const gate = new Promise<void>(resolve => { release = resolve; });
await page.route('**/wip-wasm/*.wasm', async route => { await gate; await route.continue(); });
try {
await page.goto(protocolURL(info.project.use.baseURL!));
await expect(panel(page)).toHaveAttribute('data-state', 'loading');
await page.locator('.demo-node[data-path="/"]').click();
await page.locator('.demo-node[data-path="/packages"]').click();
await expect(page).toHaveURL(protocolURL(info.project.use.baseURL!, 'packages/'));
await expect(panel(page)).toHaveAttribute('data-path', '/packages');
await page.locator('.demo-node[data-path="/sources"]').click();
await expect(page).toHaveURL(protocolURL(info.project.use.baseURL!, 'sources/'));
await expect(panel(page)).toHaveAttribute('data-path', '/sources');
release(); await ready(page);
await expect(page.getByLabel('Object Text View')).toContainText('name: "sources"');
expect(await observedCalls(page)).toEqual([{ path: '/sources', depth: 1 }]);
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
} finally { release(); }
});
test('Interface clicks fetch unknown signatures and fold/reopen acquired ones without another exchange', async ({ page }, info) => {
// Read's automatic acquisition must not mark the manual session as known.
await page.goto(info.project.use.baseURL!);
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
await page.getByRole('button', { name: 'Protocol', exact: true }).click(); await ready(page);
await expect(page.getByLabel('Object Text View')).toBeVisible();
const toggle = page.locator('[data-interface-toggle]');
const view = page.getByLabel('Interface Text View');
await expect(toggle).toHaveText('? /::document');
await expect(page.locator('.protocol-interface-scope')).toHaveCount(0);
await expect(page.locator('[data-protocol-interfaces]')).not.toContainText('scope:');
await expect(toggle).not.toHaveAttribute('aria-describedby');
await expect(toggle).toHaveAttribute('aria-expanded', 'false');
await expect(view).toBeHidden();
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
await toggle.focus(); await page.keyboard.press('Enter');
await expect(toggle).toHaveText('v /::document');
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
await expect(view).toBeVisible();
await expect(view).toContainText('interface "/::document"');
const signature = await view.textContent();
await expect(panel(page)).toHaveAttribute('data-interfaces', '1');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(page.getByRole('button', { name: 'Run read', exact: true })).toBeEnabled();
await toggle.focus(); await page.keyboard.press('Space');
await expect(toggle).toHaveText('> /::document');
await expect(toggle).toHaveAttribute('aria-expanded', 'false');
await expect(view).toBeHidden();
await expect(page.locator('[data-operation-form], [data-protocol-call]')).toHaveCount(0);
await toggle.click();
await expect(view).toHaveText(signature!);
await expect(panel(page)).toHaveAttribute('data-interfaces', '1');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await page.locator('.demo-node[data-path="/sources"]').click(); await ready(page);
await expect(toggle).toHaveText('> /::document');
await expect(view).toBeHidden();
await toggle.click();
await expect(view).toHaveText(signature!);
await expect(panel(page)).toHaveAttribute('data-interfaces', '1');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await page.reload(); await ready(page);
await expect(page.getByLabel('Object Text View')).toBeVisible();
await expect(toggle).toHaveText('? /::document');
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
});
test('failed Interface acquisition remains unknown and cannot retain a previous Operation target', async ({ page }, info) => {
// Advertise an additional unsupported reference at the UI boundary. Its
// acquisition still goes through the real WASM Client/Host failure path.
const missingName = '<img src=x onerror=window.__interfaceExecuted=1>';
await page.route('**/wip-wasm/wip_browser.js?instance=protocol', async route => {
const response = await route.fetch();
await route.fulfill({ response, body: `${await response.text()}\nconst originalObserve = BrowserSession.prototype.observe; BrowserSession.prototype.observe = function(...args) { const value = JSON.parse(originalObserve.apply(this, args)); value.observation.object.interfaces.push({scope: '/', name: ${JSON.stringify(missingName)}}); return JSON.stringify(value); };` });
});
await page.goto(protocolURL(info.project.use.baseURL!)); await ready(page);
await page.locator('.demo-node[data-path="/"]').click();
await page.getByRole('button', { name: '? /::document', exact: true }).click();
await page.getByRole('button', { name: 'Run read', exact: true }).click();
const missing = page.getByRole('button', { name: `? /::${missingName}`, exact: true });
await missing.click();
await expect(missing).toHaveAttribute('aria-expanded', 'false');
await expect(page.getByRole('button', { name: '> /::document', exact: true })).toBeVisible();
await expect(page.locator('[data-protocol-status]')).toContainText('protocol');
await expect(page.locator('[data-interface-view]:visible')).toHaveCount(0);
await expect(page.locator('[data-operation-form], [data-protocol-call]')).toHaveCount(0);
await expect(page.getByLabel('Operation result')).toBeHidden();
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(page.locator('[data-protocol-interfaces] img')).toHaveCount(0);
expect(await page.evaluate(() => '__interfaceExecuted' in window)).toBe(false);
await page.getByRole('button', { name: '> /::document', exact: true }).click();
await expect(page.locator('[data-interface-view]:visible')).toContainText('interface "/::document"');
await expect(page.getByRole('button', { name: 'Run read', exact: true })).toBeEnabled();
});
test('Protocol observes automatically but acquires Interfaces and calls Operations only explicitly', async ({ page }, info) => {
const unexpected: string[] = [];
page.on('pageerror', error => unexpected.push(error.message));
// Catch accidental use of the automatic convenience path, including hidden
// normal-mode reads whose separate Client counters would not reveal them.
await page.route('**/wip-wasm/wip_browser.js*', async route => {
const response = await route.fetch();
await route.fulfill({ response, body: `${await response.text()}\nfor (const key of ['tree', 'read']) { const original = BrowserSession.prototype[key]; BrowserSession.prototype[key] = function(...args) { window.__autoReads = (window.__autoReads || 0) + 1; return original.apply(this, args); }; }` });
});
await page.goto(protocolURL(info.project.use.baseURL!));
await ready(page);
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(panel(page)).toHaveAttribute('data-observes', '1');
await expect(panel(page)).toHaveAttribute('data-interfaces', '0');
await descriptor(page);
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(panel(page)).toHaveAttribute('data-observes', '1');
await expect(panel(page)).toHaveAttribute('data-interfaces', '1');
await page.locator('[data-protocol-call]').click();
await expect(panel(page)).toHaveAttribute('data-calls', '1');
const result = JSON.parse(await page.getByLabel('Operation result').innerText());
expect(result.path).toBe('/');
expect(result.operation).toBe('read');
expect(result.interface).toEqual({ scope: '/', name: 'document' });
expect(JSON.stringify(result.result)).toContain('Worldspace');
expect(JSON.stringify(result.result)).toContain('html');
expect(await page.evaluate(() => (window as Window & { __autoReads?: number }).__autoReads ?? 0)).toBe(0);
expect(unexpected).toEqual([]);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await panel(page).evaluate(element => { element.scrollTop = 0; });
await page.screenshot({ path: info.outputPath('protocol.png'), fullPage: true });
});
for (const route of ['book/1-concept/', 'sources/', 'packages/']) {
test(`manual read of ${route} agrees with normal content without creating child catalog Objects`, async ({ page }, info) => {
await page.goto(protocolURL(info.project.use.baseURL!, route));
await ready(page);
const expectedTitle = await page.locator('[data-selected-content] h1').textContent();
await descriptor(page);
await page.locator('[data-protocol-call]').click();
await expect(panel(page)).toHaveAttribute('data-calls', '1');
await expect(page.getByLabel('Operation result')).toContainText(expectedTitle!);
await page.getByRole('button', { name: 'Read', exact: true }).click();
await expect(page.locator('[data-selected-content]')).toBeVisible();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
await page.getByRole('button', { name: 'Protocol', exact: true }).click();
await ready(page);
await expect(panel(page)).toHaveAttribute('data-calls', '1');
await expect(page.getByLabel('Operation result')).toBeHidden();
});
}
test('selection, history, reload and mode switches never replay an operation', async ({ page }, info) => {
const base = info.project.use.baseURL!;
await page.goto(protocolURL(base));
await ready(page);
await descriptor(page);
await page.locator('[data-protocol-call]').click();
await expect(panel(page)).toHaveAttribute('data-calls', '1');
for (const name of ['sources', 'packages']) {
await page.locator(`.demo-node[data-path="/${name}"]`).click();
await expect(page).toHaveURL(protocolURL(base, `${name}/`));
await ready(page);
await expect(panel(page)).toHaveAttribute('data-calls', '1');
await expect(page.getByLabel('Operation result')).toBeHidden();
await expect(page.locator('[data-operation-form], [data-protocol-call]')).toHaveCount(0);
}
await page.goBack(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-path', '/sources');
await expect(panel(page)).toHaveAttribute('data-calls', '1');
await page.goForward(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-path', '/packages');
await expect(panel(page)).toHaveAttribute('data-calls', '1');
await page.reload(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await expect(page.getByLabel('Operation result')).toBeHidden();
});
test('fragment history synchronizes Read and Protocol without requiring an Astro swap', async ({ page }, info) => {
await page.goto(new URL('book/1-concept/', info.project.use.baseURL!).href);
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
// Use a real same-document link so Astro records its fragment shortcut state.
await page.evaluate(() => {
const heading = document.querySelector('[data-selected-content] [id]')!;
const link = document.createElement('a'); link.href = `#${heading.id}`; link.textContent = 'fragment regression';
document.querySelector('[data-selected-content]')!.append(link); link.click();
});
await expect(page).toHaveURL(/#/);
await page.getByRole('button', { name: 'Protocol', exact: true }).click(); await ready(page);
await page.goBack();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-mode', 'read');
await expect(panel(page)).toBeHidden();
await expect(page.locator('[data-selected-content]')).toBeVisible();
await page.goForward(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-calls', '0');
});
test('history into Protocol cancels deferred normal dispatch before the destination swaps', async ({ page }, info) => {
await traceObserves(page);
const base = info.project.use.baseURL!;
let releaseNormal!: () => void; let releasePage!: () => void;
const normalGate = new Promise<void>(resolve => { releaseNormal = resolve; });
const pageGate = new Promise<void>(resolve => { releasePage = resolve; });
let normalRequested = false; let destinationRequested = false; let holdDestination = false;
await page.route('**/wip-wasm/wip_browser.js', async route => {
normalRequested = true;
const response = await route.fetch(); await normalGate;
await route.fulfill({ response, body: `${await response.text()}\nfor (const key of ['tree', 'read']) { const original = BrowserSession.prototype[key]; BrowserSession.prototype[key] = function(...args) { window.__autoReads = (window.__autoReads || 0) + 1; return original.apply(this, args); }; }\nwindow.__normalModuleLoaded = true;` });
});
await page.route('**/sources/?mode=protocol', async route => {
if (holdDestination) { destinationRequested = true; await pageGate; }
await route.continue();
});
try {
await page.goto(protocolURL(base, 'sources/')); await ready(page);
await page.locator('.demo-node[data-path="/packages"]').click(); await ready(page);
await expect(page).toHaveURL(protocolURL(base, 'packages/'));
await expect(page.getByLabel('Object Text View')).toContainText('name: "packages"');
const before = await observedCalls(page);
expect(before).toEqual([{ path: '/sources', depth: 1 }, { path: '/packages', depth: 1 }]);
await page.getByRole('button', { name: 'Read', exact: true }).click();
await expect.poll(() => normalRequested).toBe(true);
holdDestination = true;
await page.evaluate(() => history.back());
await expect.poll(() => destinationRequested).toBe(true);
await expect(page).toHaveURL(protocolURL(base, 'sources/'));
releaseNormal();
await expect.poll(() => page.evaluate(() => Boolean((window as Window & { __normalModuleLoaded?: boolean }).__normalModuleLoaded))).toBe(true);
// Wait for WASM initialization too; no sleeps and no reliance on URL-only counters.
await page.evaluate(async base => { const module = await import(new URL('wip-wasm/wip_browser.js', base).href); await module.default(); }, base);
expect(await page.evaluate(() => (window as Window & { __autoReads?: number }).__autoReads ?? 0)).toBe(0);
expect(await observedCalls(page)).toEqual(before);
releasePage(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-path', '/sources');
await expect(page.getByLabel('Object Text View')).toContainText('name: "sources"');
expect(await observedCalls(page)).toEqual([...before, { path: '/sources', depth: 1 }]);
await expect(panel(page)).toHaveAttribute('data-calls', '0');
} finally { releaseNormal(); releasePage(); }
});
test('history from Protocol to another Read route never reads the outgoing Object', async ({ page }, info) => {
const base = info.project.use.baseURL!;
let release!: () => void; let hold = false; let requested = false;
const gate = new Promise<void>(resolve => { release = resolve; });
await page.route('**/wip-wasm/wip_browser.js', async route => {
const response = await route.fetch();
await route.fulfill({ response, body: `${await response.text()}\nconst originalRead = BrowserSession.prototype.read; BrowserSession.prototype.read = function(path) { (window.__readPaths ||= []).push(path); return originalRead.call(this, path); };` });
});
await page.route(new URL('sources/', base).href, async route => {
if (hold) { requested = true; await gate; } await route.continue();
});
const paths = () => page.evaluate(() => (window as Window & { __readPaths?: string[] }).__readPaths ?? []);
try {
await page.goto(new URL('sources/', base).href);
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
await page.locator('.demo-node[data-path="/packages"]').click();
await expect(page).toHaveURL(new URL('packages/', base).href);
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
await page.getByRole('button', { name: 'Protocol', exact: true }).click(); await ready(page);
const before = await paths(); expect(before).toEqual(['/sources', '/packages']);
hold = true; await page.evaluate(() => history.back());
await expect.poll(() => requested).toBe(true);
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-mode', 'read');
await page.evaluate(() => new Promise<void>(resolve => requestAnimationFrame(() => resolve())));
expect(await paths()).toEqual(before);
release();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-current-path', '/sources');
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
expect(await paths()).toEqual([...before, '/sources']);
} finally { release(); }
});
test('input and protocol errors remain visible, and tree selection clears stale controls', async ({ page }, info) => {
await page.route('**/worldspace.json', async route => {
const response = await route.fetch(); const data = await response.json();
data.nodes = data.nodes.filter((node: { path: string }) => node.path !== '/sources');
await route.fulfill({ response, json: data });
});
await page.goto(protocolURL(info.project.use.baseURL!)); await ready(page);
await page.locator('.demo-node[data-path="/sources"]').click(); await ready(page);
await expect(page.locator('[data-protocol-status]')).toBeVisible();
await expect(page.locator('[data-protocol-status]')).toContainText('protocol');
await expect(page.locator('[data-operation-form], [data-protocol-call]')).toHaveCount(0);
await expect(page.getByLabel('Object Text View')).toBeHidden();
await page.locator('.demo-node[data-path="/"]').click();
await expect(panel(page)).toHaveAttribute('data-path', '/');
await ready(page);
await page.locator('.demo-node[data-path="/playground"]').click(); await ready(page);
await page.locator('.demo-node[data-path="/playground/apple"]').click(); await ready(page);
await expect(panel(page)).toHaveAttribute('data-path', '/playground/apple');
await page.getByRole('button', { name: '? /::counter', exact: true }).click();
await page.getByRole('button', { name: 'Run increment', exact: true }).click();
await page.getByLabel('increment.amount', { exact: true }).fill('broken');
await page.locator('[data-protocol-call]').click();
await expect(page.locator('[data-protocol-status]')).toBeVisible();
await expect(page.locator('[data-protocol-status]')).toContainText('input');
await expect(panel(page)).toHaveAttribute('data-calls', '0');
await page.locator('.demo-node[data-path="/book"]').click(); await ready(page);
await expect(page.getByLabel('Object Text View')).toContainText('name: "book"');
await expect(page.locator('[data-protocol-operation-controls], [data-protocol-operation], [data-protocol-arguments], [data-operation-form], [data-protocol-call]')).toHaveCount(0);
await expect(page.locator('[data-protocol-status]')).toBeHidden();
});
test('Text View and returned markup are displayed as text, including in dark mode', async ({ page }, info) => {
await page.emulateMedia({ colorScheme: 'dark' });
await page.route('**/worldspace.json', async route => {
const response = await route.fetch(); const data = await response.json();
data.nodes[0].title = '<img src=x onerror="window.__executed=1">';
data.nodes[0].html = '<script>window.__executed=1</script><p>UNSAFE RESULT</p>';
await route.fulfill({ response, json: data });
});
await page.goto(protocolURL(info.project.use.baseURL!)); await ready(page);
await descriptor(page);
await expect(page.getByLabel('Object Text View')).toContainText('<img');
await page.locator('[data-protocol-call]').click();
await expect(page.getByLabel('Operation result')).toContainText('UNSAFE RESULT');
await expect(panel(page).locator('img, script')).toHaveCount(0);
expect(await page.evaluate(() => (window as Window & { __executed?: number }).__executed)).toBeUndefined();
await expect(page.locator('html')).toHaveCSS('color-scheme', 'dark');
await page.unroute('**/worldspace.json');
await page.getByRole('button', { name: 'Read', exact: true }).focus();
await page.keyboard.press('Enter');
await expect(panel(page)).toBeHidden();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
});
test('asset failure leaves a clear unavailable state and readable static fallback', async ({ page }, info) => {
await page.route('**/wip-wasm/wip_browser.js*', route => route.abort());
await page.goto(protocolURL(info.project.use.baseURL!));
await expect(panel(page)).toHaveAttribute('data-state', 'unavailable');
await expect(page.getByLabel('Object Text View')).toBeHidden();
await page.getByRole('button', { name: 'Read', exact: true }).click();
await expect(page.locator('[data-selected-content]').getByRole('heading', { level: 1, name: 'WIP', exact: true })).toBeVisible();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'fallback');
});
test('a trapped manual session is quarantined across routes and normal mode uses an independent instance', async ({ page }, info) => {
const asset = (name: string) => readFile(new URL(`../../test-assets/wip-wasm/${name}`, import.meta.url));
await page.route('**/wip-wasm/wip_browser.js?instance=protocol', async route => {
const source = (await asset('wip_browser.js')).toString();
await route.fulfill({ contentType: 'text/javascript', body: `${source}\nBrowserSession.prototype.observe = function () { window.__trapCalls = (window.__trapCalls || 0) + 1; window.__trappedManual = new WeakRef(this); return this.panic_probe(); };` });
});
await page.route('**/wip-wasm/*.wasm', async route => route.fulfill({ contentType: 'application/wasm', body: await asset('wip_browser_bg.wasm') }));
await page.goto(protocolURL(info.project.use.baseURL!));
await expect(panel(page)).toHaveAttribute('data-state', 'trapped');
await page.locator('.demo-node[data-path="/sources"]').click();
await expect(panel(page)).toHaveAttribute('data-state', 'trapped');
await expect(page.getByLabel('Object Text View')).toBeHidden();
const cdp = await page.context().newCDPSession(page);
await cdp.send('HeapProfiler.collectGarbage');
expect(await page.evaluate(() => Boolean((window as unknown as { __trappedManual: WeakRef<object> }).__trappedManual.deref()))).toBe(true);
expect(await page.evaluate(() => (window as Window & { __trapCalls?: number }).__trapCalls)).toBe(1);
await page.getByRole('button', { name: 'Read', exact: true }).click();
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-wip-state', 'ready');
await cdp.detach();
});
test.describe('without JavaScript', () => {
test.use({ javaScriptEnabled: false });
test('protocol URL retains static reading and does not claim execution', async ({ page }, info) => {
await page.goto(protocolURL(info.project.use.baseURL!, 'sources/'));
await expect(page.locator('[data-selected-content]')).toBeVisible();
await expect(page.locator('[data-mode-switch]')).toBeHidden();
await expect(panel(page)).toBeHidden();
await expect(page.locator('[data-landing-stage]')).not.toHaveAttribute('data-wip-state');
});
});