diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index a321652b..82593f8b 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -110,7 +110,37 @@ jobs: # Register on application-type imports — OR PR #1464) and the lifecycle # engine (ObjectTransitionedEvent) that OpenBuild's repair steps + the # Newman publish step need; `main` predates both. See openbuild#29. - additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"}]' + # + # DOCUDESK IS HERE BECAUSE THREE E2E SPECS ASSERT AGAINST ITS API AND + # THERE WAS NO WAY FOR THEM TO PASS WITHOUT IT. + # + # `tests/e2e/spec-coverage/docudesk-document-templates.spec.ts` drives the + # builder's Documents section: attach a template, preview it, and warn + # when the attached template was deleted. All three go through Docudesk's + # own REST surface (`GET/POST /apps/docudesk/api/templates`, + # `GET /apps/docudesk/api/templates/{id}`, + # `POST /apps/docudesk/api/templates/{id}/preview`). With Docudesk absent + # every one of those is a Nextcloud router 404, and the run said so + # plainly: `[globalSetup] docudesk not installed — template fixtures + # skipped`, then three failures reading `Expected: 200 / Received: 404`. + # That is not an app defect and no code change in this repository could + # have fixed it — the dependency was simply not installed. + # + # `tests/e2e/global-setup.ts` already knows what to do once it IS: it + # configures Docudesk's `template_register`/`template_schema` through + # Docudesk's own `POST /api/settings` and seeds the two template fixtures + # the specs attach to, and it degrades to a log line when Docudesk is + # missing. So this input is the whole fix. + # + # `ref: main` — Docudesk has no `development` branch (verified by listing + # the repo's branches; it publishes `main` and `beta` only), and `main` + # carries every route above. + # + # Only the API is exercised. `additional-apps` clones and runs `composer + # install` but never builds an app's frontend, so Docudesk's own JS bundle + # is absent here — which is fine, because no spec in this suite opens a + # Docudesk page. + additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"},{"repo":"ConductionNL/docudesk","app":"docudesk","ref":"main"}]' enable-sbom: true # Integration Tests (Newman) stays OFF here, deliberately, and unlike the diff --git a/playwright.config.ts b/playwright.config.ts index a7f8bc7c..d8bc22a7 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -38,6 +38,15 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: 1, + // Mirror of tests/e2e/playwright.config.ts. CI does not pick this file up + // (`playwright-test-path: tests/e2e` makes the workflow's first lookup hit + // the config next to the specs), but it IS the documented fallback the + // shared workflow uses when that file is absent — and the job it would run + // under carries `timeout-minutes: 45`. A cancellation at the job cap is not + // a verdict: no tally, and the report is never written so the artifact + // upload finds nothing. Stopping ourselves below the cap fails with numbers + // attached instead. + globalTimeout: 36 * 60 * 1000, globalSetup: './tests/e2e/global-setup.ts', reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list', use: { @@ -56,7 +65,12 @@ export default defineConfig({ // (no Location header is emitted, the page stays on /login). // Specs that need OCS-APIRequest set it on their explicit `request` // calls (e.g. versionRouting.spec.ts, applicationDetailOverview.spec.ts). - trace: 'on-first-retry', + // `retain-on-failure`, not `on-first-retry`: this config's `retries` is + // 1 only on CI and 0 locally, so on a developer box the old setting + // produced no trace for any failure at all — and CI does not use this + // config (see tests/e2e/playwright.config.ts). Keeping the two files in + // step so a local reproduction has the same evidence a CI run does. + trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure', headless: true, diff --git a/src/dialogs/DocumentTemplateAttachmentDialog.vue b/src/dialogs/DocumentTemplateAttachmentDialog.vue index 67430768..d1a0d798 100644 --- a/src/dialogs/DocumentTemplateAttachmentDialog.vue +++ b/src/dialogs/DocumentTemplateAttachmentDialog.vue @@ -21,10 +21,30 @@ @update:open="$emit('update:open', $event)" @closing="onClose">
+

{{ t('openbuild', 'Docudesk is not installed or enabled on this instance. The template list cannot be loaded.') }}

