Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m2s
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a10e2d-a5ef-7e92-b03d-b1694e915680
141 lines
7.3 KiB
JavaScript
141 lines
7.3 KiB
JavaScript
// Real UI traversal with synthetic protocols; no production hosts or email.
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { chromium } from 'playwright';
|
|
|
|
const base = new URL(process.argv[2]);
|
|
if (base.protocol !== 'http:' || !['localhost', '127.0.0.1'].includes(base.hostname)) {
|
|
throw new Error('Use-case browser runner requires an isolated loopback test server');
|
|
}
|
|
const scenario = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
|
|
assert.equal(scenario.synthetic, true);
|
|
const browser = await chromium.launch({headless: true});
|
|
try {
|
|
const context = await browser.newContext();
|
|
// Fail closed on unexpected integrations. Only the isolated app can be contacted.
|
|
await context.route('**/*', route => {
|
|
if (new URL(route.request().url()).origin === base.origin) return route.continue();
|
|
return route.abort();
|
|
});
|
|
await context.addInitScript(() => {
|
|
window.clipboardMode = 'rich';
|
|
window.clipboardReceipts = [];
|
|
Object.defineProperty(navigator, 'clipboard', {configurable: true, value: {
|
|
write: async items => {
|
|
if (window.clipboardMode !== 'rich') throw new Error('Rich copy blocked');
|
|
const item = items[0];
|
|
window.clipboardReceipts.push({
|
|
html: await (await item.getType('text/html')).text(),
|
|
text: await (await item.getType('text/plain')).text(),
|
|
});
|
|
},
|
|
writeText: async text => {
|
|
if (window.clipboardMode === 'denied') throw new Error('Copy blocked');
|
|
window.clipboardReceipts.push({text});
|
|
},
|
|
}});
|
|
});
|
|
const page = await context.newPage();
|
|
page.setDefaultTimeout(10000);
|
|
const jsErrors = [];
|
|
page.on('pageerror', error => jsErrors.push(error.message));
|
|
const detail = `${base.origin}/app/spaces/${scenario.space_slug}/`;
|
|
const original = `${detail}?page=${scenario.previous_slug}`;
|
|
async function login(role) {
|
|
await page.goto(`${base.origin}/auth/dev-login/`);
|
|
await page.locator('input[name=subject]').fill(scenario[`${role}_subject`]);
|
|
await page.locator('input[name=tenant]').fill(scenario.tenant);
|
|
await page.getByRole('button', {name: 'Establish session'}).click();
|
|
await page.waitForURL('**/app/');
|
|
}
|
|
await login('writer');
|
|
assert.match(await page.locator('main').innerText(), new RegExp(scenario.space_title));
|
|
await page.locator(`a[href="/app/spaces/${scenario.space_slug}/"]`).click();
|
|
await page.getByRole('link', {name: scenario.previous_title, exact: true}).click();
|
|
await page.getByRole('link', {name: 'Duplicate page', exact: true}).click();
|
|
await page.getByLabel('New page slug').fill(scenario.next_slug);
|
|
await page.locator('input[name=title]').fill(scenario.next_title);
|
|
await page.getByRole('button', {name: 'Create duplicate'}).click();
|
|
await page.waitForURL(`**?page=${scenario.next_slug}`);
|
|
assert.match(await page.locator('#page-content').innerText(), /Completed point/);
|
|
await page.getByRole('link', {name: 'Edit page', exact: true}).click();
|
|
assert.equal(await page.getByLabel('Body (markdown)').inputValue(), scenario.previous_body);
|
|
await page.getByLabel('Body (markdown)').fill(scenario.next_body);
|
|
await page.getByRole('button', {name: 'Save page'}).click();
|
|
await page.waitForURL(`**?page=${scenario.next_slug}`);
|
|
await page.reload();
|
|
assert.match(await page.locator('#page-content').innerText(), /Follow-up point/);
|
|
assert.doesNotMatch(await page.locator('#page-content').innerText(), /Completed point/);
|
|
// Execute the actual static script loaded by the real detail page, not a fixture DOM.
|
|
for (const mode of ['rich', 'plain', 'denied']) {
|
|
await page.evaluate(mode => {
|
|
window.clipboardMode = mode;
|
|
window.clipboardReceipts = [];
|
|
}, mode);
|
|
await page.getByRole('button', {name: 'Copy content to clipboard'}).click();
|
|
await page.waitForFunction(() => !document.getElementById('copy-page-content').disabled);
|
|
const result = await page.evaluate(() => ({
|
|
receipts: window.clipboardReceipts,
|
|
status: document.getElementById('copy-content-status').textContent,
|
|
manual: document.getElementById('copy-content-manual').value,
|
|
hidden: document.getElementById('copy-content-manual').hidden,
|
|
selected: document.getElementById('copy-content-manual').selectionEnd,
|
|
}));
|
|
const text = mode === 'denied' ? result.manual : result.receipts[0].text;
|
|
assert.match(text, /Next weekly call/);
|
|
assert.match(text, /- Follow-up point/);
|
|
assert.match(text, / - Nested detail/);
|
|
assert.match(text, /- Another agenda item/);
|
|
assert.match(text, /New notes with emphasis/);
|
|
assert.doesNotMatch(text, /Edit page|Duplicate page|Source:|content-plane|Sign out/);
|
|
if (mode === 'denied') {
|
|
assert.equal(result.receipts.length, 0);
|
|
assert.equal(result.hidden, false);
|
|
assert.equal(result.selected, text.length);
|
|
assert.match(result.status, /Ctrl\+C or Cmd\+C/);
|
|
assert.doesNotMatch(result.status, /Content copied/);
|
|
} else {
|
|
assert.match(result.status, /Content copied/);
|
|
if (mode === 'rich') {
|
|
const html = result.receipts[0].html;
|
|
assert.match(html, /<ul>/);
|
|
assert.match(html, /<strong>emphasis<\/strong>/);
|
|
// A synthetic rich email editor can consume the same exported HTML.
|
|
const email = await context.newPage();
|
|
await email.setContent('<div id="email-draft" contenteditable="true"></div>');
|
|
await email.locator('#email-draft').evaluate((node, html) => {node.innerHTML = html;}, html);
|
|
assert.equal(await email.locator('#email-draft li').count(), 3);
|
|
assert.match(await email.locator('#email-draft').innerText(), /New notes with emphasis/);
|
|
await email.close();
|
|
}
|
|
}
|
|
}
|
|
await page.goto(original);
|
|
assert.match(await page.locator('#page-content').innerText(), /Completed point/);
|
|
assert.doesNotMatch(await page.locator('#page-content').innerText(), /New notes/);
|
|
await login('reader');
|
|
await page.goto(`${detail}?page=${scenario.next_slug}`);
|
|
assert.match(await page.locator('#page-content').innerText(), /New notes/);
|
|
assert.equal(await page.getByRole('link', {name: 'Edit page', exact: true}).count(), 0);
|
|
assert.equal(await page.getByRole('button', {name: 'Copy content to clipboard'}).count(), 1);
|
|
await page.getByRole('button', {name: 'Copy content to clipboard'}).click();
|
|
await page.waitForFunction(() => !document.getElementById('copy-page-content').disabled);
|
|
assert.match(await page.locator('#copy-content-status').innerText(), /Content copied/);
|
|
const csrf = await page.locator('input[name=csrfmiddlewaretoken]').first().inputValue();
|
|
const deniedPost = await page.request.post(`${detail}pages/${scenario.next_slug}/edit/`, {
|
|
form: {csrfmiddlewaretoken: csrf, title: 'Unauthorized change',
|
|
slug: scenario.next_slug, body: 'Unauthorized reader edit'},
|
|
});
|
|
assert.equal(deniedPost.status(), 403);
|
|
assert.match(await deniedPost.text(), /read-only/);
|
|
const denied = await page.goto(`${detail}pages/${scenario.next_slug}/edit/`);
|
|
assert.equal(denied.status(), 403);
|
|
await login('outsider');
|
|
const forbidden = await page.goto(`${detail}?page=${scenario.next_slug}`);
|
|
assert.equal(forbidden.status(), 404);
|
|
assert.equal(await page.locator('#page-content').count(), 0);
|
|
assert.deepEqual(jsErrors, []);
|
|
console.log(`${scenario.usecase_id}: real UI duplication/edit/reopen/clipboard/access passed`);
|
|
} finally {
|
|
await browser.close();
|
|
}
|