mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-08-24 20:16:30 +00:00
refactor(e2e): address the message id by slot, not by a test id
data-testid existed in exactly one place in product code — the message-id badge this suite added — while every other primitive in the app is addressed through the shadcn `data-slot` attribute. Switch it to `data-slot="flow-message-id"` and move the five specs and the helper onto a selector constant. The remaining data-testid occurrences are test-local stubs inside *.test.tsx files, not product surface. 130 mock-tier tests still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a7b1533ceb
commit
7a86cb0a44
@@ -2,7 +2,9 @@ import type { Locator, TestInfo } from '@playwright/test';
|
||||
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
export const MESSAGE_ID_TESTID = 'flow-message-id';
|
||||
/** The message-id badge carries no accessible name, so it is addressed by its slot like every other
|
||||
* shadcn primitive in this codebase — `data-testid` is not used in product code. */
|
||||
export const MESSAGE_ID_SELECTOR = '[data-slot="flow-message-id"]';
|
||||
|
||||
export const attachIdSet = async (testInfo: TestInfo, name: string, ids: string[]): Promise<void> => {
|
||||
await testInfo.attach(name, {
|
||||
@@ -12,7 +14,7 @@ export const attachIdSet = async (testInfo: TestInfo, name: string, ids: string[
|
||||
};
|
||||
|
||||
export const extractMessageIds = async (panel: Locator): Promise<string[]> => {
|
||||
const raw = await panel.getByTestId(MESSAGE_ID_TESTID).allTextContents();
|
||||
const raw = await panel.locator(MESSAGE_ID_SELECTOR).allTextContents();
|
||||
|
||||
return raw.map((text) => text.replace(/\D/g, '')).filter(Boolean);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '../../fixtures/test.ts';
|
||||
import { expectCleanPage } from '../../helpers/errors.ts';
|
||||
import { MESSAGE_ID_TESTID } from '../../helpers/subscriptions.ts';
|
||||
import { MESSAGE_ID_SELECTOR } from '../../helpers/subscriptions.ts';
|
||||
import { flowsCassette } from '../../mocks/cassettes/flows.ts';
|
||||
|
||||
test.describe('themes', { tag: '@cross' }, () => {
|
||||
@@ -12,7 +12,7 @@ test.describe('themes', { tag: '@cross' }, () => {
|
||||
await page.getByRole('row', { name: /E2E Alpha/ }).click();
|
||||
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID).first()).toBeVisible();
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR).first()).toBeVisible();
|
||||
await expect(page.locator('.xterm')).toBeVisible();
|
||||
expectCleanPage(pageErrorLog);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect, test } from '../../fixtures/test.ts';
|
||||
import { expectCleanPage } from '../../helpers/errors.ts';
|
||||
import { MESSAGE_ID_SELECTOR } from '../../helpers/subscriptions.ts';
|
||||
import { variedMessagesCassette } from '../../mocks/cassettes/flows.ts';
|
||||
|
||||
type XtermHost = {
|
||||
@@ -36,7 +37,7 @@ test.describe('flow message rendering', { tag: '@flows' }, () => {
|
||||
pageErrorLog,
|
||||
}) => {
|
||||
await page.goto('/flows/5?tab=automation');
|
||||
await expect(page.getByTestId('flow-message-id')).toHaveCount(4);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(4);
|
||||
|
||||
const thinkingToggle = page.getByText('Show thinking');
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from '../../fixtures/test.ts';
|
||||
import { expectCleanPage } from '../../helpers/errors.ts';
|
||||
import { dropAndReconnect } from '../../helpers/reconnect.ts';
|
||||
import { assertNoDuplicates, extractMessageIds, MESSAGE_ID_TESTID } from '../../helpers/subscriptions.ts';
|
||||
import { assertNoDuplicates, extractMessageIds, MESSAGE_ID_SELECTOR } from '../../helpers/subscriptions.ts';
|
||||
import {
|
||||
FLOW_A_INITIAL_IDS,
|
||||
FLOW_A_RECONNECT_ID,
|
||||
@@ -32,7 +32,7 @@ test.describe('flow reconnect', { tag: ['@flows', '@smoke'] }, () => {
|
||||
|
||||
await page.goto('/flows');
|
||||
await page.getByRole('row', { name: /E2E Alpha/ }).click();
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(BEFORE_DROP.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(BEFORE_DROP.length);
|
||||
|
||||
const loadsBeforeDrop = resourceLoads.length;
|
||||
|
||||
@@ -42,11 +42,11 @@ test.describe('flow reconnect', { tag: ['@flows', '@smoke'] }, () => {
|
||||
.poll(() => resourceLoads.length, { message: 'the reconnect re-hydrates the REST resources slot' })
|
||||
.toBe(loadsBeforeDrop + 1);
|
||||
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(AFTER_RECONNECT.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(AFTER_RECONNECT.length);
|
||||
|
||||
world.raiseFlag(REPLAY_FLAG);
|
||||
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(AFTER_REPLAY.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(AFTER_REPLAY.length);
|
||||
|
||||
const ids = await extractMessageIds(page.locator('body'));
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
assertNoDuplicates,
|
||||
attachIdSet,
|
||||
extractMessageIds,
|
||||
MESSAGE_ID_TESTID,
|
||||
MESSAGE_ID_SELECTOR,
|
||||
} from '../../helpers/subscriptions.ts';
|
||||
import {
|
||||
FLOW_A_INITIAL_IDS,
|
||||
@@ -29,7 +29,7 @@ test.describe('flow subscriptions', { tag: ['@flows', '@smoke'] }, () => {
|
||||
await page.goto('/flows');
|
||||
await page.getByRole('row', { name: /E2E Alpha/ }).click();
|
||||
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(FLOW_A_IDS.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(FLOW_A_IDS.length);
|
||||
|
||||
const ids = await extractMessageIds(page.locator('body'));
|
||||
|
||||
@@ -42,13 +42,13 @@ test.describe('flow subscriptions', { tag: ['@flows', '@smoke'] }, () => {
|
||||
test('keeps concurrent flows exact-set disjoint across a round trip', async ({ page, pageErrorLog }, testInfo) => {
|
||||
await page.goto('/flows');
|
||||
await page.getByRole('row', { name: /E2E Alpha/ }).click();
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(FLOW_A_IDS.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(FLOW_A_IDS.length);
|
||||
|
||||
const idsA = await extractMessageIds(page.locator('body'));
|
||||
|
||||
await page.goBack();
|
||||
await page.getByRole('row', { name: /E2E Beta/ }).click();
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(FLOW_B_IDS.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(FLOW_B_IDS.length);
|
||||
|
||||
const idsB = await extractMessageIds(page.locator('body'));
|
||||
|
||||
@@ -59,7 +59,7 @@ test.describe('flow subscriptions', { tag: ['@flows', '@smoke'] }, () => {
|
||||
|
||||
await page.goBack();
|
||||
await page.getByRole('row', { name: /E2E Alpha/ }).click();
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(FLOW_A_IDS.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(FLOW_A_IDS.length);
|
||||
|
||||
const idsAgain = await extractMessageIds(page.locator('body'));
|
||||
|
||||
@@ -81,14 +81,14 @@ test.describe('flow subscriptions across a pager switch', { tag: '@flows' }, ()
|
||||
const streamB = subscriptionStreamKey('messageLogAdded', { flowId: '6' });
|
||||
|
||||
await page.goto('/flows/5');
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(FLOW_A_IDS.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(FLOW_A_IDS.length);
|
||||
expect(world.subscriberCount(streamA), 'flow A must be streaming before the switch').toBe(1);
|
||||
|
||||
const idsA = await extractMessageIds(page.locator('body'));
|
||||
|
||||
await page.locator('header').getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page).toHaveURL(/\/flows\/6$/);
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID)).toHaveCount(FLOW_B_IDS.length);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR)).toHaveCount(FLOW_B_IDS.length);
|
||||
|
||||
// The DOM cannot witness this leak: `messageLogs` is keyed by flowId, so a frame carrying
|
||||
// {flowId:'5'} is written to flow A's cache slot and never rendered under flow 6 however long
|
||||
@@ -98,7 +98,7 @@ test.describe('flow subscriptions across a pager switch', { tag: '@flows' }, ()
|
||||
|
||||
world.raiseFlag(PAGER_SWITCH_FLAG);
|
||||
|
||||
await expect(page.getByTestId(MESSAGE_ID_TESTID).filter({ hasText: FLOW_B_SENTINEL_ID })).toHaveCount(1);
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR).filter({ hasText: FLOW_B_SENTINEL_ID })).toHaveCount(1);
|
||||
|
||||
const idsB = await extractMessageIds(page.locator('body'));
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { MESSAGE_ID_SELECTOR } from '../../helpers/subscriptions.ts';
|
||||
import { readTerminalBuffer } from '../../helpers/terminal.ts';
|
||||
|
||||
test.describe('real backend flow run', { tag: '@real' }, () => {
|
||||
@@ -29,7 +30,7 @@ test.describe('real backend flow run', { tag: '@real' }, () => {
|
||||
// violation.
|
||||
const flowId = new URL(page.url()).pathname.match(/\d+/)?.[0] ?? '';
|
||||
|
||||
await expect(page.getByTestId('flow-message-id').first()).toBeVisible({ timeout: 90_000 });
|
||||
await expect(page.locator(MESSAGE_ID_SELECTOR).first()).toBeVisible({ timeout: 90_000 });
|
||||
await expect(page.getByText('Hello from the e2e mock LLM!').first()).toBeVisible({ timeout: 90_000 });
|
||||
|
||||
// The scenario's `uname -a` exec must stream its sandbox output back
|
||||
|
||||
@@ -210,7 +210,7 @@ function FlowMessage({ log, searchValue = '' }: FlowMessageProps) {
|
||||
<span className="text-muted-foreground/50">{formatDate(new Date(createdAt))}</span>
|
||||
<span
|
||||
className="text-muted-foreground/50"
|
||||
data-testid="flow-message-id"
|
||||
data-slot="flow-message-id"
|
||||
>
|
||||
{log.id}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user