Skip to content

[tests] Analysis of potential flaky e2e tests (AI generated) #62285

Description

@susnux

Playwright flakiness audit — findings & todo

Audit of tests/playwright (specs, page objects, utils, fixtures) correlated with
flaky-test notices from CI runs on 2026-07-17. Ordered by severity.

CI context: workers: 1, retries: 1, one shared Nextcloud server per shard.
Local context: fullyParallel with many workers, reuseExistingServer — several
findings only bite locally today but will bite CI once shard workers > 1.

Confirmed flaky on CI

  • Copy-to-same-folder tests time out at 30 se2e/files/files-copy-move.spec.ts:84,95,111 (flaked in 2 runs, 3 tests) and e2e/files/live-photos.spec.ts:39,50 (2 runs).
    Failure surfaces at support/sections/CopyMoveDialogPage.ts:68 (waitForResponse for COPY), but the helper is correct — the client's same-folder copy path (apps/files/src/actions/moveOrCopyAction.ts) does PROPFIND (conflict check) → COPY → PROPFIND (stat) serially. On a loaded shard this plus fixture setup blows the default 30 s test timeout.
    Fix: raise the global timeout (e.g. 60 s) in playwright.config.ts, or test.slow() on the copy-heavy tests.

  • handlePasswordConfirmation helpersupport/utils/password-confirmation.ts (~12 call sites; flaked in e2e/appstore/admin-settings-apps.spec.ts:159 via line 205).
    Two defects: (1) line 19 probes for the dialog with a hardcoded 500 ms window and silently returns if it isn't visible yet — on a slow shard the confirmation is skipped and the guarded action never fires; (2) line 32 waits for hidden with no bound or retry — today's CI failure.
    Fix: race the dialog appearance against the expected success response instead of a 500 ms probe; wrap fill + confirm + hidden in an expect(...).toPass() retry loop.

High

  • clearTags() wipes ALL system tags globallysupport/utils/systemtags.ts:47, wired into afterAll of 5 systemtags specs (files-view, files-sidebar, files-inline-action, files-bulk-action, admin-settings-restrictions). System tags are server-global; under local fullyParallel one spec's teardown deletes tags another spec is mid-assertion on.
    Fix: track and delete only the tags each test created.

  • systemtags/admin-settings.spec.ts mass-deletes all tags in beforeEach + fixed names foo/bare2e/systemtags/admin-settings.spec.ts:10-19. Races against concurrently created tags of the parallel default-project systemtags specs; deleteTag (non-forced) throws if a tag vanishes underneath it.
    Fix: random tag names + targeted cleanup.

  • admin-settings project is not serialized against the default projectplaywright.config.ts. Per-project workers: 1 only serializes admin-settings tests among themselves; locally they interleave with default-project tests while mutating global server state. Worst offenders:

    • e2e/files_external/admin-settings-home-folder-root-mount.spec.ts:22-43 — mounts a read-only storage over every user's home and flips overwrites_home_folders
    • e2e/files_external/admin-settings-external-storage.spec.ts:20deleteAllGlobalStorages() in beforeEach
    • e2e/appstore/admin-settings-apps.spec.ts:12-23 — enables/disables apps globally with no restore

    Fix: add project dependencies (or a separate serialized invocation) so global-state specs never overlap parallel file tests.

  • Shared admin account preferences raced by parallel specse2e/users/users-columns.spec.ts:11-30 persists column visibility against the single admin account while users.spec.ts, users-disable, users-search, users-manager, users-modify, users-groups (all default project, parallel locally) assert on the same list's columns. Also users-groups.spec.ts:176-205 persists the group sort-order preference.
    Fix: serialize the admin user-list specs or isolate per-test accounts.

  • Fixture login/user-creation band-aid: single retry after fixed 800 mssupport/fixtures/random-user.ts:23, random-user-session.ts:20, admin-session.ts:18. The transient is real (login returned HTTP 500 twice in a row in a local run on 2026-07-17, failing a test). One fixed-delay retry is not robust.
    Fix: retry with backoff (e.g. 3 attempts) or poll the login endpoint until healthy.

  • drag-n-drop.spec.ts one-shot putFired booleane2e/files/drag-n-drop.spec.ts:111-123. Read the instant the conflict dialog appears; a slightly-late PUT is missed, so the assertion can pass when the guarantee is violated. The multi-file drop test (:29-42) also has no waitForResponse for the PUTs, unlike the single-file test. Whole file uses synthetic dispatchEvent DnD — inherently fragile.
    Fix: wait for a settle window / expected request count before asserting; add PUT waits to the multi-file test.

