tududi/e2e/tests/universal-search.spec.ts
antanst 35afeb9a72 E2E test hardening.
- Use test specific db, not development
- Clean up DB after each test run.
2025-10-25 09:25:27 +03:00

192 lines
7 KiB
TypeScript

import { test, expect } from '@playwright/test';
import {
login,
navigateAndWait,
clickAndWaitForModal,
fillInputReliably,
waitForElement,
createUniqueEntity,
waitForNetworkIdle
} from '../helpers/testHelpers';
// Helper to create a task for search testing
async function createTaskForSearch(page, taskName) {
const taskInput = page.locator('[data-testid="new-task-input"]');
await taskInput.fill(taskName);
await taskInput.press('Enter');
await waitForNetworkIdle(page);
}
// Helper to create a project for search testing
async function createProjectForSearch(page, projectName) {
const addProjectButton = page.locator('button[aria-label="Add Project"]');
const nameInput = page.locator('[data-testid="project-name-input"]');
await clickAndWaitForModal(addProjectButton, nameInput);
await fillInputReliably(nameInput, projectName);
const saveButton = page.locator('[data-testid="project-save-button"]');
await saveButton.click();
await waitForElement(nameInput, { state: 'hidden' });
await waitForNetworkIdle(page);
}
// Helper to create a note for search testing
async function createNoteForSearch(page, noteTitle, noteContent = '') {
const addNoteButton = page.locator('[data-testid="add-note-button"]');
const titleInput = page.locator('[data-testid="note-title-input"]');
await clickAndWaitForModal(addNoteButton, titleInput);
await titleInput.fill(noteTitle);
if (noteContent) {
const contentTextarea = page.locator('[data-testid="note-content-textarea"]');
await contentTextarea.fill(noteContent);
}
await page.locator('[data-testid="note-save-button"]').click();
await waitForElement(titleInput, { state: 'hidden' });
await waitForNetworkIdle(page);
}
test('user can search across tasks, projects, and notes', async ({ page, baseURL }) => {
const appUrl = await login(page, baseURL);
// Create unique test data with a common search term
const searchTerm = createUniqueEntity('SearchTest');
const taskName = `${searchTerm} Task`;
const projectName = `${searchTerm} Project`;
const noteName = `${searchTerm} Note`;
// Navigate to tasks page and create a task
await navigateAndWait(page, appUrl + '/tasks');
await createTaskForSearch(page, taskName);
// Navigate to projects page and create a project
await navigateAndWait(page, appUrl + '/projects');
await createProjectForSearch(page, projectName);
// Create a note (can be done from sidebar)
await createNoteForSearch(page, noteName, 'Test note content');
// Navigate to a neutral page (like tasks) before searching
await navigateAndWait(page, appUrl + '/tasks');
// Click on the search input in the navbar to open search
const searchInput = page.locator('input[placeholder*="Search" i]').first();
await expect(searchInput).toBeVisible();
await searchInput.click();
// Type the search term
await searchInput.fill(searchTerm);
// Wait for search to finish loading by checking for the loading state to disappear
// The search component shows a loading indicator while fetching results
const loadingIndicator = page.locator('[data-testid="search-loading"]');
// If loading appears, wait for it to disappear
const isLoadingVisible = await loadingIndicator.isVisible().catch(() => false);
if (isLoadingVisible) {
await expect(loadingIndicator).not.toBeVisible({ timeout: 10000 });
}
// Wait for search results container to appear
const searchResults = page.locator('[data-testid="search-results"]');
await expect(searchResults).toBeVisible({ timeout: 10000 });
// Verify all three result sections appear
await expect(page.locator('[data-testid="search-results-task"]')).toBeVisible();
await expect(page.locator('[data-testid="search-results-project"]')).toBeVisible();
await expect(page.locator('[data-testid="search-results-note"]')).toBeVisible();
// Verify the specific items appear in search results
await expect(page.getByText(taskName).first()).toBeVisible();
await expect(page.getByText(projectName).first()).toBeVisible();
await expect(page.getByText(noteName).first()).toBeVisible();
});
test('user can filter search results by type', async ({ page, baseURL }) => {
const appUrl = await login(page, baseURL);
// Create unique test data
const searchTerm = createUniqueEntity('FilterTest');
const taskName = `${searchTerm} Task`;
const projectName = `${searchTerm} Project`;
// Create test data
await navigateAndWait(page, appUrl + '/tasks');
await createTaskForSearch(page, taskName);
await navigateAndWait(page, appUrl + '/projects');
await createProjectForSearch(page, projectName);
// Open universal search
await page.keyboard.press('Meta+K');
await page.waitForTimeout(500);
const searchInput = page.locator('input[placeholder*="search" i], input[type="search"]').first();
if (!(await searchInput.isVisible().catch(() => false))) {
await page.keyboard.press('Control+K');
await page.waitForTimeout(500);
}
await waitForElement(searchInput);
await searchInput.fill(searchTerm);
await waitForNetworkIdle(page);
// Look for filter buttons/tabs (Tasks, Projects, Notes, etc.)
const taskFilter = page.locator('button, [role="tab"]').filter({ hasText: /^tasks?$/i }).first();
if (await taskFilter.isVisible().catch(() => false)) {
await taskFilter.click();
await page.waitForTimeout(500);
// After filtering, only task should be visible
await expect(page.getByText(taskName)).toBeVisible();
// Project might not be visible or should be filtered out
// We can't assert it's not visible as it depends on the UI implementation
}
});
test('user can navigate to search result by clicking on it', async ({ page, baseURL }) => {
const appUrl = await login(page, baseURL);
// Create a unique task
const taskName = createUniqueEntity('NavigationTest Task');
await navigateAndWait(page, appUrl + '/tasks');
await createTaskForSearch(page, taskName);
// Open universal search
await page.keyboard.press('Meta+K');
await page.waitForTimeout(500);
const searchInput = page.locator('input[placeholder*="search" i], input[type="search"]').first();
if (!(await searchInput.isVisible().catch(() => false))) {
await page.keyboard.press('Control+K');
await page.waitForTimeout(500);
}
await waitForElement(searchInput);
await searchInput.fill(taskName);
await waitForNetworkIdle(page);
// Click on the search result
const searchResult = page.getByText(taskName).first();
await searchResult.click();
// Should navigate to the task detail page or close search and show task
// The exact behavior depends on the implementation
// We can verify the URL changed or the search closed
await page.waitForTimeout(1000);
// Search modal should close after clicking a result
const searchInputAfterClick = page.locator('input[placeholder*="search" i], input[type="search"]').first();
const isStillVisible = await searchInputAfterClick.isVisible().catch(() => false);
// Either the search closed, or we navigated away, or both
expect(isStillVisible || page.url().includes('/task/')).toBeTruthy();
});