From ed7502e277be9b33744192229828996956b29a56 Mon Sep 17 00:00:00 2001 From: Scott Cazan Date: Thu, 26 Feb 2026 23:03:35 +0100 Subject: [PATCH 1/2] Add in authUserId for feature flagging (#691) Adds in a ID for feature flagging without having to store any user emails in analytics. --- services/api/src/middlewares/withAnalytics.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/api/src/middlewares/withAnalytics.ts b/services/api/src/middlewares/withAnalytics.ts index 7cc520fc7..9eba6ab07 100644 --- a/services/api/src/middlewares/withAnalytics.ts +++ b/services/api/src/middlewares/withAnalytics.ts @@ -23,6 +23,7 @@ const withAnalytics: MiddlewareBuilderBase = async ({ try { // We are only identifying One Project users by email, matching frontend logic const properties: Record = {}; + properties.authUserId = user.id; if (posthogSessionId) { properties.$session_id = posthogSessionId; From d6e641113507591f8d8e7e5abca6152c5fc9f436 Mon Sep 17 00:00:00 2001 From: Valentino Hudhra <2587839+valentin0h@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:14:31 +0100 Subject: [PATCH 2/2] Add e2e test for creating a decision process instance (#674) --- .../e2e/tests/create-process-instance.spec.ts | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 tests/e2e/tests/create-process-instance.spec.ts diff --git a/tests/e2e/tests/create-process-instance.spec.ts b/tests/e2e/tests/create-process-instance.spec.ts new file mode 100644 index 000000000..eac8b0533 --- /dev/null +++ b/tests/e2e/tests/create-process-instance.spec.ts @@ -0,0 +1,192 @@ +import { expect, test } from '../fixtures/index.js'; + +// Use a wider viewport so the participant preview panel (xl:block >= 1280px) is visible. +test.use({ viewport: { width: 1440, height: 900 } }); + +test.describe('Create Process Instance', () => { + test('can create a decision process and reach launch-ready state', async ({ + authenticatedPage, + }) => { + // 1. Navigate to the decisions create page (server-side creates a draft + // instance from the seeded template and redirects to the editor) + await authenticatedPage.goto('/en/decisions/create'); + + // 2. Wait for the process builder editor to load + await expect(authenticatedPage.getByText('Process Overview')).toBeVisible({ + timeout: 30000, + }); + + // ── Step 1: General – Overview ────────────────────────────────────── + + // 3. Select the steward (current user — the first option in the dropdown) + const stewardSelect = authenticatedPage.getByLabel( + 'Who is stewarding this process?', + ); + await stewardSelect.click(); + const stewardOption = authenticatedPage + .getByRole('listbox') + .getByRole('option') + .first(); + await expect(stewardOption).toBeVisible({ timeout: 5000 }); + await stewardOption.click(); + + // 4. Fill in the process name + await authenticatedPage.getByLabel('Process Name').fill('E2E Test Process'); + + // 5. Fill in the description + await authenticatedPage + .getByLabel('Description') + .fill('A process created by E2E tests'); + + // Wait for the debounced auto-save (1000ms) to flush to the API. + // For draft instances, the store initializer always prefers server data, + // so we reload after each section to get fresh server state. + // eslint-disable-next-line playwright/no-wait-for-timeout + await authenticatedPage.waitForTimeout(2000); + await authenticatedPage.reload({ waitUntil: 'networkidle' }); + + // ── Step 1: General – Phases ──────────────────────────────────────── + + // 6. Navigate to the Phases section + const phasesTab = authenticatedPage.getByRole('tab', { name: 'Phases' }); + await expect(phasesTab).toBeVisible({ timeout: 15000 }); + await phasesTab.click(); + + await expect( + authenticatedPage.getByText( + 'Define the phases of your decision-making process', + ), + ).toBeVisible({ timeout: 10000 }); + + // 7. Fill each phase's required fields. + // The seeded template has phases rendered as collapsed accordions. + // We expand each one, fill headline/description/endDate, then move on. + const now = new Date(); + const phaseAccordions = authenticatedPage.locator( + '[class*="group/accordion-item"]', + ); + const phaseCount = await phaseAccordions.count(); + + for (let i = 0; i < phaseCount; i++) { + const phase = phaseAccordions.nth(i); + + // Click the accordion trigger to expand (the chevron button) + await phase.locator('button[slot="trigger"]').click(); + + // Wait for the phase content to be visible + const headlineField = phase.getByLabel('Headline'); + await expect(headlineField).toBeVisible({ timeout: 5000 }); + + // Fill phase name + await phase.getByLabel('Phase name').fill(`Phase ${i + 1}`); + + // Fill headline + await headlineField.fill(`Phase ${i + 1} headline`); + + // Fill description + await phase + .getByLabel('Description') + .fill(`Description for phase ${i + 1}`); + + // Set end date — consecutive months from now + const endDate = new Date(now.getFullYear(), now.getMonth() + i + 1, 15); + const formatted = [ + String(endDate.getMonth() + 1).padStart(2, '0'), + String(endDate.getDate()).padStart(2, '0'), + endDate.getFullYear(), + ].join('/'); + + const endDateInput = phase.getByLabel('End date'); + await endDateInput.fill(formatted); + await endDateInput.press('Enter'); + } + + // Wait for phases auto-save to flush, then reload + // eslint-disable-next-line playwright/no-wait-for-timeout + await authenticatedPage.waitForTimeout(2000); + await authenticatedPage.reload({ waitUntil: 'networkidle' }); + + // ── Step 1: General – Proposal Categories ─────────────────────────── + + // 8. Navigate to the Proposal Categories section + const categoriesTab = authenticatedPage.getByRole('tab', { + name: 'Proposal Categories', + }); + await expect(categoriesTab).toBeVisible({ timeout: 15000 }); + await categoriesTab.click(); + + await expect( + authenticatedPage.getByText('Proposal Categories').first(), + ).toBeVisible({ timeout: 10000 }); + + // 9. Create one category + await authenticatedPage + .getByRole('button', { name: 'Create first category' }) + .click(); + + await authenticatedPage.getByLabel('Shorthand').fill('Education'); + await authenticatedPage + .getByLabel('Full description') + .fill('Expand access to quality education in underserved communities'); + + await authenticatedPage + .getByRole('button', { name: 'Add category' }) + .click(); + + // Verify the category appears in the list + await expect( + authenticatedPage.getByText('Education', { exact: true }), + ).toBeVisible(); + + // Wait for category auto-save to flush + // eslint-disable-next-line playwright/no-wait-for-timeout + await authenticatedPage.waitForTimeout(1500); + + // ── Step 2: Proposal Template ─────────────────────────────────────── + + // 10. Navigate to the Proposal Template step + const templateTab = authenticatedPage.getByRole('tab', { + name: 'Proposal Template', + }); + await templateTab.click(); + + await expect( + authenticatedPage.getByText('Proposal template').first(), + ).toBeVisible({ timeout: 10000 }); + + // 11. Enable the Budget field in the template + await authenticatedPage + .getByRole('button', { name: 'Show in template?' }) + .click(); + + // Verify the budget config expanded (Currency select should appear) + await expect(authenticatedPage.getByLabel('Currency')).toBeVisible({ + timeout: 5000, + }); + + // 12. Verify the participant preview shows the budget field + // The preview renders an "Add budget" button when budget is enabled + await expect( + authenticatedPage.getByText('Participant Preview'), + ).toBeVisible({ timeout: 5000 }); + + await expect( + authenticatedPage.getByRole('button', { name: 'Add budget' }), + ).toBeVisible({ timeout: 5000 }); + + // ── Final: Verify Launch Process button is enabled ────────────────── + + // 13. Wait for template auto-save to flush, then reload so the store + // initializer picks up all saved data from the server + // eslint-disable-next-line playwright/no-wait-for-timeout + await authenticatedPage.waitForTimeout(2000); + await authenticatedPage.reload({ waitUntil: 'networkidle' }); + + // 14. Verify the Launch Process button is enabled (not disabled) + const launchButton = authenticatedPage.getByRole('button', { + name: 'Launch Process', + }); + await expect(launchButton).toBeVisible({ timeout: 15000 }); + await expect(launchButton).toBeEnabled({ timeout: 15000 }); + }); +});