Medium

  • ContactsMenuPage.search never awaits the debounced requestsupport/sections/ContactsMenuPage.ts:67-69. Docstring promises to wait for the /contactsmenu/contacts response but only fill()s; callers assert on results immediately.
    Fix: register waitForResponse before fill() (same pattern as open()).

  • SettingsUsersPage.saveEditDialogsupport/sections/SettingsUsersPage.ts:85-92. force: true click (can fire before the handler is wired) combined with the 500 ms password-confirmation probe.
    Fix: drop force, and fix handlePasswordConfirmation (above).

  • FilesListPage.selectAll/deselectAll force-click with no state assertionsupport/sections/FilesListPage.ts:297-308. A force-click landing mid-render can no-op; the caller only fails later (feeds the flaky files-delete test).
    Fix: assert the resulting checkbox/selection state after toggling.

  • theming.pickColorsupport/utils/theming.ts:67-79. Force-click on the trigger races the picker opening; swatch chosen by positional .nth(index) with no stable identity; "Choose" clicked without confirming the swatch registered.
    Fix: wait for the picker to open, select by color value/label, verify selection before confirming.

  • AppstorePage.appRow substring match + .first()support/sections/AppstorePage.ts:61-63. hasText is a substring match, so "Files" also matches "Files sharing"; .first() silently picks by DOM order. Feeds enableButton/disableButton/appLink.
    Fix: match the app-name cell exactly.

  • files_trashbin/files.spec.ts sharee deletes before share propagation is confirmede2e/files_trashbin/files.spec.ts:67-79. Bob rms via his DAV path with no waitForShare poll (the sharing copy-move spec added exactly that); rm throws on 404.
    Fix: reuse the waitForShare poll before acting as the sharee.

  • scrolling.spec.ts / viewport.ts virtualization assumptionssupport/utils/viewport.ts:34-51 hardcodes row-height constants mirrored from the component; isFullyInViewport does toHaveCount(1) first, so a row virtualized fully out of the DOM makes the poll spin to timeout instead of resolving false (e2e/files/scrolling.spec.ts:45,84).
    Fix: treat count 0 as "not in viewport"; derive row height from a rendered row instead of constants.

  • hotkeys.spec.ts races keyboard focuse2e/files/hotkeys.spec.ts:36-73. Key presses right after URL-driven active-row changes; the Delete test asserts row count with no DELETE response wait.
    Fix: wait for focus/selection state before pressing; pair Delete with a waitForResponse.

  • files-renaming.spec.ts layout-measured virtualization test + one-shot selection reade2e/files/files-renaming.spec.ts:43-45 reads selectionStart/End once with no retry; :132-174 computes exact viewport heights and scrolls to trigger re-render.
    Fix: poll the selection read; loosen the layout assumptions.

  • files_external/admin-settings-home-folder-root-mount.spec.ts one-shot occ config reads + admitted HACK — lines 26/36/41 expect(await getOverwritesHomeFolders()).toBe(...) without expect.poll; lines 34-35 double open() as a propagation workaround.
    Fix: wrap the config reads in expect.poll; replace the double-open with an explicit wait on the observable effect.

  • Hardcoded global-state countse2e/core/header-access-levels.spec.ts:19,48 (toHaveCount(6)/(9) account-menu entries), e2e/theming/user-settings-app-order.spec.ts:17 and e2e/theming/admin-settings-default-app.spec.ts:50 (toHaveCount(2) nav apps). Brittle to any globally enabled app (see admin-settings-apps leak above).
    Fix: assert on the specific expected entries, not totals.

  • login.spec.ts global brute-force toggle + shared IPe2e/login/login.spec.ts:32-38. Failed-login tests are only safe while protection is off; once afterAll re-enables it, accumulated failures from the shared runner IP can 429-throttle other suites' logins.
    Fix: reset the brute-force attempt table in afterAll, or isolate these tests.

  • personal-info.spec.ts global config mutation + the repo's only networkidle waitse2e/settings/personal-info.spec.ts:59-72 (force_language, force_locale, has_internet_connection from a parallel-project spec) and :88, :127 (networkidle is timing-fragile with background polling).
    Fix: move to the serialized project or scope the config changes; replace networkidle with assertions on concrete post-reload UI state.

  • header-contacts-menu.spec.ts enumeration on a shared servere2e/core/header-contacts-menu.spec.ts:33-57,97-117. Asserts random users appear in a limited/paginated contacts list — accumulated users on a reused server can push them out; also toggles global shareapi_restrict_user_enumeration_to_group while parallel enumeration tests run.
    Fix: search for the exact user instead of relying on the default listing; serialize the enumeration-config window.

  • a11y-color-contrast.spec.ts reads global theming state from the parallel projecte2e/theming/a11y-color-contrast.spec.ts:49-116. Primary color is global admin state mutated by admin-settings-colors.spec.ts; axe runs once after goto with no wait for theming CSS application.
    Fix: wait for the theming stylesheet/CSS variables before running axe; covered further by the project-serialization fix above.

  • files-external-failed.spec.ts one-shot isVisible() + reload-oncee2e/files_external/files-external-failed.spec.ts:33-36,65-67; also synchronous page.url() comparison right after a click (:42-44, :73-75).
    Fix: replace with retrying toBeVisible; assert URL with expect(page).toHaveURL.

