Compare commits
2 Commits
a520814771
...
39aea34026
| Author | SHA1 | Date | |
|---|---|---|---|
| 39aea34026 | |||
| 1ff9a2cf4b |
75
.gitea/workflows/magnitude.yml
Normal file
75
.gitea/workflows/magnitude.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
# Gitea Actions workflow for running Magnitude tests
|
||||
name: Magnitude Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, development]
|
||||
pull_request:
|
||||
branches: [main, development]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Start SurrealDB
|
||||
run: |
|
||||
docker run -d \
|
||||
--name surrealdb \
|
||||
-p 8000:8000 \
|
||||
-e SURREAL_USER=${{ secrets.SURREALDB_USER }} \
|
||||
-e SURREAL_PASS=${{ secrets.SURREALDB_PASS }} \
|
||||
surrealdb/surrealdb:latest \
|
||||
start --log trace --user ${{ secrets.SURREALDB_USER }} --pass ${{ secrets.SURREALDB_PASS }} memory
|
||||
|
||||
- name: Wait for SurrealDB
|
||||
run: sleep 5
|
||||
|
||||
- name: Start Next.js dev server
|
||||
run: pnpm dev &
|
||||
env:
|
||||
SURREALDB_URL: ws://localhost:8000/rpc
|
||||
SURREALDB_USER: ${{ secrets.SURREALDB_USER }}
|
||||
SURREALDB_PASS: ${{ secrets.SURREALDB_PASS }}
|
||||
SURREALDB_NS: ${{ secrets.SURREALDB_NS }}
|
||||
SURREALDB_DB: ${{ secrets.SURREALDB_DB }}
|
||||
ATPROTO_CLIENT_ID: ${{ secrets.ATPROTO_CLIENT_ID }}
|
||||
ATPROTO_REDIRECT_URI: ${{ secrets.ATPROTO_REDIRECT_URI }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
||||
SURREAL_JWT_SECRET: ${{ secrets.SURREAL_JWT_SECRET }}
|
||||
TEST_BLUESKY_HANDLE: ${{ secrets.TEST_BLUESKY_HANDLE }}
|
||||
TEST_BLUESKY_PASSWORD: ${{ secrets.TEST_BLUESKY_PASSWORD }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
- name: Wait for Next.js server
|
||||
run: npx wait-on http://localhost:3000 --timeout 120000
|
||||
|
||||
- name: Run Magnitude tests
|
||||
run: npx magnitude
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
TEST_BLUESKY_HANDLE: ${{ secrets.TEST_BLUESKY_HANDLE }}
|
||||
TEST_BLUESKY_PASSWORD: ${{ secrets.TEST_BLUESKY_PASSWORD }}
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: magnitude-results
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
84
AGENTS.md
84
AGENTS.md
@@ -102,9 +102,12 @@ These credentials should be used for all automated testing (Magnitude, Playwrigh
|
||||
- ✅ All manual testing with Playwright MCP completed and verified
|
||||
- ✅ All Magnitude tests written and cover all verified functionality
|
||||
- ✅ Database verified for expected state after operations (e.g., deletions actually removed records)
|
||||
- ✅ Run magnitude tests for current feature FIRST: `pnpm test tests/magnitude/your-feature.mag.ts`
|
||||
- ✅ Verify current feature tests pass
|
||||
- ✅ Run ALL magnitude tests: `pnpm test`
|
||||
- ✅ All tests passing
|
||||
- ✅ Verify ENTIRE test suite passes
|
||||
- ✅ No console errors or warnings in production code paths
|
||||
- **CRITICAL**: Do NOT commit until ALL tests pass - feature tests AND full test suite
|
||||
- Only commit after ALL checklist items are complete
|
||||
|
||||
7. **Documentation**:
|
||||
@@ -114,9 +117,88 @@ These credentials should be used for all automated testing (Magnitude, Playwrigh
|
||||
**Testing Resources**:
|
||||
- Playwright Global Setup/Teardown: https://playwright.dev/docs/test-global-setup-teardown
|
||||
- Playwright Test Agents: https://playwright.dev/docs/test-agents
|
||||
- Playwright Docker: https://playwright.dev/docs/docker
|
||||
- Magnitude.run Documentation: https://magnitude.run/docs
|
||||
- Project Test README: `tests/README.md`
|
||||
|
||||
**Playwright Docker Setup**:
|
||||
|
||||
Playwright is integrated into docker-compose for consistent testing environments:
|
||||
|
||||
1. **Run Playwright tests with docker-compose**:
|
||||
```bash
|
||||
# Start database services
|
||||
docker compose up -d
|
||||
|
||||
# Start Next.js dev server
|
||||
pnpm dev
|
||||
|
||||
# Run Playwright tests in Docker (in another terminal)
|
||||
docker compose run --rm playwright
|
||||
```
|
||||
|
||||
2. **Alternative: Use the 'test' profile**:
|
||||
```bash
|
||||
# Start all services including Playwright
|
||||
docker compose --profile test up
|
||||
|
||||
# Or run tests one-off without keeping services up
|
||||
docker compose --profile test run --rm playwright
|
||||
```
|
||||
|
||||
3. **Benefits**:
|
||||
- Non-root user execution (pwuser) for security
|
||||
- Consistent browser versions across environments
|
||||
- Integrated with existing docker-compose setup
|
||||
- Uses host networking to access dev server on localhost:3000
|
||||
- Node modules volume prevents permission issues
|
||||
|
||||
4. **Configuration**:
|
||||
- Environment variables loaded from .env file
|
||||
- Uses `network_mode: host` to access dev server
|
||||
- Runs with `profiles: [test]` to keep it optional
|
||||
|
||||
**CI/CD with Gitea Actions**:
|
||||
|
||||
Magnitude tests run automatically on every push and pull request via Gitea Actions:
|
||||
|
||||
1. **Configuration**: `.gitea/workflows/magnitude.yml`
|
||||
|
||||
2. **Workflow steps**:
|
||||
- Checkout code
|
||||
- Setup Node.js and pnpm
|
||||
- Start SurrealDB in Docker
|
||||
- Start Next.js dev server with environment variables
|
||||
- Run Magnitude tests
|
||||
- Upload test results as artifacts
|
||||
|
||||
3. **Required Secrets** (configure in Gitea repository settings):
|
||||
- `ANTHROPIC_API_KEY` - For Magnitude AI vision testing
|
||||
- `TEST_BLUESKY_HANDLE` - Test account handle
|
||||
- `TEST_BLUESKY_PASSWORD` - Test account password
|
||||
- `SURREALDB_USER`, `SURREALDB_PASS`, `SURREALDB_NS`, `SURREALDB_DB`
|
||||
- `ATPROTO_CLIENT_ID`, `ATPROTO_REDIRECT_URI`
|
||||
- `GOOGLE_API_KEY`, `DEEPGRAM_API_KEY`
|
||||
- `SURREAL_JWT_SECRET`
|
||||
|
||||
4. **Test results**: Available as workflow artifacts for 30 days
|
||||
|
||||
**Testing Framework Separation**:
|
||||
|
||||
- **Playwright**: Used for manual testing with Playwright MCP and global auth setup
|
||||
- Location: `tests/playwright/`
|
||||
- Helpers: `tests/playwright/helpers.ts`
|
||||
- Auth setup: `tests/playwright/auth.setup.ts`
|
||||
- Run: `npx playwright test`
|
||||
|
||||
- **Magnitude**: Used for automated end-to-end testing in development and CI/CD
|
||||
- Location: `tests/magnitude/`
|
||||
- Helpers: `tests/magnitude/helpers.ts`
|
||||
- Configuration: `magnitude.config.ts` (uses Claude Sonnet 4.5)
|
||||
- Run: `npx magnitude` or `pnpm test`
|
||||
|
||||
Both frameworks are independent and can be used separately or together depending on the testing need.
|
||||
|
||||
You are an expert-level, full-stack AI coding agent. Your task is to implement
|
||||
the "Ponderants" application. Product Vision: Ponderants is an AI-powered
|
||||
thought partner that interviews a user to capture, structure, and visualize
|
||||
|
||||
30
Dockerfile.playwright
Normal file
30
Dockerfile.playwright
Normal file
@@ -0,0 +1,30 @@
|
||||
# Dockerfile for Playwright testing environment
|
||||
# Based on official Playwright Docker image with non-root user setup
|
||||
|
||||
FROM mcr.microsoft.com/playwright:v1.49.1-noble
|
||||
|
||||
# Create a non-root user for running tests
|
||||
RUN useradd -ms /bin/bash pwuser && \
|
||||
mkdir -p /home/pwuser/app && \
|
||||
chown -R pwuser:pwuser /home/pwuser
|
||||
|
||||
# Switch to non-root user
|
||||
USER pwuser
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /home/pwuser/app
|
||||
|
||||
# Copy package files
|
||||
COPY --chown=pwuser:pwuser package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install pnpm globally for the user
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy the rest of the application
|
||||
COPY --chown=pwuser:pwuser . .
|
||||
|
||||
# Run Playwright tests
|
||||
CMD ["pnpm", "exec", "playwright", "test"]
|
||||
@@ -32,3 +32,26 @@ services:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- surrealdb
|
||||
|
||||
playwright:
|
||||
image: mcr.microsoft.com/playwright:v1.49.1-noble
|
||||
working_dir: /home/pwuser/app
|
||||
user: pwuser
|
||||
network_mode: host
|
||||
volumes:
|
||||
- .:/home/pwuser/app
|
||||
- /home/pwuser/app/node_modules
|
||||
environment:
|
||||
- TEST_BLUESKY_HANDLE=${TEST_BLUESKY_HANDLE}
|
||||
- TEST_BLUESKY_PASSWORD=${TEST_BLUESKY_PASSWORD}
|
||||
- PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL:-http://localhost:3000}
|
||||
command: >
|
||||
sh -c "
|
||||
npm install -g pnpm &&
|
||||
pnpm install --frozen-lockfile &&
|
||||
npx playwright test
|
||||
"
|
||||
depends_on:
|
||||
- surrealdb
|
||||
profiles:
|
||||
- test
|
||||
|
||||
@@ -6,4 +6,6 @@ export default {
|
||||
tests: 'tests/magnitude/**/*.mag.ts',
|
||||
// Run tests in headless mode to avoid window focus issues
|
||||
headless: true,
|
||||
// Use Claude Sonnet 4.5 for best performance
|
||||
model: 'anthropic:claude-sonnet-4-5-20250514',
|
||||
};
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/playwright',
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { test } from 'magnitude-test';
|
||||
|
||||
test('Application boots and displays homepage', async (agent) => {
|
||||
// Act: Navigate to the homepage (uses the default URL
|
||||
// from magnitude.config.ts)
|
||||
await agent.act('Navigate to the homepage');
|
||||
test('Application boots and displays login page', async (agent) => {
|
||||
// Act: Navigate to the root URL (should redirect to /login)
|
||||
await agent.act('Navigate to http://localhost:3000');
|
||||
|
||||
// Check: Verify that the homepage text is visible
|
||||
// This confirms the Next.js app is serving content.
|
||||
await agent.check('The text "Ponderants" is visible on the screen');
|
||||
// Check: Verify the login page loads with expected elements
|
||||
await agent.check('The text "Ponderants" or "Log in with Bluesky" is visible on the screen');
|
||||
await agent.check('A login form or button is displayed');
|
||||
});
|
||||
|
||||
59
tests/magnitude/helpers.ts
Normal file
59
tests/magnitude/helpers.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Reusable test helpers for Magnitude tests
|
||||
*
|
||||
* These helpers encapsulate common test patterns to reduce code duplication
|
||||
* and make tests more maintainable.
|
||||
*/
|
||||
|
||||
const TEST_HANDLE = process.env.TEST_BLUESKY_HANDLE;
|
||||
const TEST_PASSWORD = process.env.TEST_BLUESKY_PASSWORD;
|
||||
|
||||
if (!TEST_HANDLE || !TEST_PASSWORD) {
|
||||
throw new Error('TEST_BLUESKY_HANDLE and TEST_BLUESKY_PASSWORD must be set in .env');
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs complete OAuth login flow
|
||||
*
|
||||
* This function navigates to the login page and completes the full OAuth flow:
|
||||
* 1. Navigate to /login
|
||||
* 2. Enter handle and click "Log in with Bluesky"
|
||||
* 3. Wait for redirect to Bluesky OAuth page
|
||||
* 4. Enter password and click "Sign in"
|
||||
* 5. Click "Authorize" button
|
||||
* 6. Wait for redirect to /chat
|
||||
*
|
||||
* @param agent - The Magnitude test agent
|
||||
*/
|
||||
export async function loginFlow(agent: any) {
|
||||
// Navigate to login page
|
||||
await agent.act('Navigate to /login');
|
||||
|
||||
// Fill in handle and initiate OAuth
|
||||
await agent.act(`Type "${TEST_HANDLE}" into the "Your Handle" input field`);
|
||||
await agent.act('Click the "Log in with Bluesky" button');
|
||||
|
||||
// Wait for redirect to Bluesky OAuth page
|
||||
await agent.check('The page URL contains "bsky.social"');
|
||||
|
||||
// Fill in credentials on Bluesky OAuth page
|
||||
await agent.act(`Type "${TEST_HANDLE}" into the username/identifier field`);
|
||||
await agent.act(`Type "${TEST_PASSWORD}" into the password field`);
|
||||
|
||||
// Submit login form
|
||||
await agent.act('Click the submit/authorize button');
|
||||
|
||||
// Wait for and click authorize button
|
||||
await agent.act('Click the "Authorize" button');
|
||||
|
||||
// Verify successful login
|
||||
await agent.check('The page URL contains "/chat"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test credentials for use in tests that need them directly
|
||||
*/
|
||||
export const TEST_CREDENTIALS = {
|
||||
handle: TEST_HANDLE,
|
||||
password: TEST_PASSWORD,
|
||||
} as const;
|
||||
@@ -1,12 +1,29 @@
|
||||
import { test as setup, expect } from '@playwright/test';
|
||||
import { test as setup } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { performOAuthLogin } from './helpers';
|
||||
|
||||
const authFile = 'tests/playwright/.auth/user.json';
|
||||
|
||||
setup('authenticate', async ({ page }) => {
|
||||
// For now, just create an empty auth file
|
||||
// TODO: Implement actual OAuth flow when test credentials are available
|
||||
console.log('[Auth Setup] Skipping authentication - implement OAuth flow with test credentials');
|
||||
console.log('[Auth Setup] Starting OAuth authentication flow');
|
||||
|
||||
// Save empty state for now
|
||||
// Clear any existing auth state file to ensure fresh login
|
||||
if (fs.existsSync(authFile)) {
|
||||
fs.unlinkSync(authFile);
|
||||
console.log('[Auth Setup] Cleared existing auth state');
|
||||
}
|
||||
|
||||
// Perform OAuth login using reusable helper
|
||||
await performOAuthLogin(page);
|
||||
|
||||
// Ensure the auth directory exists
|
||||
const authDir = path.dirname(authFile);
|
||||
if (!fs.existsSync(authDir)) {
|
||||
fs.mkdirSync(authDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Save authenticated state
|
||||
await page.context().storageState({ path: authFile });
|
||||
console.log(`[Auth Setup] Saved authentication state to ${authFile}`);
|
||||
});
|
||||
|
||||
78
tests/playwright/helpers.ts
Normal file
78
tests/playwright/helpers.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Reusable test helpers for Playwright tests
|
||||
*
|
||||
* These helpers encapsulate common test patterns to reduce code duplication
|
||||
* and make tests more maintainable.
|
||||
*/
|
||||
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
const TEST_HANDLE = process.env.TEST_BLUESKY_HANDLE;
|
||||
const TEST_PASSWORD = process.env.TEST_BLUESKY_PASSWORD;
|
||||
|
||||
if (!TEST_HANDLE || !TEST_PASSWORD) {
|
||||
throw new Error(
|
||||
'TEST_BLUESKY_HANDLE and TEST_BLUESKY_PASSWORD must be set in .env file'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs complete OAuth login flow
|
||||
*
|
||||
* This function navigates to the login page and completes the full OAuth flow:
|
||||
* 1. Navigate to /login
|
||||
* 2. Enter handle and click "Log in with Bluesky"
|
||||
* 3. Wait for redirect to Bluesky OAuth page
|
||||
* 4. Enter password and click "Sign in"
|
||||
* 5. Click "Authorize" button
|
||||
* 6. Wait for redirect to /chat
|
||||
* 7. Verify authentication successful
|
||||
*
|
||||
* @param page - The Playwright Page object
|
||||
*/
|
||||
export async function performOAuthLogin(page: Page) {
|
||||
console.log('[Helper] Starting OAuth login flow');
|
||||
|
||||
// Navigate to login page
|
||||
await page.goto('/login');
|
||||
|
||||
// Fill in handle and initiate OAuth
|
||||
await page.getByLabel('Your Handle').fill(TEST_HANDLE!);
|
||||
|
||||
// Click button and wait for navigation to Bluesky OAuth page
|
||||
await Promise.all([
|
||||
page.waitForURL('**/bsky.social/**', { timeout: 30000 }),
|
||||
page.getByRole('button', { name: 'Log in with Bluesky' }).click(),
|
||||
]);
|
||||
console.log('[Helper] Redirected to Bluesky OAuth page');
|
||||
|
||||
// The identifier is pre-filled from our login flow, just fill in password
|
||||
// Use getByRole to avoid strict mode violations with multiple "Password" labeled elements
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill(TEST_PASSWORD!);
|
||||
|
||||
// Click Sign in button
|
||||
await page.getByRole('button', { name: /sign in/i }).click();
|
||||
|
||||
// Wait for the OAuth authorization page by looking for the Authorize button
|
||||
await page.getByRole('button', { name: 'Authorize' }).waitFor({ timeout: 10000 });
|
||||
console.log('[Helper] On OAuth authorization page');
|
||||
|
||||
// Click Authorize button to grant access and wait for redirect
|
||||
await Promise.all([
|
||||
page.waitForURL('**/chat', { timeout: 20000 }),
|
||||
page.getByRole('button', { name: 'Authorize' }).click(),
|
||||
]);
|
||||
console.log('[Helper] Successfully authorized, redirected to /chat');
|
||||
|
||||
// Verify we're actually logged in by checking for Profile nav link
|
||||
await expect(page.getByText('Profile')).toBeVisible({ timeout: 5000 });
|
||||
console.log('[Helper] Verified authentication successful');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test credentials for use in tests that need them directly
|
||||
*/
|
||||
export const TEST_CREDENTIALS = {
|
||||
handle: TEST_HANDLE,
|
||||
password: TEST_PASSWORD,
|
||||
} as const;
|
||||
Reference in New Issue
Block a user