[tests] Analysis of potential flaky e2e tests (AI generated)
@susnux is already working on this.
Since Jul 17, 2026.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
Playwright flakiness audit — findings & todo
Confirmed flaky on CI
-
handlePasswordConfirmationhelper ( https://github.com/nextcloud/server/pull/64404 ) —support/utils/password-confirmation.ts(~12 call sites; flaked ine2e/appstore/admin-settings-apps.spec.tsvia the twohandlePasswordConfirmationcalls in "Limit app usage to group").
Still unchanged; now the highest-value fix in this list. 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 forhiddenwith no bound or retry.
Fix: race the dialog appearance against the expected success response instead of a 500 ms probe; wrap fill + confirm + hidden in anexpect(...).toPass()retry loop. -
Copy-to-same-folder tests time out —
e2e/files/files-copy-move.spec.ts:84,95,111ande2e/files/live-photos.spec.ts:39,50.
Partly addressed, still open. The global CI timeout was raised 30 s → 45 s, but "can copy a file multiple times to the same folder" has since been observed timing out at 45 s inCopyMoveDialogPage.confirm'swaitForResponse(passed on retry). 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, once per copy.
Fix:test.slow()on the copy-heavy tests specifically. Another global timeout bump only hides it.
High
-
Systemtags specs destroy each other's tags across projects —
support/utils/systemtags.ts:47(clearTags()deletes all server-global tags) is wired intoafterAllof fourdefault-project specs (files-view,files-sidebar,files-inline-action,files-bulk-action), whilee2e/systemtags/admin-settings.spec.ts:14-19mass-deletes every tag inbeforeEachfrom the concurrently-runningadmin-settingsproject, using the fixed namesfoo/bar.
Confirmed structurally: the suite listing showssystemtags/admin-settings*.spec.tsinadmin-settingsand the fourfiles-*systemtags specs indefault; those projects run concurrently locally. This matches the knownfiles-bulk-action.spec.ts"Can assign tag to selection" failure at--workers=2.deleteTag(non-forced) additionally throws if a tag vanishes underneath it.
Fix: track and delete only the tags each test created; random tag names in the admin spec. -
admin-settingsproject is not serialized against thedefaultproject —playwright.config.ts. Per-projectworkers: 1only serializes admin-settings tests among themselves; locally they interleave withdefault-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 flipsoverwrites_home_folderse2e/files_external/admin-settings-external-storage.spec.ts:20—deleteAllGlobalStorages()inbeforeEache2e/appstore/admin-settings-apps.spec.ts:12-23— enablesupdatenotificationand disablestestingglobally inbeforeEachwith no restore; this leak is what makes the hardcoded-count findings below brittle
Fix: needs a mechanism other than project
dependencies— that was considered and rejected because with--shardit can re-run the dependency project per shard. A separate serialized invocation, or moving the global-state mutations behind a lock, are the remaining options. -
Global system-config mutation from the parallel
defaultproject — same class as the above, but from the project that runs fully parallel:e2e/settings/personal-info.spec.ts:12-18/:20-25— mutateshas_internet_connection,force_language,force_locale. Worse than first reported:afterAlldoes not restoreforce_language/force_locale, it sets them toen/en_US, so the mutation leaks permanently into every later spec on a reused server.e2e/login/login.spec.ts:32-38— disablesauth.bruteforce.protection.enabledglobally for the duration of the file.
Fix:
config:system:deleteinpersonal-info'safterAllinstead of setting values; move both files to a serialized project or scope the config changes. -
Shared admin account preferences raced by parallel specs —
e2e/users/users-columns.spec.ts:11-30persists column visibility against the singleadminaccount whileusers.spec.ts,users-disable,users-search,users-manager,users-modify,users-groups(alldefaultproject, parallel locally) assert on the same list's columns. Alsousers-groups.spec.ts:176-205persists 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 ms —
support/fixtures/random-user.ts:23,random-user-session.ts:20,admin-session.ts:18. Unchanged. The transient is real (login returned HTTP 500 twice in a row in a local run on 2026-07-17, failing a test;createRandomUsernon-zero exits and MKCOL 503s are documented too). One fixed-delay retry is not robust.
Fix: retry with backoff (e.g. 3 attempts) or poll the login endpoint until healthy.
Medium
-
ContactsMenuPage.searchnever awaits the debounced request —support/sections/ContactsMenuPage.ts:67-69. Docstring promises to wait for the/contactsmenu/contactsresponse but onlyfill()s a 500 ms-debounced input; callers assert on results immediately.
Fix: registerwaitForResponsebeforefill()(same pattern asopen()). -
isFullyInViewportfails on the very case it is asked to detect —support/utils/viewport.ts:61-76. The helper opens withawait expect(locator).toHaveCount(1), which throws when a row is virtualized fully out of the DOM — i.e. the strongest form of "not in viewport".e2e/files/scrolling.spec.ts:45,84poll this helper expectingfalse, so a fully-virtualized row fails the test instead of satisfying it.fitFilesListToRowsadditionally hardcodes row-height constants mirrored from the component.
Fix: treat count 0 as "not in viewport"; derive row height from a rendered row instead of constants. -
FilesListPage.selectAll/deselectAllforce-click with no state assertion —support/sections/FilesListPage.ts:356-367. A bareclick({ force: true })landing mid-render can no-op; the caller only fails later.
Fix: assert the resulting checkbox/selection state after toggling. (Note: this applies to rawclick({force:true})only —check()/uncheck({force:true})elsewhere in the suite already assert their own end state.) -
SettingsUsersPage.saveEditDialog—support/sections/SettingsUsersPage.ts:85-92.force: trueclick (can fire before the handler is wired) combined with the 500 ms password-confirmation probe.
Fix: dropforce, and fixhandlePasswordConfirmation(above). -
AppstorePage.appRowsubstring match +.first()—support/sections/AppstorePage.ts:61-63.hasTextis a substring match, so "Files" also matches "Files sharing";.first()silently picks by DOM order. FeedsenableButton/disableButton/appLink.
Fix: match the app-name cell exactly. -
files_trashbin/files.spec.tssharee deletes before share propagation is confirmed —e2e/files_trashbin/files.spec.ts:67-79. Bobrms via his DAV path with nowaitForSharepoll;rmthrows on 404. The helper exists and is used by eight other specs.
Fix: reuse thewaitForSharepoll before acting as the sharee. -
Hardcoded global-state counts —
e2e/core/header-access-levels.spec.ts:19,48(toHaveCount(6)/(9)account-menu entries),e2e/theming/user-settings-app-order.spec.ts:17ande2e/theming/admin-settings-default-app.spec.ts:50(toHaveCount(2)nav apps). Brittle to any globally enabled app — see the unrestored app enable/disable in the admin-settings finding above.
Fix: assert on the specific expected entries, not totals. -
a11y-color-contrast.spec.tsreads global theming state from the parallel project —e2e/theming/a11y-color-contrast.spec.ts:49-116. Primary color is global admin state mutated byadmin-settings-colors.spec.ts; axe runs once aftergotowith no wait for theming CSS application.
Fix: wait for the theming stylesheet/CSS variables before running axe; also covered by the project-serialization fix above. -
files-renaming.spec.tsone-shot selection read + layout-measured virtualization test —e2e/files/files-renaming.spec.ts:43-45readsselectionStart/Endonce with no retry;:132-174computes 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.tsone-shot occ config reads + admitted HACK — lines 26/36/41expect(await getOverwritesHomeFolders()).toBe(...)withoutexpect.poll; lines 34-35 doubleopen()as a propagation workaround.
Fix: wrap the config reads inexpect.poll; replace the double-open with an explicit wait on the observable effect. -
theming.pickColorpositional swatch selection —support/utils/theming.ts:67-79. Partly addressed — there is now anexpect.pollconfirming the trigger's colour actually changed, which catches a dropped selection. Residual: the swatch is still chosen by positional.nth(index)with no stable identity, and the trigger is still force-clicked.
Fix: select by colour value/label rather than index. -
header-contacts-menu.spec.tsenumeration on a shared server —e2e/core/header-contacts-menu.spec.ts. Partly addressed — the file now setstest.describe.configure({ mode: 'serial' }), which covers the global enumeration-config toggle within the file. Residual: the tests still assert that random users appear in the default, limited contacts listing, and accumulated users on a reused server can push them out.
Fix: search for the exact user instead of relying on the default listing. -
personal-info.spec.tsuses the repo's onlynetworkidlewaits —e2e/settings/personal-info.spec.ts:39and:371.networkidleis timing-fragile with background polling.
Fix: replace with assertions on concrete post-reload UI state. (The config-mutation half of this finding was promoted to High, above.)
Low
These are real, but note that the first two cause silent false passes rather than
flakes — they will never appear in a flaky-test report, so they are worth fixing on
correctness grounds even though they cost no CI time.
-
drag-n-drop.spec.tsone-shotputFiredboolean —e2e/files/drag-n-drop.spec.ts:112-124. Read the instant the conflict dialog appears; a slightly-late PUT is missed, so the assertion passes while the guarantee is violated. The multi-file drop test (:29-42) also has nowaitForResponsefor the PUTs, unlike the single-file tests. Whole file uses syntheticdispatchEventDnD — inherently fragile.
Fix: wait for a settle window / expected request count before asserting; add PUT waits to the multi-file test. -
files-external-failed.spec.tssynchronous URL comparison —e2e/files_external/files-external-failed.spec.ts:42-44,73-75.expect(page.url()).toBe(url)immediately after a click passes if a navigation is merely slow.
Fix: assert withexpect(page).not.toHaveURL(...)/ an explicit negative wait. (TheisVisible()-then-reload-once pattern at:33-36,65-67is benign — the followingtoBeVisibleretries — so only the URL assertions need changing.) -
SetupPage.installRecommendedAppsswallows a genuine timeout —support/sections/SetupPage.ts:122-141..catch(() => {})hides "prompt never consumed"; loop trusts caller-suppliedappCount.
Fix: fail loudly on the timeout; derive the prompt count from the page. -
Generic unnamed
getByRole('dialog')locators —support/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. -
dav/availability.spec.tsfixedreplacement-user—e2e/dav/availability.spec.ts:48-95. Pre-delete +finallymostly handle it, but a fixed global username remains a latent collision.
Fix: random user name. -
users-modify.spec.tsloose group-name regex —e2e/users/users-modify.spec.ts:124-143. The 6-char UUID fragment is matched via an unanchoredRegExp, so it could hit another group.
Fix: exact-match the option. -
login-redirect.spec.tsURL fragment in redirect regex —e2e/login/login-redirect.spec.ts:42-44. Fragments aren't sent to the server; brittle if the app normalizes the value.
Withdrawn — not actual problems
-
— incorrect mechanism.login.spec.tsglobal brute-force toggle lets failures accumulate and 429-throttle other suitesThrottler::registerAttempt()returns early whenauth.bruteforce.protection.enabledis false (lib/private/Security/Bruteforce/Throttler.php:54-55), so no attempts are recorded while the file has protection disabled, and there is nothing to throttle onceafterAllre-enables it. The residual concern — adefault-project spec mutating global system config at all — is tracked under the High finding above. -
— incorrect. Playwright'sFilesNavigationPage.setShowHiddenFilesforce-toggle without state assertioncheck()/uncheck()assert the resulting checked state themselves and retry;forceonly skips the actionability checks, not the post-condition. The same applies to thecheck/uncheckcalls inusers-columns.spec.ts. Only bareclick({ force: true })lacks verification, which is tracked separately underFilesListPage.selectAll. -
— fixed in 7ceba63b8f5 (2026-09-14): every keypress is now gated onhotkeys.spec.tsraces keyboard focusawait expect(page).toHaveURL(/\/apps\/files\/files\/\d+/). The "Delete asserts row count with no DELETE response wait" sub-claim was never valid —toHaveCountis a retrying assertion and self-heals.
Done
-
files-settings.spec.ts"Can set it to personal files" re-navigates before thedefault_viewconfig PUT persists — fixed by registeringwaitForResponsefor/apps/files/api/v1/config/default_viewbefore clicking the radio (same idiom asFilesListPage.enableGridView). https://github.com/nextcloud/server/pull/62284 - Setup wizard DB installs run under the default 30 s timeout —
e2e/core/setup.spec.ts:136(MySQL) and:145(MariaDB). https://github.com/nextcloud/server/pull/62282 -
users.spec.tsfixed username with cleanup outsidefinally—newuser-basicleaked on assertion failure and collided on the reused local server; now a random id +finally. https://github.com/nextcloud/server/pull/62283 -
files-delete"can delete multiple files" — the 5waitForResponselisteners had identical predicates (all resolving to the same first DELETE) and a one-shot raw-status assertion that parallel DAV DELETEs could trip with a transient 423. https://github.com/nextcloud/server/pull/62286 - CI test timeout raised to 45 s and several unbounded
FilesListPagewaits bounded — 576d78c7b06. This reduced, but did not eliminate, the copy-to-same-folder timeouts tracked above.
Suggested order of work
If only three things get fixed, these give the most per change:
handlePasswordConfirmation— one file, ~12 call sites, the only finding with a repeat CI failure attributed to it.- Systemtags tag cleanup — scope
clearTags()to the tags each test created; one util plus fiveafterAlls, and it kills the known--workers=2failure. personal-info'safterAll—config:system:deleteinstead of settingforce_language/force_locale; a two-line change that stops a permanent global leak.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.