# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: application.spec.js >> Forms Validation and Error Messages >> Temperature field accepts only 0.0-1.0 range - Location: tests/e2e/application.spec.js:543:3 # Error details ``` Error: expect(locator).toBeVisible() failed Locator: locator('.text-danger') Expected: visible Error: strict mode violation: locator('.text-danger') resolved to 2 elements: 1)
The title field is required.
aka getByText('The title field is required.') 2)
The temperature field must not be greater than 1.
aka getByText('The temperature field must') Call log: - Expect "toBeVisible" with timeout 10000ms - waiting for locator('.text-danger') - waiting for navigation to finish... - navigated to "http://localhost:16820/prompt-projects/create" ``` # Test source ```ts 460 | untestableFeatures.push('AI provider detail view: no providers exist to click'); 461 | } 462 | }); 463 | 464 | test('Edit AI provider', async () => { 465 | await adminPage.goto(`${BASE_URL}/ai-providers`); 466 | const firstEdit = adminPage.locator('a[href*="ai-providers/"][class*="btn-warning"]').first(); 467 | const count = await firstEdit.count(); 468 | if (count > 0) { 469 | await firstEdit.click(); 470 | await adminPage.waitForURL((url) => url.pathname.includes('/ai-providers/') && !url.pathname.includes('/create'), { timeout: 10000 }); 471 | await adminPage.waitForLoadState('networkidle').catch(() => {}); 472 | await expect(adminPage.locator('#name')).toBeVisible(); 473 | } else { 474 | untestableFeatures.push('AI provider edit: no providers exist to edit'); 475 | } 476 | }); 477 | 478 | test('Delete AI provider', async () => { 479 | await adminPage.goto(`${BASE_URL}/ai-providers`); 480 | const firstDelete = adminPage.locator('form[action*="ai-providers"][method="POST"]').first(); 481 | const count = await firstDelete.count(); 482 | if (count > 0) { 483 | adminPage.on('dialog', (dialog) => dialog.accept()); 484 | await firstDelete.evaluate((form) => form.submit()); 485 | await expect(adminPage.locator('.alert-success, .alert-danger')).toBeVisible({ timeout: 10000 }); 486 | } else { 487 | untestableFeatures.push('AI provider delete: no providers exist to delete'); 488 | } 489 | }); 490 | 491 | test('API key visibility toggle works', async () => { 492 | await adminPage.goto(`${BASE_URL}/ai-providers/create`); 493 | const apiKeyInput = adminPage.locator('#api_key'); 494 | const toggleBtn = adminPage.locator('#toggleApiKey'); 495 | if (await toggleBtn.count() > 0) { 496 | await expect(apiKeyInput).toHaveAttribute('type', 'password'); 497 | await toggleBtn.click(); 498 | await expect(apiKeyInput).toHaveAttribute('type', 'text'); 499 | await toggleBtn.click(); 500 | await expect(apiKeyInput).toHaveAttribute('type', 'password'); 501 | } else { 502 | untestableFeatures.push('API key toggle: toggle button not found'); 503 | } 504 | }); 505 | }); 506 | 507 | // ========== FORMS VALIDATION ========== 508 | test.describe('Forms Validation and Error Messages', () => { 509 | let adminPage; 510 | 511 | test.beforeEach(async ({ context }) => { 512 | adminPage = await login(context, SUPERADMIN_EMAIL, SUPERADMIN_PASSWORD); 513 | }); 514 | 515 | test.afterEach(async () => { 516 | await adminPage.close(); 517 | }); 518 | 519 | test('Prompt project form shows validation errors for missing fields', async () => { 520 | await adminPage.goto(`${BASE_URL}/prompt-projects/create`); 521 | await adminPage.evaluate(() => { 522 | const form = document.querySelector('form:has(#title)'); 523 | if (form) { 524 | Array.from(form.querySelectorAll('[required]')).forEach(el => el.removeAttribute('required')); 525 | form.submit(); 526 | } 527 | }); 528 | await expect(adminPage.locator('.text-danger')).toHaveCount(2, { timeout: 10000 }); 529 | }); 530 | 531 | test('AI provider form shows validation errors for missing fields', async () => { 532 | await adminPage.goto(`${BASE_URL}/ai-providers/create`); 533 | await adminPage.evaluate(() => { 534 | const form = document.querySelector('form:has(#name)'); 535 | if (form) { 536 | Array.from(form.querySelectorAll('[required]')).forEach(el => el.removeAttribute('required')); 537 | form.submit(); 538 | } 539 | }); 540 | await expect(adminPage.locator('.text-danger')).toHaveCount(4, { timeout: 10000 }); 541 | }); 542 | 543 | test('Temperature field accepts only 0.0-1.0 range', async () => { 544 | await adminPage.goto(`${BASE_URL}/prompt-projects/create`); 545 | await adminPage.fill('#temperature', '1.5'); 546 | await adminPage.evaluate(() => { 547 | const form = document.querySelector('form:has(#title)'); 548 | if (form) form.submit(); 549 | }); 550 | const providerSelect = adminPage.locator('#provider_id'); 551 | const providerCount = await providerSelect.locator('option').count(); 552 | if (providerCount > 1) { 553 | await providerSelect.selectOption({ index: 1 }); 554 | } 555 | await adminPage.fill('#objective', 'Test'); 556 | await adminPage.evaluate(() => { 557 | const form = document.querySelector('form:has(#title)'); 558 | if (form) form.submit(); 559 | }); > 560 | await expect(adminPage.locator('.text-danger')).toBeVisible({ timeout: 10000 }); | ^ Error: expect(locator).toBeVisible() failed 561 | }); 562 | 563 | test('Max tokens must be a positive integer', async () => { 564 | await adminPage.goto(`${BASE_URL}/prompt-projects/create`); 565 | await adminPage.fill('#max_tokens', '-5'); 566 | await adminPage.evaluate(() => { 567 | const form = document.querySelector('form:has(#title)'); 568 | if (form) form.submit(); 569 | }); 570 | const providerSelect = adminPage.locator('#provider_id'); 571 | const providerCount = await providerSelect.locator('option').count(); 572 | if (providerCount > 1) { 573 | await providerSelect.selectOption({ index: 1 }); 574 | } 575 | await adminPage.fill('#objective', 'Test'); 576 | await adminPage.evaluate(() => { 577 | const form = document.querySelector('form:has(#title)'); 578 | if (form) form.submit(); 579 | }); 580 | await expect(adminPage.locator('.text-danger')).toBeVisible({ timeout: 10000 }); 581 | }); 582 | }); 583 | 584 | // ========== COOKIE CONSENT ========== 585 | test.describe('Cookie Consent Banner', () => { 586 | test('Banner appears on public pages', async ({ page }) => { 587 | await page.goto(BASE_URL); 588 | await expect(page.locator('#cookieConsentBanner')).toBeAttached(); 589 | }); 590 | 591 | test('Accept all cookies closes banner', async ({ page }) => { 592 | await page.goto(BASE_URL); 593 | await page.waitForSelector('#cookieConsentBanner', { state: 'visible', timeout: 5000 }).catch(() => {}); 594 | const acceptBtn = page.locator('.cookie-btn-accept'); 595 | if (await acceptBtn.count() > 0) { 596 | await acceptBtn.click(); 597 | await page.waitForTimeout(500); 598 | await expect(page.locator('#cookieConsentBanner')).not.toBeVisible(); 599 | } else { 600 | untestableFeatures.push('Cookie consent accept button not visible (banner may not have appeared)'); 601 | } 602 | }); 603 | 604 | test('Cookie preferences modal can be opened', async ({ page }) => { 605 | await page.goto(BASE_URL); 606 | await page.waitForSelector('#cookieConsentBanner', { state: 'visible', timeout: 5000 }).catch(() => {}); 607 | const customizeBtn = page.locator('.cookie-btn-customize'); 608 | if (await customizeBtn.count() > 0) { 609 | await customizeBtn.click(); 610 | await expect(page.locator('#cookieConsentPreferences')).toBeVisible(); 611 | await page.locator('.btn-close').click(); 612 | } else { 613 | untestableFeatures.push('Cookie customize button not visible'); 614 | } 615 | }); 616 | 617 | test('Cookie consent persists across page navigation', async ({ page }) => { 618 | await page.goto(BASE_URL); 619 | await page.waitForSelector('#cookieConsentBanner', { state: 'visible', timeout: 5000 }).catch(() => {}); 620 | const acceptBtn = page.locator('.cookie-btn-accept'); 621 | if (await acceptBtn.count() > 0) { 622 | await acceptBtn.click(); 623 | await page.waitForTimeout(500); 624 | await page.goto(`${BASE_URL}/login`); 625 | const banner = page.locator('#cookieConsentBanner'); 626 | await expect(banner).not.toBeVisible({ timeout: 3000 }).catch(() => {}); 627 | } else { 628 | untestableFeatures.push('Cookie consent accept button not visible for persistence test'); 629 | } 630 | }); 631 | }); 632 | 633 | // ========== RESPONSIVENESS ========== 634 | test.describe('Responsiveness', () => { 635 | test('Desktop viewport 1920x1080: sidebar visible and layout stable', async ({ page }) => { 636 | await page.setViewportSize({ width: 1920, height: 1080 }); 637 | await page.goto(`${BASE_URL}/login`); 638 | await expect(page.locator('.login-card')).toBeVisible(); 639 | }); 640 | 641 | test('Mobile viewport 375x667: login page adapts', async ({ page }) => { 642 | await page.setViewportSize({ width: 375, height: 667 }); 643 | await page.goto(`${BASE_URL}/login`); 644 | await expect(page.locator('.login-card')).toBeVisible(); 645 | await expect(page.locator('#email')).toBeVisible(); 646 | }); 647 | 648 | test('Mobile: hamburger menu toggles sidebar', async ({ context }) => { 649 | const page = await login(context, SUPERADMIN_EMAIL, SUPERADMIN_PASSWORD); 650 | await page.setViewportSize({ width: 375, height: 667 }); 651 | await page.goto(`${BASE_URL}/dashboard`); 652 | const sidebar = page.locator('#sidebar'); 653 | await expect(sidebar).toBeVisible(); 654 | await page.click('#sidebarCollapse'); 655 | await page.waitForTimeout(300); 656 | await page.close(); 657 | }); 658 | 659 | test('Mobile: dashboard cards stack vertically', async ({ page }) => { 660 | await page.setViewportSize({ width: 375, height: 667 }); ```