diff --git a/.gitignore b/.gitignore index 2f13284..744ef9b 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ storybook-static # Tests coverage/ .aider* +playwright-report/ +blob-report/ +.playwright/ diff --git a/e2e/pages/base-page.ts b/e2e/pages/base-page.ts new file mode 100644 index 0000000..5e7c8e8 --- /dev/null +++ b/e2e/pages/base-page.ts @@ -0,0 +1,16 @@ +import type { Page } from '@playwright/test'; + +/** + * Shared base for all page objects. Subclasses extend this and expose + * domain-specific locators + actions — never raw selectors leaking into tests. + */ +export abstract class BasePage { + protected constructor(protected readonly page: Page) {} + + /** + * Navigate to a path relative to baseURL. + */ + async goto(path = '/') { + await this.page.goto(path); + } +} diff --git a/e2e/pages/comparison-page.ts b/e2e/pages/comparison-page.ts new file mode 100644 index 0000000..d456604 --- /dev/null +++ b/e2e/pages/comparison-page.ts @@ -0,0 +1,36 @@ +import type { + Locator, + Page, +} from '@playwright/test'; +import { BasePage } from './base-page'; + +/** + * Page object for the root comparison view. Encapsulates locators for the + * primary controls so tests don't hardcode aria-labels or DOM structure. + */ +export class ComparisonPage extends BasePage { + readonly searchInput: Locator; + readonly previewInput: Locator; + + constructor(page: Page) { + super(page); + this.searchInput = page.getByRole('textbox', { name: 'Search typefaces' }); + this.previewInput = page.getByRole('textbox', { name: 'Preview text' }); + } + + /** + * Open the root page and wait for the main controls to be interactable. + */ + async open() { + await this.goto('/'); + await this.searchInput.waitFor({ state: 'visible' }); + } + + async searchFor(query: string) { + await this.searchInput.fill(query); + } + + async setPreviewText(text: string) { + await this.previewInput.fill(text); + } +} diff --git a/e2e/smoke.test.ts b/e2e/smoke.test.ts new file mode 100644 index 0000000..d616504 --- /dev/null +++ b/e2e/smoke.test.ts @@ -0,0 +1,23 @@ +import { + expect, + test, +} from '@playwright/test'; +import { ComparisonPage } from './pages/comparison-page'; + +test.describe('smoke', () => { + test('loads the comparison view with its primary controls', async ({ page }) => { + const view = new ComparisonPage(page); + await view.open(); + + await expect(view.searchInput).toBeVisible(); + await expect(view.previewInput).toBeVisible(); + }); + + test('accepts a search query', async ({ page }) => { + const view = new ComparisonPage(page); + await view.open(); + await view.searchFor('Inter'); + + await expect(view.searchInput).toHaveValue('Inter'); + }); +}); diff --git a/playwright.config.ts b/playwright.config.ts index bc84607..ab4707a 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,10 +1,54 @@ -import { defineConfig } from '@playwright/test'; +import { + defineConfig, + devices, +} from '@playwright/test'; + +/** + * E2E config. Tests run against the production build via `vite preview` on port 4173. + * Locally: all three browser engines run in parallel. + * CI: chromium only, workers=1 — the runner has 6GB RAM and `yarn build` already + * spikes 1–2GB, so we keep the E2E peak bounded. + */ +const isCI = !!process.env.CI; export default defineConfig({ + testDir: 'e2e', + testMatch: /.*\.test\.ts$/, + + fullyParallel: true, + forbidOnly: isCI, + retries: isCI ? 2 : 0, + workers: isCI ? 1 : undefined, + + reporter: isCI + ? [['html', { open: 'never' }], ['github']] + : [['html', { open: 'on-failure' }], ['list']], + + use: { + baseURL: 'http://localhost:4173', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + }, + + projects: isCI + ? [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] + : [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], webServer: { command: 'yarn build && yarn preview', port: 4173, - reuseExistingServer: true, + reuseExistingServer: !isCI, + timeout: 120_000, }, - testDir: 'e2e', }); diff --git a/tsconfig.json b/tsconfig.json index 68fface..f80031d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -40,7 +40,8 @@ "src/**/*.d.ts", "vitest.config*.ts", "vitest.setup*.ts", - "vitest.types.d.ts" + "vitest.types.d.ts", + "playwright.config*.ts" ], "exclude": [ "node_modules",