Unverified 提交 f1cc6330 authored 作者: keppo-bot[bot]'s avatar keppo-bot[bot] 提交者: GitHub

Deflake sendPrompt E2E helper against Lexical/jotai chat-switch races (#3246)

## Summary Fixes the 6 `TimeoutError: locator.click: ... element is not enabled` failures in `chat_tabs.spec.ts` and `per_chat_input.spec.ts` on CI run [24692456779](https://github.com/dyad-sh/dyad/actions/runs/24692456779) caused by a Lexical/jotai race during chat switches. ## Root cause Trace + screenshot from the failing shards show chat 2 with an empty input and a disabled Send button the moment the second `sendPrompt()` runs. The race: 1. `clickNewChat()` navigates to chat 2 (`selectedChatIdAtom` flips, URL updates, `ChatInput` re-renders with `chatId=2`). 2. Playwright's `fill()` into the Lexical editor fires `OnChangePlugin.onChange` before React has flushed the atom update for this render cycle. 3. `chatInputValueAtom`'s writer is keyed off `selectedChatIdAtom`, so the typed text gets stored under the **previous** chat's slot. 4. On the next render `ExternalValueSyncPlugin` sees `value=""` for chat 2 and resets the editor to empty. 5. `inputValue.trim()` is empty → Send button stays `disabled` → 30s click timeout. Only the *second* `sendPrompt()` after a `clickNewChat()` fails, and it's flaky because it depends on render/event ordering. ## Fix Wrap `click → fill` in `expect.toPass()` and assert the editor actually contains the prompt and the Send button is enabled before clicking. On a dropped fill the loop re-runs once atoms settle, eliminating the race deterministically. No product changes. ## Test plan - [x] `npm run fmt && npm run lint && npm run ts` - [ ] CI: chat_tabs.spec.ts and per_chat_input.spec.ts pass on shards 1/2 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3246" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review"> </picture> </a> <!-- devin-review-badge-end --> Co-authored-by: 's avatarWill Chen <7344640+wwwillchen@users.noreply.github.com> Co-authored-by: 's avatarClaude Opus 4.7 (1M context) <noreply@anthropic.com>
上级 1ab2dfc1
......@@ -88,9 +88,24 @@ export class ChatActions {
timeout,
}: { skipWaitForCompletion?: boolean; timeout?: number } = {},
) {
await this.getChatInput().click();
await this.getChatInput().fill(prompt);
await this.page.getByRole("button", { name: "Send message" }).click();
// Retry fill + assertions to survive Lexical/jotai races during chat
// switches: the per-chat input atom is keyed off selectedChatIdAtom and
// there's a render window where the editor's onChange writes to the old
// chat's slot. In that case ExternalValueSyncPlugin clears the editor on
// the next render, so the Send button stays disabled. Re-filling once the
// atoms have settled deterministically recovers.
const chatInput = this.getChatInput();
const sendButton = this.page.getByRole("button", { name: "Send message" });
await expect(chatInput).toBeVisible();
await expect(async () => {
await chatInput.click();
await chatInput.fill(prompt);
await expect(chatInput).toContainText(prompt);
await expect(sendButton).toBeEnabled();
}).toPass({ timeout: Timeout.MEDIUM });
await sendButton.click();
if (!skipWaitForCompletion) {
await this.waitForChatCompletion({ timeout });
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论