Frontmatter
| id | 9443 |
| title | Stabilize Playwright Unit Tests by Centralizing Global Mocks |
| state | Closed |
| labels | bugaitestingcore |
| assignees | tobiu |
| createdAt | Mar 11, 2026, 3:27 PM |
| updatedAt | Mar 11, 2026, 4:59 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9443 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Mar 11, 2026, 4:59 PM |
Stabilize Playwright Unit Tests by Centralizing Global Mocks

Input from Gemini 3.1 Pro:
✦ The root cause of the random test suite failures was isolated to an architectural race condition during test teardown, not a global mock pollution issue.
The Problem: In unit tests (like
GridScrollProfile.spec.mjs), setting a config likedragResortable: trueorresponsive: truetriggers an async module load viaimport()inside the respectiveafterSet...hook oncontainer.Baseorcomponent.Base. When the test completes, it synchronously calls.destroy()on the component hierarchy, unregistering it from theComponentManager. However, Playwright worker processes are reused. If the pendingimport()microtask resolves after the test has finished (while the worker is spinning up the next test file), the hook resumes execution against a now-destroyed instance. ForafterSetDragResortable, it would then callme.createSortZone(), which attempts to accessme.parent.id. Becauseme.parentdynamically queries theComponentManager(where the instance no longer exists), it returnednull, crashing the entire Playwright worker with aTypeError.The Solution: The framework's built-in
trap()method was designed precisely to handle this scenario by safely rejecting pending async operations when a component is destroyed. We wrapped the dynamic module imports insrc/container/Base.mjsandsrc/component/Base.mjswiththis.trap():module = await me.trap(me.loadSortZoneModule()); // ... module = await me.trap(import(`../../src/plugin/Responsive.mjs`));This ensures that if the test suite tears down the component before the network/file system resolves the module, the execution gracefully aborts rather than blowing up the worker environment for subsequent tests.
The entire unit test suite now runs reliably in a single worker process without cross-contamination.
Goal
Resolve test cross-contamination issues caused by the "Single-Thread Simulation" architecture where Playwright workers reuse the same Node.js global state across multiple test files. Specifically, fix the
TypeError: Neo.currentWorker?.on is not a functionwhich breaks tests (like the Toolbar tests) when run collectively.Context
Currently, individual test files (like
CrossWindowMove.spec.mjsandComboBoxInternalId.spec.mjs) forcefully overwrite global mocks inNeo(e.g.Neo.currentWorker = {}) in theirbeforeEachblocks. Because Playwright reuses workers, these destructive overwrites persist and corrupt the environment for subsequent tests, leading to mysterious failures when running the full suite.Proposed Solution
test/playwright/setup.mjsto provide a robust, standardized mock forNeo.main,Neo.currentWorker,Neo.worker, andNeo.applyDeltasout-of-the-box.beforeEachmock overrides in the ~11 affected test files so they either rely on the central mock or properly backup/restore their specific changes usingafterEach.