-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Deflake chat input prompt entry in E2E #3186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
1a60832
5177652
5a285d5
c2d660a
032be8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -88,9 +88,18 @@ 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(); | ||||||||||||||||||||||
| const chatInput = this.getChatInput(); | ||||||||||||||||||||||
| const sendButton = this.page.getByRole("button", { name: "Send message" }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| await expect(chatInput).toBeVisible(); | ||||||||||||||||||||||
|
cursor[bot] marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Visibility assertion uses short default timeout before retryLow Severity The new Reviewed by Cursor Bugbot for commit c2d660a. Configure here. |
||||||||||||||||||||||
| await expect(async () => { | ||||||||||||||||||||||
| await chatInput.click(); | ||||||||||||||||||||||
| await chatInput.fill(prompt); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MEDIUM | retry-correctness Retry loop can silently produce accumulated text in the Lexical editor Inside the await chatInput.click();
await chatInput.fill(prompt);
await expect(chatInput).toContainText(prompt);
await expect(sendButton).toBeEnabled();The newly-added rule in
💡 Suggestion: Use |
||||||||||||||||||||||
| await expect(chatInput).toContainText(prompt); | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| await expect(sendButton).toBeEnabled(); | ||||||||||||||||||||||
|
Comment on lines
+98
to
+99
|
||||||||||||||||||||||
| await expect(chatInput).toContainText(prompt); | |
| await expect(sendButton).toBeEnabled(); | |
| const chatInputText = await chatInput.textContent(); | |
| expect(chatInputText ?? "").toContain(prompt); | |
| expect(await sendButton.isEnabled()).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The nested web-first assertions (toContainText, toBeEnabled) inside the toPass() callback each carry their own default auto-wait timeout (~5s each). A single retry attempt can block for up to 10s on these assertions, largely defeating the retry loop. Use instant checks (e.g., await chatInput.textContent() + synchronous expect, and await sendButton.isEnabled()) or pass { timeout: 0 } to the nested expects so toPass retries quickly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At e2e-tests/helpers/page-objects/components/ChatActions.ts, line 100:
<comment>The nested web-first assertions (`toContainText`, `toBeEnabled`) inside the `toPass()` callback each carry their own default auto-wait timeout (~5s each). A single retry attempt can block for up to 10s on these assertions, largely defeating the retry loop. Use instant checks (e.g., `await chatInput.textContent()` + synchronous `expect`, and `await sendButton.isEnabled()`) or pass `{ timeout: 0 }` to the nested expects so `toPass` retries quickly.</comment>
<file context>
@@ -88,9 +88,20 @@ export class ChatActions {
+ await expect(async () => {
+ await chatInput.click();
+ await chatInput.fill(prompt);
+ await expect(chatInput).toContainText(prompt);
+ await expect(sendButton).toBeEnabled();
+ }).toPass({ timeout: Timeout.SHORT });
</file context>
| await expect(chatInput).toContainText(prompt); | |
| await expect(sendButton).toBeEnabled(); | |
| const chatInputText = await chatInput.textContent(); | |
| expect(chatInputText ?? "").toContain(prompt); | |
| expect(await sendButton.isEnabled()).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟢 LOW | race-condition
sendButton.click() is outside the retry loop
Once toPass() resolves, the final click is a single-shot call:
}).toPass({ timeout: Timeout.MEDIUM });
await sendButton.click();If the send button flips back to disabled between the final retry check and the actual click (e.g., React commits a state change while re-validating the Lexical content), the click will either miss or be silently swallowed — putting us right back in the flake class this PR is trying to fix.
💡 Suggestion: After sendButton.click(), assert that the chat is actually in a sending state (spinner visible, send button disabled, or the message appears in the history) so any lost click surfaces as a loud failure rather than a timeout further downstream in waitForChatCompletion().
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,7 @@ The chat input uses a Lexical editor (contenteditable). Standard Playwright meth | |
|
|
||
| - **Clearing input**: `fill("")` doesn't reliably clear Lexical. Use keyboard shortcuts instead: `Meta+a` then `Backspace`. | ||
| - **Timing issues**: Lexical may need time to update its internal state. Use `toPass()` with retries for resilient tests. | ||
| - **Avoid locator drift**: When both home/chat inputs may exist, scope the editor locator to the specific container (for example `chat-input-container`) and reuse one locator instance for click/fill/assertions. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MEDIUM | documentation-code-mismatch New rule contradicts This new rule instructs readers to "scope the editor locator to the specific container (for example getChatInput() {
return this.page.locator(
'[data-lexical-editor="true"][aria-placeholder^="Ask Dyad to build"]',
);
}That means the same placeholder under Future contributors will follow this rule expecting it to describe
💡 Suggestion: At minimum, fix the PR description so reviewers and |
||
| - **Helper methods**: Use `po.clearChatInput()` and `po.openChatHistoryMenu()` from test_helper.ts for reliable Lexical interactions. | ||
|
|
||
| ```ts | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 MEDIUM | unexplained-change
Fixture swap from
minimal→minimal-with-ai-rulesis not explainedAll 6 tests in this file switched their fixture. The only difference between the two fixtures is the presence of
AI_RULES.md:~15 other spec files still use
minimal, so this is now an outlier, and the PR description doesn't mention the switch at all. Two problems:minimalfixture. If tab behavior regresses when AI rules are absent (different banners, setup flow, focus stealing), none of these tests will catch it.💡 Suggestion: If the swap is needed to avoid a focus/race condition caused by the no-AI-rules onboarding path, call that out in the PR description and add a one-line
//comment next to the firstimportApp("minimal-with-ai-rules")explaining why. Otherwise, revert tominimalto stay consistent with the rest of the e2e suite.