Low

  • dav/availability.spec.ts fixed replacement-usere2e/dav/availability.spec.ts:48-95. Pre-delete + finally mostly handle it, but a fixed global username remains a latent collision.
    Fix: random user name.

  • Generic unnamed getByRole('dialog') locatorssupport/sections/CopyMoveDialogPage.ts:23-24, BackgroundFilePickerDialogPage.ts:16-17, AppstorePage.ts:146-148. Match any open dialog; fragile if a second dialog/toast overlaps.
    Fix: name-scope the dialogs.

  • FilesNavigationPage.setShowHiddenFiles force-toggle without state assertionsupport/sections/FilesNavigationPage.ts:78-88. A dropped force-toggle goes unnoticed until a later assertion.
    Fix: assert the switch state after toggling (as SetupPage.selectDatabase does).

  • SetupPage.installRecommendedApps swallows a genuine timeoutsupport/sections/SetupPage.ts:122-141. .catch(() => {}) hides "prompt never consumed"; loop trusts caller-supplied appCount.
    Fix: fail loudly on the timeout; derive the prompt count from the page.

  • users-modify.spec.ts loose group-name regexe2e/users/users-modify.spec.ts:124-143. 4–6 char hex fragment matched via unanchored RegExp could hit another group.
    Fix: exact-match the option.

  • login-redirect.spec.ts URL fragment in redirect regexe2e/login/login-redirect.spec.ts:42-44. Fragments aren't sent to the server; brittle if the app normalizes the value.

Done

  • files-settings.spec.ts "Can set it to personal files" re-navigates before the default_view config PUT persists — fixed by registering waitForResponse for /apps/files/api/v1/config/default_view before clicking the radio (same idiom as FilesListPage.enableGridView). Verified locally. test(files): wait for PUT before continue with tests #62284
  • Setup wizard DB installs run under the default 30 s timeoute2e/core/setup.spec.ts:136 (MySQL) and :145 (MariaDB) each flaked once. Only Oracle gets test.setTimeout(200_000) (line 165), yet MySQL/MariaDB/Postgres also perform a full Nextcloud installation. test: mark installation tests as slow #62282
  • users.spec.ts fixed username with cleanup outside finallye2e/users/users.spec.ts:14-31. newuser-basic leaks on assertion failure and collides on the reused local server. Fix: random id + finally, like the file's other tests.
    test(users): prevent flaky tests with hardcoded values #62283
  • files-delete "can delete multiple files"e2e/files/files-delete.spec.ts:39-54 (flaked once, non-204 at line 53).
    Two defects: (1) the 5 waitForResponse listeners have identical predicates, so they can all resolve to the same first DELETE response — 5 distinct deletes are never actually verified; (2) expect(response.status()).toBe(204) is one-shot on raw status; parallel DAV DELETEs can transiently return 423 (file locked, e.g. preview generation).
    Fix: match each file's distinct URL per listener; assert the UI end state (rows gone) instead of, or in addition to, raw status.
    test(files): wait for actual requests when deleting files #62286

Metadata

Metadata

Assignees

Labels

Fields

No fields configured for Overview.

Projects

Status
Confirmed

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions