VideoTimelineEditor keyboard test flakes in full-suite runs: sync getByRole races the clip lane's commit
- Dominant language
- JavaScript
- Stars
- 38
- Forks
- 31
- Avg merge
- 22m
- Merged PRs (30d)
- 988
Description
## What fails
`VideoTimelineEditor.keyboard.test.jsx` → "still selects the block on Enter, which the drag must not swallow" fails **only in a full-suite run** (`cd client && npx vitest run`, 960 files), with:
```
TestingLibraryElementError: Unable to find an accessible element with the role "button"
and name `Remove second.png from timeline`
❯ blockFor src/pages/VideoTimelineEditor.keyboard.test.jsx:77:4
❯ src/pages/VideoTimelineEditor.keyboard.test.jsx:117:20
```
Observed once in a local full-suite run on 2026-09-13 (959 passed / 1 failed). It does **not** reproduce:
- running the file alone (3/3 pass)
- running `src/pages` (1426 tests) three times in a row
So it is load/timing sensitive, not a deterministic failure.
## Likely cause
`renderEditor()` waits only for the "Loading project…" placeholder to disappear:
```js
const renderEditor = async () => {
render();
await waitFor(() => expect(screen.queryByText('Loading project…')).not.toBeInTheDocument());
};
```
Every test then reaches for a clip block with a **synchronous** query:
```js
const blockFor = (assetFile) => screen
.getByRole('button', { name: `Remove ${assetFile} from timeline` })
.closest('[aria-roledescription="sortable"]');
```
The suite's `getTimelineProject`, `getGalleryImages`, `listVideoHistory`, `listMusicLibrary` and `listImageGalleryPage` mocks are all separate promises. "Loading project…" clearing is not proof that the segment lane has committed its blocks, so under load the sync `getByRole` can run a tick early. The first two tests happen to `await pressKey(...)` right after, which lets a straggling microtask land; the Enter test asserts sooner, which is consistent with it being the one that failed.
## Suggested fix
Make the block lookup await its element rather than assume it is already committed — either:
- `const blockFor = async (assetFile) => (await screen.findByRole('button', { name: ... })).closest('[aria-roledescription="sortable"]')`, awaiting it at the three call sites; or
- extend `renderEditor()` to `await screen.findByRole('button', { name: /Remove .* from timeline/ })` so every test starts from a committed lane.
Prefer whichever keeps the three tests reading the same way. Do **not** paper over it with an arbitrary sleep.
## Verify
Run the whole client suite (`cd client && npx vitest run`) a few times — the file must pass in the full 960-file run, not just standalone. The three assertions the file guards (#7243: Space/arrow/Space reorder, Escape cancel, Enter still selects) must all still hold.
Contributor guide
Assessment
This issue has not been assessed yet.