chore(mock): 🔧 Update test files in mock editor mode to reflect new validation rules
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
c21765a38b
commit
8ccbb1d968
2 changed files with 86 additions and 143 deletions
|
|
@ -1,38 +1,34 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Draft card edit mode tests — MSW mock mode.
|
||||
* Extraction chip display tests — MSW mock mode.
|
||||
*
|
||||
* DraftDiffCard has inline edit mode triggered by the "Edit value" button.
|
||||
* All selectors confirmed from DraftDiffCard.tsx:
|
||||
* After the assistant extracts attributes from a chat message, read-only
|
||||
* ExtractionChips appear below the message thread. Each chip shows:
|
||||
* - A label span (e.g. "Height", "Hair Color", "Languages")
|
||||
* - A value span (e.g. "168", "blonde", "en, is")
|
||||
*
|
||||
* button aria-label="Edit value" — toggles edit mode on
|
||||
* button aria-label="Cancel edit" — toggles edit mode off
|
||||
* button aria-label="Confirm change" — text "Confirm" (normal) / "Save" (edit mode)
|
||||
* input aria-label="Edit value for {label}" — text input in edit mode
|
||||
* Chips update on every AI reply — only the last AI message with extractions
|
||||
* is shown. Sending a second message replaces the previous chip set.
|
||||
*
|
||||
* When edit mode is active:
|
||||
* - The inline EditInput appears
|
||||
* - Confirm button text changes to "Save"
|
||||
* - Edit button text changes to "Cancel" (aria-label: "Cancel edit")
|
||||
* MSW extraction for "I'm 5'6, have blonde hair and slim build, speak English and Icelandic":
|
||||
* height_cm: 168 → chip "Height" / "168"
|
||||
* hair_color: blonde → chip "Hair Color" / "blonde"
|
||||
* languages: ['en','is'] → chip "Languages" / "en, is"
|
||||
*
|
||||
* Clicking Confirm/Save calls onConfirm(code, editValue) which triggers
|
||||
* AssistantProvider.confirmAttribute → POST /confirm + GET /preview.
|
||||
*
|
||||
* Test message: "I'm 5'6, have blonde hair and slim build, speak English and Icelandic"
|
||||
* MSW extracts: height (168cm), hair_color (blonde), languages (['en'])
|
||||
* First card aria-label: "Draft change for Height"
|
||||
* MSW extraction for "I speak Spanish and German":
|
||||
* languages: ['es','de'] → chip "Languages" / "es, de"
|
||||
*/
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
const EDITOR_URL = '/providers/valeria-reykjavik/edit';
|
||||
|
||||
test.describe('Profile assistant — draft card edit mode', () => {
|
||||
test('Edit button toggles inline input visible', async ({ page }) => {
|
||||
test.describe('Profile assistant — extraction chip display', () => {
|
||||
test('extraction chips show label and value for each extracted attribute', async ({ page }) => {
|
||||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
|
@ -45,29 +41,18 @@ test.describe('Profile assistant — draft card edit mode', () => {
|
|||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
await input.press('Enter');
|
||||
|
||||
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
|
||||
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
|
||||
// Click "Edit value"
|
||||
const editBtn = firstDraftCard.getByRole('button', { name: 'Edit value' });
|
||||
await expect(editBtn).toBeVisible({ timeout: 5_000 });
|
||||
await editBtn.click();
|
||||
|
||||
// Inline input appears
|
||||
const editInput = firstDraftCard.locator('[aria-label^="Edit value for"]');
|
||||
await expect(editInput).toBeVisible({ timeout: 3_000 });
|
||||
|
||||
// Edit button switches to "Cancel edit"
|
||||
await expect(firstDraftCard.getByRole('button', { name: 'Cancel edit' })).toBeVisible({ timeout: 3_000 });
|
||||
|
||||
// Confirm button text is now "Save"
|
||||
await expect(firstDraftCard.getByRole('button', { name: 'Confirm change' })).toContainText('Save');
|
||||
// All three extracted attributes appear as chips
|
||||
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
|
||||
await expect(dialog.locator('text=168').first()).toBeVisible({ timeout: 3_000 });
|
||||
await expect(dialog.locator('text=Hair Color').first()).toBeVisible({ timeout: 3_000 });
|
||||
await expect(dialog.locator('text=blonde').first()).toBeVisible({ timeout: 3_000 });
|
||||
await expect(dialog.locator('text=Languages').first()).toBeVisible({ timeout: 3_000 });
|
||||
});
|
||||
|
||||
test('Cancel edit reverts to normal card state', async ({ page }) => {
|
||||
test('sending a second message replaces chips with latest extractions', async ({ page }) => {
|
||||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
|
@ -77,29 +62,28 @@ test.describe('Profile assistant — draft card edit mode', () => {
|
|||
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
|
||||
|
||||
const input = page.getByRole('textbox', { name: /message input/i });
|
||||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
|
||||
// First message — height and hair extracted
|
||||
await input.fill("I'm 5'6, have blonde hair");
|
||||
await input.press('Enter');
|
||||
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Second message — only languages extracted, replaces previous chip set
|
||||
await input.fill('I speak Spanish and German');
|
||||
await input.press('Enter');
|
||||
|
||||
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
|
||||
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
// Languages chip with new values appears
|
||||
await expect(dialog.locator('text=Languages').first()).toBeVisible({ timeout: 15_000 });
|
||||
await expect(dialog.locator('text=es, de').first()).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
// Enter edit mode
|
||||
await firstDraftCard.getByRole('button', { name: 'Edit value' }).click();
|
||||
await expect(firstDraftCard.locator('[aria-label^="Edit value for"]')).toBeVisible({ timeout: 3_000 });
|
||||
|
||||
// Cancel edit
|
||||
await firstDraftCard.getByRole('button', { name: 'Cancel edit' }).click();
|
||||
|
||||
// Input gone, normal state restored
|
||||
await expect(firstDraftCard.locator('[aria-label^="Edit value for"]')).not.toBeVisible({ timeout: 3_000 });
|
||||
await expect(firstDraftCard.getByRole('button', { name: 'Edit value' })).toBeVisible({ timeout: 3_000 });
|
||||
await expect(firstDraftCard.getByRole('button', { name: 'Confirm change' })).toContainText('Confirm');
|
||||
// Height chip from first message is gone (chips reflect only last AI extraction)
|
||||
await expect(dialog.locator('text=168')).not.toBeVisible({ timeout: 3_000 });
|
||||
});
|
||||
|
||||
test('saving edited value confirms attribute and enables Preview Draft', async ({ page }) => {
|
||||
test('Preview Draft auto-enables once extraction chips appear', async ({ page }) => {
|
||||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
|
@ -108,27 +92,16 @@ test.describe('Profile assistant — draft card edit mode', () => {
|
|||
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
|
||||
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
|
||||
|
||||
const input = page.getByRole('textbox', { name: /message input/i });
|
||||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
await input.press('Enter');
|
||||
|
||||
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
|
||||
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
|
||||
// Enter edit mode and override the extracted value
|
||||
await firstDraftCard.getByRole('button', { name: 'Edit value' }).click();
|
||||
const editInput = firstDraftCard.locator('[aria-label^="Edit value for"]');
|
||||
await editInput.clear();
|
||||
await editInput.fill('175');
|
||||
|
||||
// Save via Confirm button (shows "Save" text in edit mode)
|
||||
await firstDraftCard.getByRole('button', { name: 'Confirm change' }).click();
|
||||
|
||||
// Card exits edit mode — Edit value button returns
|
||||
await expect(firstDraftCard.getByRole('button', { name: 'Edit value' })).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
// confirmAttribute + fetchDraftPreview fire → hasDrafts = true → Preview Draft enabled
|
||||
// Preview Draft is disabled before any message
|
||||
const previewBtn = dialog.getByRole('button', { name: /preview draft/i }).first();
|
||||
await expect(previewBtn).toBeDisabled({ timeout: 3_000 });
|
||||
|
||||
const input = page.getByRole('textbox', { name: /message input/i });
|
||||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
await input.press('Enter');
|
||||
|
||||
// Chips appear → fetchDraftPreview fires → hasDrafts=true → button enabled
|
||||
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
|
||||
await expect(previewBtn).not.toBeDisabled({ timeout: 8_000 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { test, expect } from '@playwright/test';
|
|||
/**
|
||||
* Profile assistant tests on the Editor route — MSW mock mode.
|
||||
*
|
||||
* IMPORTANT: These tests are serial — each test builds on state created by
|
||||
* prior interactions with the same MSW session.
|
||||
* IMPORTANT: These tests are serial — each test builds on state from the
|
||||
* same session. Serial mode avoids MSW service-worker re-activation delays.
|
||||
*
|
||||
* On the editor route the AssistantProvider detects the "editor" context via
|
||||
* useLocation/useParams. The session creation MSW handler returns:
|
||||
|
|
@ -13,17 +13,23 @@ import { test, expect } from '@playwright/test';
|
|||
* - quick replies: "Physical traits", "Services", "Use template"
|
||||
*
|
||||
* When the user describes physical traits, the MSW /messages handler returns
|
||||
* an AI reply with extracted attributes as draft cards:
|
||||
* - generic regions with accessible name "Draft change for Height", etc.
|
||||
* - "Confirm change" button inside each draft card
|
||||
* - "Preview Draft" button in the assistant footer (disabled until confirmed)
|
||||
* an AI reply with extracted attributes. These are rendered as read-only
|
||||
* ExtractionChips (label + value spans) below the message thread.
|
||||
* After sendMessage, fetchDraftPreview fires automatically → hasDrafts=true
|
||||
* → "Preview Draft" button becomes enabled without any confirmation step.
|
||||
*
|
||||
* Accessibility selectors confirmed from snapshot:
|
||||
* Accessibility selectors:
|
||||
* dialog "Profile assistant"
|
||||
* textbox "Message input"
|
||||
* generic "Draft change for Height"
|
||||
* button "Confirm change" (text: "Confirm")
|
||||
* button "Preview Draft"
|
||||
* button "Preview Draft" (disabled until hasDrafts=true)
|
||||
*
|
||||
* Extraction chips are plain styled spans with no aria roles — use text
|
||||
* content selectors to assert their presence.
|
||||
*
|
||||
* MSW extraction for "I'm 5'6, have blonde hair and slim build, speak English and Icelandic":
|
||||
* height_cm: 168 → chip label "Height", chip value "168"
|
||||
* hair_color: blonde → chip label "Hair Color", chip value "blonde"
|
||||
* languages: ['en','is'] → chip label "Languages", chip value "en, is"
|
||||
*/
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
|
@ -36,7 +42,7 @@ test.describe('Profile assistant — editor context', () => {
|
|||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait for profile editor to fully load (categories visible)
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await expect(fab).toBeVisible({ timeout: 10_000 });
|
||||
|
|
@ -54,7 +60,7 @@ test.describe('Profile assistant — editor context', () => {
|
|||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
|
@ -68,113 +74,77 @@ test.describe('Profile assistant — editor context', () => {
|
|||
await expect(dialog.locator('button', { hasText: /use template/i })).toBeVisible({ timeout: 5_000 });
|
||||
});
|
||||
|
||||
test('describing physical traits extracts attributes and shows draft cards', async ({ page }) => {
|
||||
test('describing physical traits shows extraction chips', async ({ page }) => {
|
||||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
||||
const dialog = page.getByRole('dialog', { name: 'Profile assistant' });
|
||||
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
|
||||
|
||||
// Wait for welcome message before interacting
|
||||
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
|
||||
|
||||
// Send a description that triggers attribute extraction
|
||||
const input = page.getByRole('textbox', { name: /message input/i });
|
||||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
await input.press('Enter');
|
||||
|
||||
// MSW returns AI reply with draft cards — each has aria-label="Draft change for X"
|
||||
const draftCard = page.locator('[aria-label*="Draft change for"]').first();
|
||||
await expect(draftCard).toBeVisible({ timeout: 15_000 });
|
||||
// MSW extracts height_cm → chip with label "Height" and value "168"
|
||||
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
|
||||
await expect(dialog.locator('text=168').first()).toBeVisible({ timeout: 3_000 });
|
||||
|
||||
// Confirm button is present on the first draft card
|
||||
const confirmBtn = draftCard.getByRole('button', { name: /confirm/i });
|
||||
await expect(confirmBtn).toBeVisible({ timeout: 5_000 });
|
||||
// hair_color → chip with label "Hair Color"
|
||||
await expect(dialog.locator('text=Hair Color').first()).toBeVisible({ timeout: 3_000 });
|
||||
});
|
||||
|
||||
test('confirming a draft attribute applies the change', async ({ page }) => {
|
||||
test('extraction chips are read-only with no confirm or edit buttons', async ({ page }) => {
|
||||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
||||
const dialog = page.getByRole('dialog', { name: 'Profile assistant' });
|
||||
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
|
||||
|
||||
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
|
||||
|
||||
const input = page.getByRole('textbox', { name: /message input/i });
|
||||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
await input.press('Enter');
|
||||
|
||||
// Wait for draft cards
|
||||
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
|
||||
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
// Wait for extraction chips to appear
|
||||
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Capture current draft card count
|
||||
const initialDraftCount = await page.locator('[aria-label*="Draft change for"]').count();
|
||||
|
||||
// Click confirm on the first draft
|
||||
const confirmBtn = firstDraftCard.getByRole('button', { name: /confirm/i });
|
||||
await confirmBtn.click();
|
||||
|
||||
// After confirming, either the card disappears or its state changes
|
||||
// We wait for any change: either count decreases or the confirm button state changes
|
||||
await page.waitForFunction(
|
||||
(count: number) => {
|
||||
const cards = document.querySelectorAll('[role="generic"][aria-label*="Draft change for"]');
|
||||
// Either fewer cards remain, or the confirm button is gone/disabled
|
||||
return cards.length < count || !document.querySelector('button[aria-label*="Confirm"], button:has-text("Confirm")');
|
||||
},
|
||||
initialDraftCount,
|
||||
{ timeout: 8_000 },
|
||||
).catch(() => {
|
||||
// Acceptable: button may transition to "Confirmed" state rather than disappearing
|
||||
});
|
||||
|
||||
// Verify the confirmation was accepted — at minimum the dialog is still open
|
||||
await expect(dialog).toBeVisible();
|
||||
// Chips are read-only annotations — no confirm, edit, or cancel buttons
|
||||
await expect(dialog.getByRole('button', { name: /confirm/i })).not.toBeVisible();
|
||||
await expect(dialog.getByRole('button', { name: /edit value/i })).not.toBeVisible();
|
||||
await expect(dialog.getByRole('button', { name: /cancel edit/i })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Preview Draft button enables after at least one confirmation', async ({ page }) => {
|
||||
test('Preview Draft enables automatically after extraction', async ({ page }) => {
|
||||
await page.goto(EDITOR_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
|
||||
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
|
||||
|
||||
const fab = page.getByRole('button', { name: 'Open profile assistant' });
|
||||
await fab.click();
|
||||
|
||||
const dialog = page.getByRole('dialog', { name: 'Profile assistant' });
|
||||
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
|
||||
|
||||
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
|
||||
|
||||
const input = page.getByRole('textbox', { name: /message input/i });
|
||||
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
|
||||
await input.press('Enter');
|
||||
|
||||
// Wait for draft cards to appear
|
||||
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
|
||||
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
|
||||
// Confirm the first draft
|
||||
const confirmBtn = firstDraftCard.getByRole('button', { name: /confirm/i });
|
||||
await confirmBtn.click();
|
||||
|
||||
// Preview Draft button should be enabled after confirmation triggers a preview fetch.
|
||||
// After hasDrafts=true, TWO "Preview draft" buttons render (header icon + footer button).
|
||||
// Use first() to avoid strict-mode multiple-match violation.
|
||||
// sendMessage auto-calls fetchDraftPreview → hasDrafts=true → button enabled
|
||||
// No confirmation step required.
|
||||
const previewBtn = dialog.getByRole('button', { name: /preview draft/i }).first();
|
||||
await expect(previewBtn).toBeVisible({ timeout: 8_000 });
|
||||
await expect(previewBtn).not.toBeDisabled({ timeout: 8_000 });
|
||||
await expect(previewBtn).not.toBeDisabled({ timeout: 15_000 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue