Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit 8718b91

Browse files
committed
test(schema-editor): add Newman + Playwright e2e + lint pass
- Newman collection openbuilt-schema-editor.postman_collection.json exercises POST/PUT/DELETE schemas + lifecycle CRUD under openbuilt-{slug}. Skips chain-#3-dependent requests via probe-and- pm.test.skip() so the collection passes when the OR runtime schema API isn't deployed yet. - Playwright tests/e2e/schema-designer.spec.ts walks login -> create virtual app -> add schema -> add 2 fields -> save -> edit -> delete with confirm. Marks parts of cross-spec journey #2. - Rename Promise resolver param r -> resolve in SchemaDesigner.spec to satisfy promise/param-names.
1 parent 1c173b0 commit 8718b91

3 files changed

Lines changed: 482 additions & 15 deletions

File tree

‎tests/e2e/schema-designer.spec.ts‎

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 ConductionNL / OpenBuilt Contributors
3+
* SPDX-License-Identifier: EUPL-1.2
4+
*
5+
* Playwright end-to-end spec for the OpenBuilt schema designer
6+
* (spec #4 — openbuilt-schema-editor). Marks parts of cross-spec
7+
* journey #2 (create-virtual-app → design-schema → edit-page →
8+
* publish-version-1).
9+
*
10+
* Flow under test:
11+
* 1. Log in as admin (NC_ADMIN_USER / NC_ADMIN_PASS env vars).
12+
* 2. Open the OpenBuilt app and create a virtual application
13+
* (slug `pw-hello`, title "PW Hello"). The smoke spec from
14+
* bootstrap-openbuilt already exercises this part; we re-use
15+
* the same UX to land on the application page.
16+
* 3. Navigate to that virtual app's Schemas tab —
17+
* /builder/pw-hello/schemas.
18+
* 4. Click "Add schema" → fill slug `message` + title "Message" →
19+
* submit. Assert the new row appears in the list.
20+
* 5. Open the schema → add two fields (`subject`, `body`) →
21+
* Save. Reload; assert both fields persist.
22+
* 6. Open the schema again → edit the title → Save → assert the
23+
* list row reflects the new title.
24+
* 7. Delete the schema via the per-row Delete action; assert the
25+
* confirm dialog appears and only fires deletion on
26+
* confirmation. The schema row should disappear from the list.
27+
*
28+
* Runs against a live Nextcloud at NC_BASE_URL (default
29+
* http://localhost:8080) with the OpenBuilt app installed AND chain
30+
* spec #3 (openregister-runtime-schema-api) deployed. Until chain #3
31+
* lands, the schema CRUD calls return 404 and the test will fail at
32+
* step 4 — this is the expected gating behaviour documented in spec
33+
* tasks.md §7.
34+
*
35+
* The spec is intentionally self-contained — it uses
36+
* `page.request.post('/login')` to authenticate inside the test so
37+
* the suite can be run via `npx playwright test tests/e2e` without a
38+
* shared global-setup file (chain #3 + this spec land together).
39+
*/
40+
41+
import { test, expect } from '@playwright/test'
42+
43+
const BASE_URL = process.env.NC_BASE_URL ?? 'http://localhost:8080'
44+
const ADMIN_USER = process.env.NC_ADMIN_USER ?? 'admin'
45+
const ADMIN_PASS = process.env.NC_ADMIN_PASS ?? 'admin'
46+
47+
const APP_SLUG = 'pw-hello'
48+
const SCHEMA_SLUG = 'message'
49+
50+
test.describe('OpenBuilt Schema Designer — end-to-end (REQ-OBSD-001..008)', () => {
51+
test.beforeEach(async ({ page }) => {
52+
// Log in via the Nextcloud /login form. Cookies persist for the
53+
// remainder of this test's context.
54+
await page.goto(`${BASE_URL}/login`, { waitUntil: 'domcontentloaded' })
55+
await page.fill('input[name="user"]', ADMIN_USER)
56+
await page.fill('input[name="password"]', ADMIN_PASS)
57+
await Promise.all([
58+
page.waitForLoadState('networkidle'),
59+
page.click('button[type="submit"]'),
60+
])
61+
})
62+
63+
test('create virtual app → add schema → add 2 fields → save → edit → delete', async ({ page }) => {
64+
// Step 1 — open the OpenBuilt app at the Applications page.
65+
await page.goto(`${BASE_URL}/index.php/apps/openbuilt/applications`, {
66+
waitUntil: 'domcontentloaded',
67+
})
68+
69+
// Step 2 — create the virtual app via the existing manifest editor
70+
// (bootstrap-openbuilt spec covers UX). We do this via the
71+
// application editor's primary action; if the app already exists
72+
// from a previous run, the test continues idempotently.
73+
const addAppButton = page.getByRole('button', { name: /add application/i })
74+
if (await addAppButton.isVisible().catch(() => false)) {
75+
await addAppButton.click()
76+
await page.getByLabel(/slug/i).fill(APP_SLUG)
77+
await page.getByLabel(/title/i).fill('PW Hello')
78+
await page.getByRole('button', { name: /save|create/i }).click()
79+
await page.waitForLoadState('networkidle')
80+
}
81+
82+
// Step 3 — navigate to the Schema Designer for this virtual app.
83+
await page.goto(`${BASE_URL}/index.php/apps/openbuilt/builder/${APP_SLUG}/schemas`, {
84+
waitUntil: 'domcontentloaded',
85+
})
86+
87+
// Wait for the panel to render — either the empty state or a row list.
88+
const panel = page.locator('.openbuilt-schema-list')
89+
await expect(panel).toBeVisible({ timeout: 10_000 })
90+
91+
// Step 4 — add a schema named `message`.
92+
await page.getByRole('button', { name: /add schema/i }).first().click()
93+
await page.getByLabel(/slug/i).fill(SCHEMA_SLUG)
94+
await page.getByLabel(/title/i).fill('Message')
95+
await page.getByRole('button', { name: /add schema|save/i }).last().click()
96+
97+
// Detail view loads; back button is visible.
98+
await expect(page.getByRole('button', { name: /back to schemas/i })).toBeVisible({
99+
timeout: 10_000,
100+
})
101+
102+
// Step 5 — add two fields and Save.
103+
const addFieldButton = page.getByRole('button', { name: /add field/i })
104+
await addFieldButton.click()
105+
// The first row's Name input — there is only one field so far.
106+
await page.getByLabel('Name', { exact: false }).first().fill('subject')
107+
108+
await addFieldButton.click()
109+
await page.getByLabel('Name', { exact: false }).nth(1).fill('body')
110+
111+
await page.getByRole('button', { name: /^save$/i }).click()
112+
// Expect either the toast or the saving state to settle.
113+
await page.waitForLoadState('networkidle')
114+
115+
// Reload and verify persistence.
116+
await page.reload({ waitUntil: 'domcontentloaded' })
117+
await expect(page.getByLabel('Name', { exact: false }).first()).toHaveValue('subject')
118+
await expect(page.getByLabel('Name', { exact: false }).nth(1)).toHaveValue('body')
119+
120+
// Step 6 — edit the title and save.
121+
await page.getByLabel(/title/i).first().fill('Message v2')
122+
await page.getByRole('button', { name: /^save$/i }).click()
123+
await page.waitForLoadState('networkidle')
124+
125+
// Back to the list — the row should reflect the new title.
126+
await page.getByRole('button', { name: /back to schemas/i }).click()
127+
await expect(page.locator('.openbuilt-schema-list__rows')).toContainText('Message v2')
128+
129+
// Step 7 — delete the schema via the per-row action; confirm in
130+
// the dialog (REQ-OBSD-008).
131+
const row = page
132+
.locator('.openbuilt-schema-list__row')
133+
.filter({ hasText: SCHEMA_SLUG })
134+
await row.getByRole('button', { name: /delete/i }).click()
135+
// Confirm dialog asks for explicit confirmation.
136+
const confirmButton = page
137+
.locator('.delete-schema-dialog, [role="dialog"]')
138+
.getByRole('button', { name: /delete|confirm/i })
139+
await confirmButton.click()
140+
141+
// Row disappears from the list.
142+
await expect(
143+
page.locator('.openbuilt-schema-list__row').filter({ hasText: SCHEMA_SLUG }),
144+
).toHaveCount(0, { timeout: 10_000 })
145+
})
146+
})

0 commit comments

Comments
 (0)