fix(e2e): get the stage Playwright suite green after the UI redesigns

Merged
EV
18d ago
+5081-2506

PR


What and why

Stage d365fbe9e6 (run 40364004456) was the first Playwright run in weeks to get past "Build API + Web". The four e2e shards then failed 46 of ~80 scenarios (10 + 12 + 10 + 14). This PR fixes the e2e suite so those scenarios test the same behaviour against today's UI.

Root causes, by each scenario's first failure:

Root causeScenarios
Stale selector: the markup changed in a UI redesign since the suite was last green33
Changed flow: the image-URL input became a file-upload avatar (#10336)11
Test data or order dependency: manage-user-invites assumed another shard's invite1
Catalog drift: roles-permissions toggle count and stale permission lists1
App regression0

Each scenario failed at its first broken step, and the steps behind that one had never run against the new UI. For example, 14 scenarios stopped in addProject before reaching their own steps. So every step of each scenario was checked against the current templates on origin/develop, not only the first failure. Further stale selectors and changed flows turned up behind the first failure, and they are fixed too. The evidence is the stage trace DOM snapshots and screenshots, the templates, and the commit that changed the markup.

What is not changed:

  • No scenario, step or assertion was deleted or skipped, and no .skip/fixme was added. The .feature files are untouched and every step file defines the same steps.
  • App code is untouched: the investigation found no app regression to fix.

Where a flow changed, the step now uses the equivalent control:

  • Picture: the step uploads a PNG through <ngx-image-uploader> instead of typing an image URL.
  • Type-to-confirm deletes: the step types the organization name or invitee email before confirming.

Several steps also got stricter, so a failed save now fails where it happens. Before, these could pass without saving anything:

  • the project, contact, client and organization forms must close (or the row must exist) after save;
  • the dialogs must close after send/create;
  • addOrganization re-enables its existing-row check, which was commented out.

Failing scenario → root cause → fix

Shard numbers are from run 40364004456. Commits referenced are on develop.

Scenario (shard)First failureRoot cause (evidence)Fix
add-organization (1), delete-organization (1), manage-organization (3), register (4)ga-country ng-select never attached after the first "Next"Stale selector. #10608 (ffc762ee8a) moved the step buttons out of nb-stepper into nb-card-footer.dialog-footer and relabelled them Continue / Skip / Create organization. The helper searched nb-stepper for Next/Add, found nothing and returned false, so the stepper never left step 1. Confirmed in the trace DOM.New advanceStepForm: clicks the footer's primary button once it is enabled, then checks the step indicator moved or the dialog closed. Delete is type-to-confirm since #10616 (a19de169d1), so the step types the name. Grids are filtered to the new organization. manage-organization no longer renames whichever row is last.
organization-public-page (3)button[type="submit"] (Next)Stale selector (same #10608 footer). Also: the share route needs the organization id (/share/organization/:profileLink/:organizationId), and in ng-select 24 the language combobox role sits on the inner input.Uses advanceStepForm. Navigates to the full share route, and uses ngx-language-selector ng-select for the language.
organization-projects (3) and, via addProject: add-tasks (1), clients (1), contacts-leads (1), customers (1), manage-employees (3), estimates (2), expenses (2), invoices (2), payments (4), sales-estimates (4), sales-invoices (4), teams-tasks (4), timesheets (4)span.tab-text .nth(3) (.nth(1))Stale selector. #11177 (954aac863c) replaced the form's nb-tabset with its own tablist, button[role=tab]#project-form-tab-<id>, and moved Save to div.form-footer > button[status=primary].clickTabButton opens a tab by id: budget, settings, and old indices map to the same tabs. It checks aria-selected. Save waits until enabled and for the redirect; verifyProjectFormClosed proves the project was saved.
↳ downstream of addProject(never reached on stage)Stale selectors, one group per area (rows below)(fixes in the rows below)
↳ clients, contacts-leads, customersContact wizard #10308 (e9e6bfd9fa): members picker is now ng-select.members-select; step buttons are .wizard-actions button.green; the panel-closing click target became the avatar aside.Members picked through the new picker; the wizard must close after Save.
↳ estimates, invoices, sales-estimates, sales-invoicesThe View button became action primary (5536f7b711), and the old edit selector then hit View. The contact select is wrapped in div.form-group (#10182). The actions popover now closes only on a click inside it.Component-scoped selectors; toolbar buttons found by icon; waits on the real outcome instead of fixed sleeps.
↳ manage-employeesInvite dialog #10761 (699b3c7187), including the type-to-confirm delete. Account Save and language select #10115. Manage Invites #10113.Updated selectors; the step types the invitee email before confirming.
↳ add-tasks, teams-tasks, timesheetsOnce a project exists, the header renders its own ga-project-selector, which the bare selector matched first.Pickers scoped to their dialog or timer. addTask waits for POST /api/tasks.
↳ expenses, paymentsBare div.ng-option click could hit the loading placeholder.Option picked through selectNgOption; the payments note is unique per run and rows are selected by text.
appointments (1), approval-request (1), edit-employee (2), goals-kpi (2), human-resources (2), proposals (4), time-off (4)#inputImageUrl input[type="text"] in addEmployeeChanged flow. #10336 (a73de1d121) replaced the image-URL input with the <ngx-image-uploader> avatar, and moved every dialog action into nb-card-footer.dialog-footer (Continue, Continue, Create employees) with a header step bar.The step uploads the picture through the avatar (new uploadImageThroughUploader helper). Each step change is confirmed on the step bar, and Create must close the dialog.
↳ downstream of addEmployeeBreakdown rows and identity edit button (a15f8536cd, 33e3c09f30). Membership tabs (17125da2e8). Appointments header and pick-employee footer (d58dd759cb).Updated selectors. goals-kpi, proposals and time-off use names unique per run, filter the grid, and wait for the dialog to close.
add-user (1), edit-user (2), remove-user (4)[placeholder="Image"]Changed flow (#10336, same basic-info form)Avatar upload. Confirm button scoped to ga-user-mutation; the dialog must close.
manage-interviews (3)ga-candidate-mutation #inputImageUrl … in addCandidateChanged flow (#10336). Behind it, #10359 (75957d91aa) rebuilt the interview dialog: Next and Save are in the footer, and an interviewer is required.Avatar upload; footer Next and Save; an interviewer is picked. The title is unique per run because a duplicate title disables Next.
login "Log out" (3), edit-profile (2), my-tasks-tracked-in-timesheets (3)p[routerlink="/auth/logout"]Stale selector. #10044 (01171bba2e) made Sign out an <a class="panel-row" routerlink="/auth/logout">. The stage DOM shows the panel open with that link rendered.gauzy-user-menu a[routerlink="/auth/logout"]. In my-tasks, the pickers are scoped, and the timesheet check that swallowed its own failure is a real assertion again.
candidates (1), manage-candidates-invites (3), invite-user (2)nb-card-footer … button[status="success"]Stale selector. #10761 (699b3c7187) redesigned the invite dialog: the send button is nb-card-footer.invite-footer .actions button[status=primary]. At failure the form was valid and Send was enabled.New selector, which waits until enabled; the dialog must close. Invite delete is type-to-confirm.
manage-user-invites (3)tr.angular2-smart-row not visible; the grid said "You have not invited any users."Test data / order. Each shard seeds a fresh DB with DEMO=false, which creates no invites, and invite-user runs in another shard.The scenario creates its own invite, filters the grid by its email, and types the email into the type-to-confirm delete dialog.
manage-interviews-criterions (3)span.col-2 > button[status="success"]Stale selector. #10359 (75957d91aa) moved both panels to ga-candidate-criterion-card.Rescoped selectors; the same assertions.
organization-vendors (4)vendor row via div.float-left.text-truncateStale selector. .float-left was removed in #10160 (c53db3d7c0). The trace DOM shows the saved row as div.ga-name-cell "Dominos Pizza", so the save worked.div.ga-name-cell; names unique per run; rows selected by name.
file-storage (2)nb-card-header > h6 "S3 Configuration"Stale selector (#10481, 367113adaf).provider-heading > h6; submit button in .form-actions
goals-general-settings (2).settings-title captionStale selector (#10227, 33d464dba1): five captions are span.settings-label:is(.settings-title, .settings-label)
import-export (2)div.infoStale selector (#10538, b1ac7714ea)Scoped to section.actions-panel
reports (4)div.align-items-center > h6Stale selector (#10289, f0bce080a9)nb-card.report-card … h6.report-name and its toggle
settings-button (4)Themes nb-select .nth(2)Stale selector. In #10044 Themes became a popover, so Quick Settings has two selects.Layout select is index 1; theme toggle selector updated
settings-features (4)nb-card-header > header "Manage Features for"Stale selector (#10439, 418bc244c6). The index-based toggle checks also no longer matched the seed order.Checks by feature and sub-feature label; all 57 checks converted one-for-one.
roles-permissions (4)"GENERAL card rendered 306 toggles; expected 307"Catalog drift. PermissionGroups.GENERAL lists ORG_TAGS_EDIT twice (role-permission.model.ts l.598 and l.671). #10526 (09ecd8e5aa) renders each permission once, regrouped by section, so indexes no longer name toggles. ENABLED_PERMISSIONS was stale too (e.g. SUPER_ADMIN listed 179, the seed grants 364). This is not an app bug: SUPER_ADMIN was mapped label by label on the stage page.Toggles identified by label. Each card must render every catalog permission exactly once (DEMO variant accepted). ENABLED_PERMISSIONS regenerated from the seed defaults plus plugin grants for all seven roles.

Validation

  • Typecheck: tsc -p apps/gauzy-e2e/tests/tsconfig.json --noEmit shows only the 5 errors that already exist in change-language.steps.ts.
  • Run 1 (40421871684, head c327d2ce86): 77 passed, 1 flaky, 2 failed, 3 skipped.
    • Shards 1, 3 and 4 were green.
    • The 3 skips are the existing runtime skips in documents-hub, organization-documents and organization-help-center, unchanged here.
    • Run 1 found two more problems, both fixed in 171219ca2a:
ScenarioFailure in run 1Root cause (evidence)Fix
estimates (failed 3/3), sales-invoices (flaky, passed on retry)the email dialog never closedCI environment, not the app. In the trace, PUT /api/invoices/email/<address> was still pending after 75 s on every attempt. The committed .env.local points the API at smtp.gmail.com:465 with no credentials, so mail could never be delivered. From the runner, that connect can hang until nodemailer's 2-minute connection timeout, and the dialog waits for the API's answer. The step's re-click also queued a second and third email.The e2e API's mail goes to a closed local port (MAIL_HOST=127.0.0.1, MAIL_PORT=2525), so a send fails at once and the dialog closes; this is a workflow env change. The confirm step no longer re-clicks while its request is in flight.
human-resources (failed 3/3)chart option "Bar" never visibleTest timing. The forced coordinate click landed at y=416.5, on the panel header, while the KPI tiles were still pushing the panel down; the select settled at ~433.Click the select's button without force, so Playwright waits for it to be stable, and confirm the list opened.
  • Run 2 (40454347665, head 171219ca2a): in progress; results will be posted here.

Known risks / follow-ups (not changed here)

  • organization-public-page try/catch: the scenario already wraps four of its seven steps in try/catch, which can hide failures in those steps. This PR neither adds nor removes them. Making them strict needs test-data and assertion rework; that is a follow-up.
  • Duplicate permission: PermissionsEnum.ORG_TAGS_EDIT is listed twice in PermissionGroups.GENERAL. It has no effect on behaviour (the UI de-duplicates), so it is left as is.
  • roles-permissions, roles other than SUPER_ADMIN: their lists come from the seed and plugin defaultFor sources. Only SUPER_ADMIN could be checked against the stage page.
  • Time budget: the long CRUD chains (estimates, my-tasks) run close to the 240 s test timeout.
  • Uploaded pictures: upload success is reported, not asserted, because the file-storage scenario points its shard's tenant at a placeholder S3 bucket.

🤖 Generated with Claude Code