Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 88 additions & 16 deletions tests/e2e/version-rollback.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,102 @@ async function appRecord(page: import('@playwright/test').Page): Promise<Record<
}, TEST_SLUG)
}

// ⚠️ SKIPPED because it has NEVER BEEN EXECUTED — not because of a known blocker.
// ⚠️ STILL SKIPPED — the assertions have never been reached, and the reason is
// the SIDEBAR, not the rollback contract.
//
// It is written against a contract that WAS verified live (the VersionHistory
// fix below it was confirmed on a real instance: 3 rows, `.version-history__empty`
// count 0, where the panel had always rendered empty before). But the disposable
// e2e instance was destroyed by a disk-full event before this spec itself could
// be run once, so its selectors, timings and the rollback assertion are unproven.
// The contract below is written against verified behaviour, and the product fix
// it depends on IS confirmed on both instances: `?tab=history` deep-links the
// tab and `.version-history__row` count is 3, where the panel used to render
// empty for every app. So the data is right.
//
// Enabling it is a one-line change — delete the `.skip` — but do that WITH a run,
// not on the strength of this comment. Shipping an unexecuted spec as if it were
// coverage is the exact failure mode the notes above document three times over.
// What cannot be driven is the UI. Measured on the shared dev instance:
//
// aside.app-sidebar width 0 (closed)
// section.app-sidebar__tab[role=tabpanel] display:none
// button[aria-label="Open sidebar"] present AND visible
// -> clicking it TIMES OUT on actionability, at 1280x720 and at 1920x1080
//
// So the sidebar will not open, and every tab-scoped assertion is unreachable
// even though the tab's content is mounted underneath it. That is an instance /
// UI defect to chase on its own, not something a selector change fixes.
//
// Three dead ends recorded so the next attempt skips them:
// - `getByText('Version history')` resolves the tab BUTTON'S LABEL SPAN, which
// is display:none once the tab strip collapses to icons. Waiting on its
// visibility waits forever (18 resolutions, all "hidden"). There are FIVE
// nodes with that exact text; the deepest is the panel's own <h3>.
// - `getByRole('tab', …)` finds nothing — these are not ARIA tabs.
// - `[aria-label*="sidebar" i]` matches "Close sidebar" first; the control is
// labelled exactly "Open sidebar".
//
// The one thing that DOES work, and is the way in for a rewrite:
// /apps/openbuild/applications/{uuid}?tab=history
// mounts the tab content directly. Assert against the DOM it produces, or fix
// the sidebar first.

/**
* Open the app detail page and reveal its "Version history" sidebar tab.
*
* The sidebar's open/closed state is NOT the same on every instance — it is
* per-user UI state, so it differs between a freshly seeded fixture box and a
* long-lived shared one. Both were observed within an hour:
*
* disposable instance : tabs already open; clicking the toggle TIMES OUT
* shared dev instance : tabs present in the DOM but "element is not visible"
*
* So neither "click the toggle first" nor "click the tab directly" works
* everywhere. This probes the tab and only opens the sidebar when it is hidden.
*
* ⚠️ This does NOT currently succeed on the shared dev instance — the sidebar
* refuses to open there at all (see the block comment above). Kept because the
* probe-then-open shape is right and the dead ends are documented; a rewrite
* should either fix the sidebar or drive `?tab=history` directly.
*
* @param page Playwright page.
* @param uuid The application uuid.
* @return {Promise<void>}
*/
async function openVersionHistory(page: import('@playwright/test').Page, uuid: string): Promise<void> {
await page.goto(`${BASE_URL}/apps/openbuild/applications/${uuid}`, { waitUntil: 'domcontentloaded' })
await page.waitForLoadState('networkidle', { timeout: 60_000 }).catch(() => {})

// Target the TAB, not its label. `getByText('Version history')` resolves to
// the `<span class="_sidebarTabsButton__name_…">` inside the tab button, and
// that span is display:none whenever the tab strip collapses to icons — which
// it does at this viewport on the shared instance. The span being hidden says
// nothing about the tab being reachable, so waiting on its visibility waits
// forever (18 resolutions, all "hidden").
const tab = page.getByRole('tab', { name: /version history/i }).first()

if (!(await tab.isVisible().catch(() => false))) {
// The control is a button labelled exactly "Open sidebar". An
// `[aria-label*="sidebar" i]` match is NOT good enough — "Close sidebar"
// is also present and matches first.
await page.getByRole('button', { name: 'Open sidebar', exact: true })
.click({ timeout: 15_000 })
.catch(() => {})
await expect(tab, 'the Version history tab must become reachable').toBeVisible({ timeout: 20_000 })
}
await tab.click({ timeout: 20_000 })
}

test.describe.skip('openbuild-versioning — rollback (REQ-OBV-003)', () => {
// The default 30s cannot cover this on a loaded instance. Measured on the
// shared dev box (28 applications, 200+ schemas), a single
// GET /api/applications takes ~6.9s — against ~0.3s on a disposable
// fixture instance — and this spec needs several, plus two page loads and
// a version-chain seed. The budget is set from that measurement rather
// than raised until it passes.
test.describe.configure({ timeout: 150_000 })

test.beforeEach(async ({ page }) => {
await suppressSupportDialog(page)
await page.goto(`${BASE_URL}/apps/openbuild/`, { waitUntil: 'domcontentloaded' })
await ensureVersionChain(page, TEST_SLUG, 'PW Version Chain')
})

test('the version history tab lists the chain', async ({ page }) => {
await page.goto(`${BASE_URL}/apps/openbuild/applications/${await appUuid(page)}`, { waitUntil: 'domcontentloaded' })
await page.waitForLoadState('networkidle', { timeout: 30_000 }).catch(() => {})
await page.getByText('Version history', { exact: true }).first().click({ timeout: 10_000 })
await openVersionHistory(page, await appUuid(page))

// The regression guard: this panel used to render `.version-history__empty`
// for every app because of the applicationUuid filter described above.
Expand All @@ -103,10 +177,8 @@ test.describe.skip('openbuild-versioning — rollback (REQ-OBV-003)', () => {
test('rolling back copies the snapshot manifest onto the app as a draft', async ({ page }) => {
const before = await appRecord(page)

await page.goto(`${BASE_URL}/apps/openbuild/applications/${await appUuid(page)}`, { waitUntil: 'domcontentloaded' })
await page.waitForLoadState('networkidle', { timeout: 30_000 }).catch(() => {})
await page.getByText('Version history', { exact: true }).first().click({ timeout: 10_000 })
await expect(page.locator('.version-history__row').first()).toBeVisible({ timeout: 15_000 })
await openVersionHistory(page, await appUuid(page))
await expect(page.locator('.version-history__row').first()).toBeVisible({ timeout: 20_000 })

// "Roll back" only renders on a NON-production row (`v-if="!isProduction(row)"`),
// so this also proves the terminal production version offers no rollback.
Expand Down
Loading