Add playwright

This commit is contained in:
Jason Laster 2025-03-12 15:06:13 -04:00
parent 7849e2198c
commit db0e3abb51
10 changed files with 9032 additions and 10034 deletions

View File

@ -24,8 +24,8 @@ jobs:
- name: Run type check
run: pnpm run typecheck
# - name: Run ESLint
# run: pnpm run lint
- name: Run ESLint
run: pnpm run lint
- name: Run tests
run: pnpm run test

38
.github/workflows/playwright.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Playwright Tests
on:
pull_request:
jobs:
test:
name: 'Playwright Tests'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Set up pnpm
uses: pnpm/action-setup@v2
with:
version: 9.4.0
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
run: npx playwright install chromium
- name: Run Playwright tests
run: pnpm test:e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30

6
.gitignore vendored
View File

@ -42,3 +42,9 @@ site
# commit file ignore
app/commit.json
# Playwright specific
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@ -10,8 +10,10 @@
"deploy": "npm run build && wrangler pages deploy",
"build": "remix vite:build",
"dev": "node pre-start.cjs && remix vite:dev",
"test": "vitest --run",
"test": "vitest --run --exclude tests/e2e",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint app",
"lint:fix": "npm run lint -- --fix && prettier app --write",
"start:windows": "wrangler pages dev ./build/client",
@ -121,6 +123,7 @@
"devDependencies": {
"@blitz/eslint-plugin": "0.1.0",
"@cloudflare/workers-types": "^4.20241127.0",
"@playwright/test": "^1.51.0",
"@remix-run/dev": "^2.15.0",
"@types/diff": "^5.2.3",
"@types/dom-speech-recognition": "^0.0.4",

25
playwright.config.ts Normal file
View File

@ -0,0 +1,25 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm run dev',
port: 5173,
reuseExistingServer: !process.env.CI,
},
});

File diff suppressed because it is too large Load Diff

62
tests/README.md Normal file
View File

@ -0,0 +1,62 @@
# End-to-End Tests
This directory contains end-to-end tests using [Playwright](https://playwright.dev/).
## Running Tests
You can run the tests using the following commands:
```bash
# Run all tests
pnpm test:e2e
# Run tests in UI mode
pnpm test:e2e:ui
# Run only Chromium tests
pnpm test:e2e:chromium
```
## Test Structure
- `e2e/` - Contains all end-to-end tests
- `setup/` - Test utilities and helper functions
- `homepage.spec.ts` - Tests for the homepage
- `chat.spec.ts` - Tests for the chat interface
## Adding New Tests
When adding new tests:
1. Create a new `.spec.ts` file in the `e2e/` directory
2. Import the necessary utilities from `setup/test-utils.ts`
3. Use the `test.describe()` and `test()` functions to define test suites and cases
## Debugging Tests
You can debug tests by running them in UI mode:
```bash
pnpm test:e2e:ui
```
This will open the Playwright UI, where you can:
- See test results
- View screenshots and videos
- Trace test execution
- Re-run specific tests
## Test Data Attributes
For reliable selectors, we use data attributes in our components:
- `data-testid="chat-interface"` - The main chat interface
- `data-testid="message-input"` - The message input field
- `data-testid="send-button"` - The send message button
- `data-testid="assistant-message"` - Messages from the assistant
- `data-testid="model-selector"` - The model selection dropdown
- `data-testid="model-option"` - Individual model options in the dropdown
## Configuration
The Playwright configuration is in `playwright.config.ts` at the project root.

View File

@ -0,0 +1,13 @@
import { test, expect } from '@playwright/test';
test('should load the homepage', async ({ page }) => {
// Navigate to the homepage
await page.goto('/');
// Check that the page title is correct
const title = await page.title();
expect(title).toContain('Nut');
// Verify some key elements are visible
await expect(page.locator('header')).toBeVisible();
});

10
tests/e2e/problem.spec.ts Normal file
View File

@ -0,0 +1,10 @@
import { test, expect } from '@playwright/test';
test('Should be able to load a problem', async ({ page }) => {
await page.goto('/');
await page.getByRole('banner').locator('div').nth(1).click();
await page.getByRole('link', { name: 'Problems' }).click();
await page.getByRole('link', { name: 'Contact book tiny search icon' }).click();
await page.getByRole('link', { name: 'Load Problem' }).click();
await expect(page.getByText('Import the "problem" folder')).toBeVisible();
});

View File

@ -0,0 +1,37 @@
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';
/**
* Waits for the page to be fully loaded
*/
export async function waitForPageLoad(page: Page): Promise<void> {
await page.waitForLoadState('networkidle');
}
/**
* Checks if an element is visible on the page
*/
export async function expectElementVisible(page: Page, selector: string): Promise<void> {
await expect(page.locator(selector)).toBeVisible();
}
/**
* Fills an input field with text
*/
export async function fillInput(page: Page, selector: string, text: string): Promise<void> {
await page.locator(selector).fill(text);
}
/**
* Clicks a button on the page
*/
export async function clickButton(page: Page, selector: string): Promise<void> {
await page.locator(selector).click();
}
/**
* Gets the text content of an element
*/
export async function getElementText(page: Page, selector: string): Promise<string> {
return page.locator(selector).textContent() as Promise<string>;
}