From a9229342e6f51672a5e0a1947c3ba5d14f6a6bd8 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 28 May 2026 12:55:10 +0300 Subject: [PATCH 1/7] test: base playwrignt setup for firefox and chrome --- .gitignore | 3 +++ e2e/pages/base-page.ts | 16 ++++++++++++ e2e/pages/comparison-page.ts | 36 ++++++++++++++++++++++++++ e2e/smoke.test.ts | 23 +++++++++++++++++ playwright.config.ts | 50 +++++++++++++++++++++++++++++++++--- tsconfig.json | 3 ++- 6 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 e2e/pages/base-page.ts create mode 100644 e2e/pages/comparison-page.ts create mode 100644 e2e/smoke.test.ts 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", -- 2.52.0 From f79b24272cd987a680de0d9534e859ffd9a4c3af Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 28 May 2026 13:20:25 +0300 Subject: [PATCH 2/7] ci/cd: add e2e tests with playwright into gitea actions workflow --- .gitea/workflows/workflow.yml | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/.gitea/workflows/workflow.yml b/.gitea/workflows/workflow.yml index af1335f..27f2ee6 100644 --- a/.gitea/workflows/workflow.yml +++ b/.gitea/workflows/workflow.yml @@ -50,6 +50,55 @@ jobs: timeout-minutes: 5 run: yarn test:component --reporter=verbose --logHeapUsage + - name: Upload Build Artifacts + uses: actions/upload-artifact@v4 + with: + name: svelte-static-dist + path: | + dist/ + package.json + + e2e: + needs: build + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.59.0-jammy + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: | + corepack enable + corepack prepare yarn@stable --activate + + - name: Persistent Yarn Cache + uses: actions/cache@v4 + with: + path: .yarn/cache + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: ${{ runner.os }}-yarn- + + - name: Install dependencies + run: yarn install --immutable + + # Pull down the compiled dist folder so Vite can preview it + - name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + name: svelte-static-dist + + - name: E2E Tests + timeout-minutes: 15 + run: yarn test:e2e + + - name: Upload Playwright report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + publish: needs: build # Only runs if tests/lint pass runs-on: ubuntu-latest -- 2.52.0 From 7a9422b57432328c462bdec211f2a096240039db Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 28 May 2026 14:05:14 +0300 Subject: [PATCH 3/7] test: add aria attributes to tested components --- src/widgets/ComparisonView/ui/Header/Header.svelte | 12 ++++++++++-- src/widgets/ComparisonView/ui/Sidebar/Sidebar.svelte | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/widgets/ComparisonView/ui/Header/Header.svelte b/src/widgets/ComparisonView/ui/Header/Header.svelte index f2a30d8..88d515f 100644 --- a/src/widgets/ComparisonView/ui/Header/Header.svelte +++ b/src/widgets/ComparisonView/ui/Header/Header.svelte @@ -93,7 +93,11 @@ const fontBName = $derived(comparisonStore.fontB?.name ?? '');