- diff --git a/tests/dialogs/DocumentTemplateAttachmentDialog.spec.js b/tests/dialogs/DocumentTemplateAttachmentDialog.spec.js index 6b82b4a7..b7952424 100644 --- a/tests/dialogs/DocumentTemplateAttachmentDialog.spec.js +++ b/tests/dialogs/DocumentTemplateAttachmentDialog.spec.js @@ -66,3 +66,53 @@ describe('DocumentTemplateAttachmentDialog — preview sanitization', () => { expect(out).toContain('
  • a
  • ') }) }) + +/** + * The two `.ob-document-attach__warn` paragraphs are mutually exclusive claims + * about the same subject: "Docudesk is not installed" and "the template you + * attached no longer exists IN Docudesk". The second is not knowable when the + * first is true. + * + * They were authored as two independent `v-if`s, and both rendered together + * whenever `docudeskAvailable` arrived late — the normal case, since + * PageDesignerHost initialises it to `true` and resolves the real value + * asynchronously. The dialog's `open` watcher then ran its snapshot refresh, + * took a 404 from a route that does not exist, set `templateMissing`, and the + * late `false` stacked the absence message on top. + * + * Playwright saw it as `strict mode violation: locator('.ob-document-attach__warn') + * resolved to 2 elements` in run 31083894467. These assertions pin the + * invariant directly so the next regression is caught in milliseconds by the + * unit suite instead of in a 19-minute browser run. + */ +describe('DocumentTemplateAttachmentDialog — mutually exclusive warnings', () => { + /** + * Mount the dialog and drive it into the both-warnings-eligible state. + * + * @param {boolean} docudeskAvailable Value of the capability prop. + * @return {Promise} The mounted wrapper. + */ + async function warnState(docudeskAvailable) { + const wrapper = mount(DocumentTemplateAttachmentDialog, { + propsData: { docudeskAvailable }, + stubs: baseStubs, + }) + // `templateMissing` is what a 404 from the snapshot refresh sets. + await wrapper.setData({ templateMissing: true }) + return wrapper + } + + it('renders exactly one warning when Docudesk is absent AND a template 404d', async () => { + const wrapper = await warnState(false) + const warns = wrapper.findAll('.ob-document-attach__warn') + expect(warns).toHaveLength(1) + expect(warns[0].text()).toContain('not installed') + }) + + it('renders the deleted-template warning when Docudesk IS available', async () => { + const wrapper = await warnState(true) + const warns = wrapper.findAll('.ob-document-attach__warn') + expect(warns).toHaveLength(1) + expect(warns[0].text()).toContain('no longer exists') + }) +}) diff --git a/tests/e2e/automations-rbac.spec.ts b/tests/e2e/automations-rbac.spec.ts index c93ed1bd..1a7e4ae3 100644 --- a/tests/e2e/automations-rbac.spec.ts +++ b/tests/e2e/automations-rbac.spec.ts @@ -27,6 +27,11 @@ const EDITOR_USER = process.env.NC_RBAC_EDITOR_USER ?? 'rbac-editor' const EDITOR_PASS = process.env.NC_RBAC_EDITOR_PASS ?? 'RbacEditor-1!' const OWNER_USER = process.env.NC_RBAC_OWNER_USER ?? 'rbac-owner' const OWNER_PASS = process.env.NC_RBAC_OWNER_PASS ?? 'RbacOwner-1!' +// Admin credentials for the capability probe below ONLY. Same resolution the +// config uses for `use.httpCredentials`; the tests themselves deliberately run +// as non-admins. +const ADMIN_USER = process.env.NC_ADMIN_USER ?? 'admin' +const ADMIN_PASS = process.env.NC_ADMIN_PASSWORD ?? process.env.NC_ADMIN_PASS ?? 'admin' // The seeded `hello-world` fixture only ever carries a single `production` // version (see lib/Command/SeedHelloWorldFixture.php) — there is no draft // version to author on, so REQ-AUTD-008's "editor authors on draft, gets 403 @@ -46,24 +51,48 @@ const APP_SLUG = process.env.NC_RBAC_TEST_SLUG ?? 'rbac-automations-app' const APP_TITLE_PATTERN = new RegExp(APP_SLUG.replace(/-/g, '.?'), 'i') /** - * Same schema-slug-collision guard as automations.spec.ts (duplicated here - * since each e2e spec file is self-contained) — see that file's - * `automationSchemaIsUsable()` doc comment for the full root-cause writeup. - * On this shared instance the openbuild `automation` schema slug collides - * with a pre-existing, unrelated schema of the same slug from another app, - * so `POST`ing a real automation payload 400s regardless of which - * Application/Version it targets — this blocks BOTH scenarios in this file - * (composing one, and toggling a pre-existing one) identically. + * Is openbuild's `automation` schema readable and shaped as this suite expects? + * + * THIS PROBE USED TO REPORT `false` HERE AND `true` EVERYWHERE ELSE, IN THE + * SAME RUN, AGAINST THE SAME INSTANCE. + * + * It is a copy of `automations.spec.ts`'s helper, and it carried that file's + * reason with it: "the openbuild `automation` schema slug collides with a + * pre-existing schema of the same slug on this shared instance — automation + * CREATE/SAVE 400s regardless of app/version". Run 31083894467 disproves that + * for CI outright. Seven tests in `automations.spec.ts` sit behind the very + * same guard and PASSED, composing and saving real automations end to end + * (REQ-AUTD-002 ×3, -003, -005, -006, -007). Both tests in THIS file skipped. + * + * The discriminator is not the instance, it is the auth context. This describe + * declares `test.use({ storageState: { cookies: [], origins: [] } })` so each + * test can log in as a non-admin — which also makes the `request` fixture + * anonymous. The probe's read of `api/schemas/automation` was therefore + * refused, `resp.ok()` was false, and the helper reported the refusal as a + * fact about the schema. A guard that returns "the feature is broken" when it + * means "I could not look" produces exactly this: a permanent skip with a + * confident, wrong explanation attached. + * + * Two changes. The probe authenticates with the admin credentials the config + * already uses for `httpCredentials`, independently of whatever storageState + * the test is running under. And a NON-OK response is now a thrown error + * rather than a `false`, so "cannot read the schema" fails the run loudly + * instead of silently becoming "the schema is unusable" — the failure mode + * this helper just spent a release exhibiting. * * @param request Playwright APIRequestContext (fixture-provided). - * @return {Promise} True when automation CREATE/SAVE is usable. + * @return {Promise} True when the schema reads back with the expected shape. */ async function automationSchemaIsUsable(request: APIRequestContext): Promise { + const auth = Buffer.from(`${ADMIN_USER}:${ADMIN_PASS}`).toString('base64') const resp = await request.get(`${NEXTCLOUD_URL}/index.php/apps/openregister/api/schemas/automation`, { - headers: { 'OCS-APIRequest': 'true' }, + headers: { 'OCS-APIRequest': 'true', Authorization: `Basic ${auth}` }, }) if (resp.ok() === false) { - return false + throw new Error( + `automationSchemaIsUsable: could not read api/schemas/automation — HTTP ${resp.status()}. ` + + 'This is a broken probe, not a verdict about the schema; it must not be reported as one.', + ) } const schema = await resp.json() return schema?.properties?.trigger?.type === 'object' @@ -93,7 +122,7 @@ test.describe('automation-designer — RBAC (REQ-AUTD-008)', () => { test.use({ storageState: { cookies: [], origins: [] } }) test('editor authors + enables an automation on a non-production (draft) version', async ({ page, request }) => { - test.skip(await automationSchemaIsUsable(request) === false, 'openbuild `automation` schema slug collides with a pre-existing schema of the same slug on this shared instance — automation CREATE/SAVE 400s regardless of app/version; see automationSchemaIsUsable()') + test.skip(await automationSchemaIsUsable(request) === false, 'openbuild `automation` schema does not read back with a `trigger` object property — see automationSchemaIsUsable() for why this must be a real verdict and not a failed lookup') await loginAs(page, EDITOR_USER, EDITOR_PASS) await page.goto(`${NEXTCLOUD_URL}/apps/openbuild/automations`) await page.waitForSelector('.automations-page', { timeout: 20_000 }) @@ -121,7 +150,7 @@ test.describe('automation-designer — RBAC (REQ-AUTD-008)', () => { }) test('editor gets 403 enabling on the production version; owner succeeds', async ({ page, browser, request }) => { - test.skip(await automationSchemaIsUsable(request) === false, 'requires a pre-existing automation on the production version, which cannot be created — openbuild `automation` schema slug collides with a pre-existing schema of the same slug on this shared instance; see automationSchemaIsUsable()') + test.skip(await automationSchemaIsUsable(request) === false, 'openbuild `automation` schema does not read back with a `trigger` object property — see automationSchemaIsUsable() for why this must be a real verdict and not a failed lookup') await loginAs(page, EDITOR_USER, EDITOR_PASS) await page.goto(`${NEXTCLOUD_URL}/apps/openbuild/automations`) await page.waitForSelector('.automations-page', { timeout: 20_000 }) diff --git a/tests/e2e/bootstrap-openbuild.e2e.spec.ts b/tests/e2e/bootstrap-openbuild.e2e.spec.ts index bd8d3ced..b6d8a2e6 100644 --- a/tests/e2e/bootstrap-openbuild.e2e.spec.ts +++ b/tests/e2e/bootstrap-openbuild.e2e.spec.ts @@ -17,14 +17,28 @@ import { test, expect } from '@playwright/test' * - Playwright browsers installed (`npx playwright install --with-deps`). */ test.describe('bootstrap-openbuild hello-world', () => { - // QUARANTINED (Conduction/openbuild#41): openbuild admin UI not functional in this build — builder host blank (BuilderHostView unresolved by nc-vue CnPageRenderer) / no detail/editor/version pages. Re-enable when #41 is fixed. - test.skip('renders the three seeded hello-message objects on the index page', async ({ page }) => { + // UN-QUARANTINED 2026-08-06. The recorded reason — "#41: builder host blank + // (BuilderHostView unresolved by nc-vue CnPageRenderer)" — is the SAME + // sentence builder-host.spec.ts carries above its own un-quarantine note + // saying it "no longer holds". builder-host.spec.ts's first test performs + // this identical journey (goto /apps/openbuild/builder/hello-world, then + // assert the same three seeded titles) and PASSES in CI — measured in run + // 31083894467. One of the two files was simply never revisited. + test('renders the three seeded hello-message objects on the index page', async ({ page }) => { await page.goto('/apps/openbuild/builder/hello-world') // The SPA needs a moment to fetch the manifest and resolve the index page. // The hello-world manifest's index page lists `hello-message` objects with // the title, body and @self.created columns. - await expect(page).toHaveURL(/\/index\.php\/apps\/openbuild\/builder\/hello-world/) + // + // The `/index.php` prefix is OPTIONAL and is not what this test is about. + // Nextcloud emits it only when `htaccess.IgnoreFrontController` is off; + // CI turns it ON (tests/e2e/ci-seed.sh gates on the served page reporting + // `modRewriteWorking:true`), so the pretty form is what the router + // produces there and the old anchored regex could not have matched. The + // app path is still asserted in full — only the webroot style, an + // instance-configuration artifact, is allowed to vary. + await expect(page).toHaveURL(/(\/index\.php)?\/apps\/openbuild\/builder\/hello-world/) // Seed bodies — anchored on the canonical strings written by // SeedHelloWorld::buildSampleMessages(). At minimum the page must diff --git a/tests/e2e/builder-undo-redo.spec.ts b/tests/e2e/builder-undo-redo.spec.ts index 0f059c73..7b647642 100644 --- a/tests/e2e/builder-undo-redo.spec.ts +++ b/tests/e2e/builder-undo-redo.spec.ts @@ -31,6 +31,7 @@ import { test, expect, type Page } from '@playwright/test' import { ensureApp as ensureAppFixture, dismissOverlays, suppressSupportDialog } from './support/appFixture' +import { ensureVersionChain } from './support/versionChain' // PLAYWRIGHT_BASE_URL wins — see tests/e2e/support/baseUrl.ts. import { E2E_BASE_URL as BASE_URL } from './support/baseUrl' @@ -244,18 +245,30 @@ test.describe('builder-undo-redo — page designer (REQ-BUR-001..004)', () => { }) test('REQ-BUR-004: a version switch resets the session history', async ({ page }) => { - // Requires a second seeded ApplicationVersion (e.g. "staging") for - // pw-undo-redo, same precondition class as versionRouting.spec.ts's - // 9.1/9.3 — skip gracefully rather than fail when it isn't seeded. - const versionCheck = await page.request.get( - `${BASE_URL}/index.php/apps/openbuild/api/applications/${APP_SLUG}/versions/staging`, - ).catch(() => null) - if (!versionCheck || versionCheck.status() !== 200) { - test.skip(true, `SKIP: ApplicationVersion "staging" not seeded for ${APP_SLUG} — seed one to exercise this scenario`) - return - } - - await page.goto(`${BASE_URL}/apps/openbuild/builder/${APP_SLUG}/pages`, { waitUntil: 'domcontentloaded' }) + // THIS TEST USED TO SKIP ITSELF, AND THE REASON WAS FALSE. + // + // It probed `GET .../versions/staging`, found nothing, and skipped with + // "ApplicationVersion 'staging' not seeded — seed one to exercise this + // scenario". Nothing in CI was ever going to seed it, so the skip was + // permanent: a guard on a precondition the suite could satisfy for + // itself, phrased as a fact about the environment. + // + // `tests/e2e/support/versionChain.ts::ensureVersionChain()` provisions + // development -> staging -> production on demand and is proven working + // in this same job — versionRouting.spec.ts drives `?_version=staging` + // through it and passes (9.1 / 9.2 / 9.3, run 31083894467). So the + // scenario was drivable all along; it simply never asked. + // + // A DEDICATED SLUG, not `pw-undo-redo`. The other tests in this describe + // open `/pages` with no `?_version=`, so their default-version + // resolution depends on how many versions the app has. Growing a chain + // on the shared slug would leave them running against a different app + // shape on every run AFTER the first — a fixture that changes what its + // neighbours test is worse than the skip it replaces. + const CHAIN_SLUG = 'pw-undo-redo-chain' + await ensureVersionChain(page, CHAIN_SLUG, 'PW Undo Redo Chain') + + await page.goto(`${BASE_URL}/apps/openbuild/builder/${CHAIN_SLUG}/pages`, { waitUntil: 'domcontentloaded' }) await expect(page.locator('.page-designer__left')).toBeVisible({ timeout: 15_000 }) await page.locator('.page-list-editor__add').click() @@ -264,7 +277,7 @@ test.describe('builder-undo-redo — page designer (REQ-BUR-001..004)', () => { await expect(pageDesignerButton(page, 'Undo')).toBeEnabled() await page.goto( - `${BASE_URL}/apps/openbuild/builder/${APP_SLUG}/pages?_version=staging`, + `${BASE_URL}/apps/openbuild/builder/${CHAIN_SLUG}/pages?_version=staging`, { waitUntil: 'domcontentloaded' }, ) await expect(page.locator('.page-designer__left')).toBeVisible({ timeout: 15_000 }) diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index c8e780bd..5e364ff9 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -159,7 +159,26 @@ export default defineConfig({ // HTML page loads — which breaks the browser-based login redirect (no // Location header is emitted, the page stays on /login). Specs that // need it set it on their explicit `request` calls. - trace: 'on-first-retry', + // `on-first-retry` WROTE NOTHING IN THIS JOB, EVER. + // + // This config sets `retries: 0` (see above, deliberately). Playwright + // only records a trace on a RETRY under that mode, and a retry can + // never happen, so the trace file was never produced — while the + // workflow's trace-upload step dutifully ran, found nothing, and said + // so quietly under `if-no-files-found: ignore`. Two settings that are + // each individually defensible combined into an instrument that is + // switched off: every red run since this job existed had a screenshot + // and a video but no trace, which is the one artifact that carries the + // network log and the DOM at each step. + // + // `retain-on-failure` records every test and keeps the trace only for + // the ones that fail — no dependence on retries at all. The output + // lands under `outputDir` (APP_ROOT/test-results), which IS globbed by + // the shared workflow's upload step (it uploads both + // `server/apps//test-results/` and + // `server/apps//tests/e2e/test-results/`), so the traces actually + // leave the runner. + trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure', headless: true, diff --git a/tests/e2e/spec-coverage/application-detail-overview.spec.ts b/tests/e2e/spec-coverage/application-detail-overview.spec.ts index 279917cb..7348dc03 100644 --- a/tests/e2e/spec-coverage/application-detail-overview.spec.ts +++ b/tests/e2e/spec-coverage/application-detail-overview.spec.ts @@ -73,28 +73,48 @@ test.skip('REQ-OBADO-001 — detail page renders the app icon from the Applicati }) // @e2e application-detail-overview::register-widget-deep-links-to-openregister -// QUARANTINED (Conduction/openbuild#41): openbuild admin UI not functional in this build — no application detail / icon / template-clone UI renders. Re-enable when #41 is fixed. -test.skip('REQ-OBADO-006 — Register widget shows an "Open in OpenRegister" link on detail page', async ({ page }) => { +// UN-QUARANTINED 2026-08-06. The #41 reason ("no application detail UI +// renders") is stale: `src/components/applicationDetail/widgets/RegisterWidget.vue` +// exists, renders exactly this affordance, and has been under active +// development through 2026-07-29, while this file was last touched at the +// original 2026-06-04 quarantine commit. The detail route itself is driven and +// asserted by applicationDetailOverview.spec.ts, which passes in CI. +test('REQ-OBADO-006 — Register widget shows an "Open in OpenRegister" link on detail page', async ({ page }) => { // @e2e application-detail-overview::register-widget-deep-links-to-openregister test.skip(!LIVE, 'Requires live dev env with the ApplicationDetailHeader cockpit built — set OPENBUILD_E2E_LIVE=1') await gotoHelloWorldDetail(page) await expect(page.locator('main')).toBeVisible({ timeout: 10_000 }) - // The Register widget should have an "Open in OpenRegister" link - const openRegisterLink = page - .locator('a, button') - .filter({ hasText: /open.*openregister|openregister/i }) - .first() - + // SCOPED TO THE REGISTER WIDGET, not to the page. + // + // The old locator was `page.locator('a, button').filter({ hasText: + // /open.*openregister|openregister/i }).first()` — every anchor and button + // on the page, narrowed by a substring the Nextcloud app menu's own + // "Register" entry and any breadcrumb could satisfy. A `.first()` over a + // page-wide set passes for whatever happens to render first, which means it + // would have kept passing with the Register widget deleted. Anchoring to + // `.ob-register-widget` makes the assertion be about the widget the + // requirement names. + const widget = page.locator('.ob-register-widget') + await expect(widget, 'the Register widget must render on the detail page').toBeVisible({ timeout: 15_000 }) + + const openRegister = widget.getByRole('button', { name: /open in openregister/i }) await expect( - openRegisterLink, - 'Register widget must have an "Open in OpenRegister" link', + openRegister, + 'Register widget must expose the "Open in OpenRegister" affordance', ).toBeVisible({ timeout: 10_000 }) - // The href should point to /apps/openregister/... - const href = await openRegisterLink.getAttribute('href') - if (href) { - expect(href, 'link must point to the OpenRegister app').toMatch(/\/apps\/openregister/) - } + // The affordance navigates by assigning `window.location.href` (see + // RegisterWidget.openInOpenRegister) rather than by rendering an anchor, so + // the target is asserted on the NAVIGATION REQUEST the click issues. An + // `getAttribute('href')` here would be null and — as the old body did — + // skipped by an `if (href)`, i.e. asserted nothing about where it goes. + const navigation = page.waitForRequest( + (req) => req.isNavigationRequest() && /\/apps\/openregister\//.test(req.url()), + { timeout: 10_000 }, + ) + await openRegister.click() + const target = (await navigation).url() + expect(target, 'the deep link must target the OpenRegister app').toMatch(/\/apps\/openregister\/registers\//) }) diff --git a/tests/e2e/spec-coverage/page-designer-ui.spec.ts b/tests/e2e/spec-coverage/page-designer-ui.spec.ts index 52be0452..f4696893 100644 --- a/tests/e2e/spec-coverage/page-designer-ui.spec.ts +++ b/tests/e2e/spec-coverage/page-designer-ui.spec.ts @@ -43,14 +43,16 @@ const PAGE_DESIGNER = (slug: string) => `${BASE}/apps/openbuild/builder/${slug}/ // REQ-OBPDUI-001 — Controlled designer orchestrates pages, menu, undo/redo // --------------------------------------------------------------------------- -// QUARANTINED (Conduction/openbuild#41): the openbuild builder/virtual-app surface is not -// functional in this build — PageDesignerHost mounts but the virtual-app load returns 500 -// ("Failed to load the virtual app: Request failed with status code 500"), so the designer -// panes never render. Same #41 family as openbuild-runtime.spec.ts / page-designer.spec.ts. -// Re-enable when #41 is fixed (UI coverage is referenced by the page-designer-ui spec; the -// host contract is also covered by Vitest unit tests in the meantime). +// UN-QUARANTINED 2026-08-06. The recorded reason was that the virtual-app load +// 500s so the designer panes never render. That is contradicted by this job's +// own results: the NEXT test in this very file opens the same +// `PAGE_DESIGNER('hello-world')` URL, waits for the same `.page-designer-host` +// selector, and passes — as do every page-editor-coverage.spec.ts scenario and +// the docudesk builder specs, all of which drive +// `/apps/openbuild/builder//pages`. The 500 was fixed weeks ago; this +// file was last touched 2026-06-06 and simply never rechecked. // @e2e page-designer-ui::page-designer-renders-three-pane-layout -test.skip('REQ-OBPDUI-001 — page designer route renders the three-pane layout', async ({ page }) => { +test('REQ-OBPDUI-001 — page designer route renders the three-pane layout', async ({ page }) => { // @e2e page-designer-ui::page-designer-renders-three-pane-layout await page.goto(PAGE_DESIGNER('hello-world')) await expect(page.locator('.page-designer-host'), 'designer must load').toBeVisible({ timeout: 15_000 }) @@ -76,19 +78,33 @@ test('REQ-OBPDUI-001 — undo button is disabled when no edits have been made', // REQ-OBPDUI-002 — Route hosts resolve slug + version and persist manifest // --------------------------------------------------------------------------- -// QUARANTINED (Conduction/openbuild#41): builder/virtual-app surface non-functional in this -// build — the host renders a "Failed to load the virtual app: Request failed with status -// code 500" error and a "Version not found" empty-state, so the route cannot resolve a slug. -// Re-enable when #41 is fixed. +// UN-QUARANTINED 2026-08-06 — same stale reason as REQ-OBPDUI-001 above, same +// contradicting evidence: the neighbouring `unknown ?_version` test drives this +// exact route and passes. // @e2e page-designer-ui::page-designer-route-renders-for-valid-slug -test.skip('REQ-OBPDUI-002 — PageDesignerHost route renders for a known slug', async ({ page }) => { +test('REQ-OBPDUI-002 — PageDesignerHost route renders for a known slug', async ({ page }) => { // @e2e page-designer-ui::page-designer-route-renders-for-valid-slug await page.goto(PAGE_DESIGNER('hello-world')) await expect(page.locator('main'), 'main content must load').toBeVisible({ timeout: 15_000 }) - // The page must not be a 404 error page. - const body = await page.textContent('body') - expect(body).not.toMatch(/404|not found/i) + // The route RESOLVED THE SLUG — that is the requirement, and it is asserted + // positively rather than by scanning the whole page for the word "404". + // + // The old body did the latter, and it was an assertion that could not + // distinguish success from failure: `page.textContent('body')` on this route + // includes the Nextcloud chrome and every string the designer renders, so + // any legitimate "not found" copy — the version-not-found empty state the + // very next test asserts EXISTS, for one — would have failed it, while a + // blank designer with no error text would have passed it. Asserting the + // host actually mounted for this slug is both stricter and honest. + await expect( + page.locator('.page-designer-host'), + 'the designer host must mount for a known slug', + ).toBeVisible({ timeout: 15_000 }) + await expect( + page.getByText(/version not found/i), + 'a known slug must NOT land on the version-not-found state', + ).toHaveCount(0) }) // @e2e page-designer-ui::unknown-version-renders-not-found-state diff --git a/tests/e2e/spec-coverage/page-editor-coverage.spec.ts b/tests/e2e/spec-coverage/page-editor-coverage.spec.ts index 1e787372..04c36a24 100644 --- a/tests/e2e/spec-coverage/page-editor-coverage.spec.ts +++ b/tests/e2e/spec-coverage/page-editor-coverage.spec.ts @@ -410,22 +410,52 @@ test('REQ-PEC-006 — Create, configure, save and render a wiki page', async ({ // row happens to render first. // // Bind hello-world's OWN register and schema by slug, not `{ index: 1 }`. - // The register select lists every register on the instance (175 of them - // here) sorted by title, so index 1 resolved to Nextcloud's `directory` + // The register select lists every register on the instance (175 of them on a + // dev box) sorted by title, so index 1 resolved to Nextcloud's `directory` // register, whose `nc-user` schema declares no properties this picker // offers. The Content/Title field rows then rendered a ` once a // register + schema are bound (task 5.1); fall back to free-text input @@ -449,10 +479,10 @@ test('REQ-PEC-006 — Create, configure, save and render a wiki page', async ({ // merely "not empty" — that is what a lossless round-trip means. await expect( reopened.locator('.wiki-page-editor__group-row', { hasText: /^\s*Register\b/ }).locator('select'), - ).toHaveValue('openbuild-hello-world-production') + ).toHaveValue('openbuild') await expect( reopened.locator('.wiki-page-editor__group-row', { hasText: /^\s*Schema\b/ }).locator('select'), - ).toHaveValue('hello-world-production-hello-message') + ).toHaveValue('hello-message') await expect( reopened.locator('.wiki-page-editor__group-row', { hasText: /^\s*Content field\b/ }).locator('select'), ).toHaveValue('body')