veza/tests/e2e/helpers/conversation.ts
senke 7c74a6d408 fix(e2e): unambiguous chat conversation + new-channel locators — rc1-day2 root cause #1
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>
2026-04-18 17:11:57 +02:00

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 });
}