22 @critical failures in 41-chat-deep.spec.ts shared one root cause:
`firstConversationRow` searched for `button[type="button"]` inside
the sidebar container, which also matched the "New Channel" CTA
button at the sidebar footer. When the listener test user had no
conversations seeded, `waitForConversationOrEmpty` raced and
returned 'has-conversations' because the CTA button matched the
conversation-row locator — `selectFirstConversation` then clicked
the CTA, opened CreateRoomDialog, and the subsequent
`expect(input).toBeEnabled()` failed because clicking the CTA
never set `currentConversationId`.
Fix:
* `data-testid="chat-conversation-item"` on ConversationItem
(+ `data-conversation-id` for callers that need the id).
* `data-testid="chat-new-channel-cta"` on the New Channel
footer button.
* `firstConversationRow` / `waitForConversationOrEmpty` /
`createRoom` rewired to target by testid. No more overlap.
* Shared helper `tests/e2e/helpers/conversation.ts` with a
minimal `navigateToConversation(page)` — picks the first
existing conversation if any, else creates a disposable one,
returns when the message input is enabled. Signature is
deliberately minimal (no options) to avoid the second-API-
surface trap. Future callers that need specialised behavior
set up store state directly instead of extending this helper.
Results:
* 22 failed → 20 passed / 3 failed / 10 skipped (graceful skips
when test user lacks seed data).
* The 3 remaining failures are distinct root causes:
- `:220` chat page debug text leak (suspected [object Object]
or undefined rendering somewhere in chat UI — real bug,
tracked separately)
- `:339` / `:347` createRoom DOM-detach race: the "Create
room" button gets detached mid-click, suggesting the dialog
is re-rendering during the click handler. Likely a fix in
the dialog lifecycle rather than the test. Tracked
separately.
29-chat-functional.spec.ts (2 failures on send-message) not
touched by this fix — those tests don't hit the row-vs-CTA
ambiguity, they fail further downstream when the backend doesn't
echo sent messages. Same class as #7 (backend-side chat
processing incomplete in test env).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
import type { Page } from '@playwright/test';
|
|
import { expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Navigate to a chat conversation ready for message input.
|
|
*
|
|
* Minimal signature by design — a helper that starts growing options
|
|
* becomes a second API surface. If a test needs a specific conversation
|
|
* id, a specific room type, or pre-seeded messages, that test should
|
|
* fetch via the API + set store state directly rather than extending
|
|
* this helper. Keep it boring.
|
|
*
|
|
* Flow:
|
|
* 1. /chat must already be loaded and the sidebar rendered.
|
|
* 2. If at least one conversation row exists: click the first one.
|
|
* 3. Otherwise: click "New Channel", create a disposable room.
|
|
* 4. Wait for the message input to become enabled — that's the
|
|
* observable signal that `currentConversationId` is set in the
|
|
* chat store (ChatInput.tsx disables on `!currentConversationId`).
|
|
*
|
|
* Returns the conversation id chosen (via data-conversation-id attr)
|
|
* when an existing one is selected, or null when a new one was
|
|
* created (the id is assigned by the backend after dialog submit).
|
|
*
|
|
* Target selectors use testids added in v1.0.7-rc1-day2:
|
|
* - chat-conversation-item (on ConversationItem button)
|
|
* - chat-new-channel-cta (on the "New Channel" footer button)
|
|
* The pre-rc1 tests used `button[type="button"]` which ambiguously
|
|
* matched the "New Channel" CTA itself, producing the 22-failure
|
|
* cascade.
|
|
*/
|
|
export async function navigateToConversation(
|
|
page: Page,
|
|
): Promise<string | null> {
|
|
const existing = page.getByTestId('chat-conversation-item').first();
|
|
const existingCount = await page.getByTestId('chat-conversation-item').count();
|
|
|
|
if (existingCount > 0) {
|
|
const id = await existing.getAttribute('data-conversation-id');
|
|
await existing.click();
|
|
await expectInputReady(page);
|
|
return id;
|
|
}
|
|
|
|
// No conversation exists — create a disposable one. Room name is
|
|
// timestamped to avoid collisions across concurrent test workers.
|
|
const roomName = `e2e-helper-${Date.now()}`;
|
|
await page.getByTestId('chat-new-channel-cta').click();
|
|
|
|
const nameInput = page.locator('#room-name');
|
|
await expect(nameInput).toBeVisible({ timeout: 5_000 });
|
|
await nameInput.fill(roomName);
|
|
|
|
await page.getByRole('button', { name: /^create room$/i }).click();
|
|
|
|
// Dialog closes, new conversation becomes current.
|
|
await expect(nameInput).toBeHidden({ timeout: 5_000 });
|
|
await expectInputReady(page);
|
|
return null;
|
|
}
|
|
|
|
async function expectInputReady(page: Page): Promise<void> {
|
|
const input = page.getByLabel('Type a message');
|
|
await expect(input).toBeVisible({ timeout: 8_000 });
|
|
await expect(input).toBeEnabled({ timeout: 8_000 });
|
|
}
|