import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; export class SearchHelper { constructor(private page: Page) {} /** * Navigate to the docs page and wait for it to load */ async navigateToDocs() { await this.page.goto(`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/docs`); await this.page.waitForLoadState("networkidle"); // Wait for the search input to be available (client component hydration) const searchInput = this.getSearchInput(); await searchInput.waitFor({ state: "attached", timeout: 10000 }); } /** * Find and return the search input field */ getSearchInput() { return this.page.locator('input[placeholder="Search..."]'); } /** * Perform a search with the given term */ async performSearch(searchTerm: string) { const searchInput = this.getSearchInput(); // Wait for the search input to be visible and attached to the DOM // This is important because the Search component is a client component that needs to hydrate await searchInput.waitFor({ state: "visible", timeout: 10000 }); await searchInput.fill(searchTerm); await searchInput.press("Enter"); // Wait for search to complete await this.page.waitForTimeout(1000); } /** * Wait for search results to appear */ async waitForSearchResults() { await this.page.waitForTimeout(1000); } /** * Get search results container */ getSearchResultsContainer() { // Use the data-testid attribute for reliable selection return this.page.locator('[data-testid="search-results-container"]'); } /** * Get "No Llamas Found" message */ getNoResultsMessage() { // Use the data-testid attribute for reliable selection return this.page.locator('[data-testid="no-results-message"]'); } /** * Get loading message */ getLoadingMessage() { // Look for the loading message within the search results container return this.page.locator( '[data-testid="search-results-container"] h4:has-text("Mustering all the Llamas")' ); } /** * Get all search result links */ getSearchResultLinks() { return this.page.locator('a[href*="/docs"]'); } /** * Verify search results are visible */ async expectSearchResultsVisible() { const resultsContainer = this.getSearchResultsContainer(); const noResultsMessage = this.getNoResultsMessage(); await expect(resultsContainer.or(noResultsMessage)).toBeVisible(); } /** * Verify search results are not visible */ async expectSearchResultsNotVisible() { const resultsContainer = this.getSearchResultsContainer(); await expect(resultsContainer).not.toBeVisible(); } /** * Verify search input is visible */ async expectSearchInputVisible() { const searchInput = this.getSearchInput(); await expect(searchInput).toBeVisible(); } /** * Verify search input has the expected value */ async expectSearchInputValue(expectedValue: string) { const searchInput = this.getSearchInput(); await expect(searchInput).toHaveValue(expectedValue); } /** * Clear search by clicking the copy button */ async clearSearch() { await this.page.click('button:has-text("Copy")'); } /** * Verify Pagefind files are accessible */ async verifyPagefindFilesAccessible() { const isDev = this.page.url().includes("localhost"); // Check Pagefind JavaScript file const pagefindJsResponse = await this.page.request.get( isDev ? "http://localhost:3000/pagefind/pagefind.js" : `${process.env.BASE_URL}${ process.env.NEXT_PUBLIC_BASE_PATH ?? "" }/_next/static/pagefind/pagefind.js` ); expect(pagefindJsResponse.status()).toBe(200); // Check Pagefind index file const pagefindIndexResponse = await this.page.request.get( isDev ? "http://localhost:3000/pagefind/pagefind-ui.js" : `${process.env.BASE_URL}${ process.env.NEXT_PUBLIC_BASE_PATH ?? "" }/_next/static/pagefind/pagefind-ui.js` ); expect(pagefindIndexResponse.status()).toBe(200); } /** * Measure search performance */ async measureSearchPerformance(searchTerm: string): Promise { const startTime = Date.now(); await this.performSearch(searchTerm); const endTime = Date.now(); return endTime - startTime; } /** * Test search with multiple terms */ async testMultipleSearches(searchTerms: string[]) { for (const term of searchTerms) { await this.performSearch(term); await this.expectSearchResultsVisible(); await this.clearSearch(); } } /** * Test search on mobile viewport */ async testMobileSearch() { await this.page.setViewportSize({ width: 375, height: 667 }); await this.expectSearchInputVisible(); await this.performSearch("TinaDocs"); await this.expectSearchResultsVisible(); } } /** * Test data for search tests */ export const SEARCH_TEST_DATA = { knownTerms: [ "TinaDocs", "documentation", "search", "API", "TinaCMS", "deployment", "theming", "components", ], nonExistentTerms: [ "xyz123nonexistent", "completelyrandomterm", "shouldnotexist", ], specialCharacters: [ "@#$%", "test@example.com", "user-name", "file/path", "test&query", "test+query", ], }; /** * Create a search helper instance */ export function createSearchHelper(page: Page): SearchHelper { return new SearchHelper(page); }