|
| 1 | +import { test, expect } from '@playwright/test'; |
| 2 | +import { loginAsAdmin, waitForAppReady } from './fixtures/admin-login'; |
| 3 | + |
| 4 | +/** |
| 5 | + * End-to-end regression for the Event Scripts create flow — the path that |
| 6 | + * was broken for the Triskele customer on 2026-04-22. |
| 7 | + * |
| 8 | + * Covers the entire chain that broke during that fire-drill: |
| 9 | + * - form opens without a stuck spinner (loading-spinner race fix) |
| 10 | + * - Service dropdown populates with raw service names (/system/event |
| 11 | + * services_only fast path + case-interceptor exemption) |
| 12 | + * - Script Type dropdown populates (response lookup uses raw key) |
| 13 | + * - Script Method dropdown populates |
| 14 | + * - Save produces a 201/200, not a 400 "No record(s) detected" |
| 15 | + */ |
| 16 | +test.describe('Event Scripts — create flow', () => { |
| 17 | + // TODO: this spec navigates via hash-routing, which doesn't reliably |
| 18 | + // trigger the lazy-loaded route resolver on `page.goto('#/event-scripts')`. |
| 19 | + // Need to either click through the sidenav (resolved reliably by role |
| 20 | + // selectors once the welcome/license flow is accounted for) or switch |
| 21 | + // the app to path-based routing. Keeping the spec scaffolded so the |
| 22 | + // intent is visible and fixing the navigation is small follow-up work. |
| 23 | + test.fixme('create an event script end-to-end', async ({ page }) => { |
| 24 | + await loginAsAdmin(page); |
| 25 | + await waitForAppReady(page); |
| 26 | + |
| 27 | + // Navigate into Event Scripts. Use a hard reload of the full URL so |
| 28 | + // the hash router definitely re-evaluates (in-app hash-only changes |
| 29 | + // from page.goto don't always trigger lazy-loaded route resolvers). |
| 30 | + await page.goto('/dreamfactory/dist/#/event-scripts', { |
| 31 | + waitUntil: 'networkidle', |
| 32 | + timeout: 15_000, |
| 33 | + }); |
| 34 | + |
| 35 | + // Click the + button. DfManageTable renders a mat-mini-fab with class |
| 36 | + // .save-btn for the "New Entry" action. |
| 37 | + const addBtn = page.locator('button.save-btn').first(); |
| 38 | + await expect(addBtn).toBeVisible({ timeout: 15_000 }); |
| 39 | + await addBtn.click(); |
| 40 | + |
| 41 | + // Spinner must clear. If the loading-spinner race is back, this |
| 42 | + // waits forever until the test timeout. |
| 43 | + await expect( |
| 44 | + page.locator('mat-progress-spinner, .loading-spinner') |
| 45 | + ).toHaveCount(0, { timeout: 20_000 }); |
| 46 | + |
| 47 | + // Service dropdown |
| 48 | + const serviceSelect = page.getByLabel(/^service$/i); |
| 49 | + await serviceSelect.click(); |
| 50 | + // `db` is always present in a dev-docker setup; any underscore service |
| 51 | + // also exercises the case-interceptor exemption. |
| 52 | + const dbOption = page.getByRole('option', { name: /^db$/ }); |
| 53 | + await expect(dbOption).toBeVisible({ timeout: 10_000 }); |
| 54 | + await dbOption.click(); |
| 55 | + |
| 56 | + // Script Type dropdown must populate. Empty = the pre-fix bug. |
| 57 | + const typeSelect = page.getByLabel(/script\s*type/i); |
| 58 | + await typeSelect.click(); |
| 59 | + const firstType = page.getByRole('option').first(); |
| 60 | + await expect(firstType).toBeVisible({ timeout: 10_000 }); |
| 61 | + // Pick a type without {table_name} templating so selectedRoute() |
| 62 | + // alone produces a valid completeScriptName. |
| 63 | + const dbType = page.getByRole('option', { name: /^db$/ }); |
| 64 | + await dbType.click(); |
| 65 | + |
| 66 | + // Script Method |
| 67 | + const methodSelect = page.getByLabel(/script\s*method/i); |
| 68 | + await methodSelect.click(); |
| 69 | + const firstMethod = page |
| 70 | + .getByRole('option', { name: /\.get\.pre_process$/ }) |
| 71 | + .first(); |
| 72 | + await expect(firstMethod).toBeVisible({ timeout: 10_000 }); |
| 73 | + await firstMethod.click(); |
| 74 | + |
| 75 | + // Grab the method text we picked so we can clean up after save. |
| 76 | + // At this point completeScriptName === selectedRouteItem. |
| 77 | + |
| 78 | + // Activate the script and add a one-line body. |
| 79 | + const isActiveToggle = page.getByLabel(/active/i).first(); |
| 80 | + await isActiveToggle.click(); |
| 81 | + |
| 82 | + // Save |
| 83 | + const saveBtn = page.getByRole('button', { name: /^save$/i }); |
| 84 | + const savePromise = page.waitForResponse( |
| 85 | + r => |
| 86 | + r.url().includes('/api/v2/system/event_script') && |
| 87 | + r.request().method() === 'POST' |
| 88 | + ); |
| 89 | + await saveBtn.click(); |
| 90 | + const resp = await savePromise; |
| 91 | + expect( |
| 92 | + [200, 201].includes(resp.status()), |
| 93 | + `expected save to succeed, got ${resp.status()}: ${await resp.text()}` |
| 94 | + ).toBe(true); |
| 95 | + }); |
| 96 | +}); |
0 commit comments