Compare commits

..

12 Commits

Author SHA1 Message Date
57319e6712 fix: Replace remaining agent.open() calls in voice and cache tests
Some checks failed
Magnitude Tests / test (push) Failing after 1m4s
Fixed agent.open() in:
- tests/magnitude/09-voice.mag.ts (4 instances)
- tests/magnitude/cache-success.mag.ts (1 instance)

All Magnitude tests now use the correct agent.act('Navigate to...') API.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 17:35:47 +00:00
a553cc6130 fix: Replace agent.open() with agent.act('Navigate to...') in tests
Magnitude test framework doesn't have an agent.open() method.
Navigation must be done through agent.act() with natural language.

Fixed all 10 test cases in node-publishing.mag.ts:
- Happy path tests (3)
- Unhappy path tests (6)
- Integration test (1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 17:35:13 +00:00
5fc02f8d9b fix: Complete CI/CD testing infrastructure setup
**Environment Variables:**
- Fixed docker-compose.ci.yml to use correct environment variable names:
  - SURREALDB_JWT_SECRET (not SURREAL_JWT_SECRET)
  - GOOGLE_GENERATIVE_AI_API_KEY (not GOOGLE_API_KEY)
- Updated Gitea Actions workflow to match correct variable names

**Docker Configuration:**
- Removed SurrealDB health check (minimal scratch image lacks utilities)
- Added 10-second sleep before Next.js starts to wait for SurrealDB
- Updated magnitude service to run as root user for npm global installs
- Added xvfb-run to magnitude command for headless browser testing
- Updated Playwright Docker image from v1.49.1 to v1.56.1 in both files
- Added named volume for node_modules to persist installations

**Test Configuration:**
- Updated magnitude.config.ts to use Claude Sonnet 4.5 (20250929)
- Added headless: true to playwright.config.ts

**Testing:**
- CI test script (./scripts/test-ci-locally.sh) now works correctly
- All services start properly: SurrealDB → Next.js → Magnitude
- Playwright launches successfully in headless mode with xvfb-run

Note: Users need to ensure .env contains:
- ATPROTO_CLIENT_ID
- ATPROTO_REDIRECT_URI
- SURREALDB_JWT_SECRET
- GOOGLE_GENERATIVE_AI_API_KEY
- ANTHROPIC_API_KEY

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 15:03:01 +00:00
ef0725be58 chore: Add development utilities and MCP configuration
- Added debug-db.mjs script for debugging SurrealDB queries
- Added .mcp.json configuration for Playwright test MCP server
- Added Claude Code agents for Playwright test generation, planning, and healing

These tools assist with development and debugging workflows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:13:51 +00:00
b457e94ccb chore: Add dotenv as devDependency
Added for potential use in development scripts and testing utilities.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:13:13 +00:00
4abe8183d8 docs: Update AGENTS.md with CI testing infrastructure details
- Documented the containerized CI approach using docker-compose.ci.yml
- Added instructions for local CI testing with test-ci-locally.sh
- Explained benefits of the approach (reproducibility, simplicity)
- Updated .gitignore to ignore SurrealDB data directory

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:12:58 +00:00
bb650a3ed9 refactor: Simplify CI testing to use docker-compose directly
Instead of trying to use workflow runner tools (act/act_runner), the script
now directly runs the docker-compose command that CI uses. This is:

- More accurate (exact same command as CI)
- Simpler (no additional tools needed)
- Faster (no workflow interpretation overhead)
- Easier to debug (direct access to service logs)

The CI workflow literally runs `docker compose -f docker-compose.ci.yml`, so
running that locally is the most accurate way to test.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:12:35 +00:00
9df7278d55 fix: Use nektos/act instead of gitea/act_runner for local testing
gitea/act_runner is a runner daemon that needs to connect to a Gitea instance,
not a local testing tool. nektos/act is the correct tool for running workflows
locally, and it's compatible with both GitHub Actions and Gitea Actions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:10:42 +00:00
a8da8753f1 feat: Add CI testing infrastructure with act_runner support
- Created scripts/test-ci-locally.sh to test Gitea Actions workflows locally using act_runner
- Created docker-compose.ci.yml for containerized CI test environment
- Updated .gitea/workflows/magnitude.yml to use docker-compose for CI
- Added scripts/README.md documenting the CI testing approach
- Created reusable test helpers in tests/playwright/

This allows developers to run the exact same workflow that CI runs, locally,
making it much easier to debug CI failures without push cycles.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:07:16 +00:00
0ea3296885 refactor: Remove redundant standalone Dockerfile.playwright
Some checks failed
Magnitude Tests / test (push) Failing after 37s
The standalone Dockerfile is no longer needed since we integrated Playwright
directly into docker-compose.yml using the official Playwright image.

Benefits of removal:
- Simpler setup (no build step required)
- Less maintenance (one less file to keep updated)
- docker-compose.yml approach is more integrated and easier to use

The docker-compose service provides the same functionality with:
- Same base image (mcr.microsoft.com/playwright:v1.49.1-noble)
- Same non-root user execution
- Better integration with existing services

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 13:56:51 +00:00
39aea34026 feat: Integrate Playwright into docker-compose
Some checks failed
Magnitude Tests / test (push) Failing after 2m2s
Adds Playwright service to docker-compose.yml for easier test execution
and better integration with existing database services.

## Changes

- Add `playwright` service to docker-compose.yml:
  - Uses official Playwright image (mcr.microsoft.com/playwright:v1.49.1-noble)
  - Runs as non-root user (pwuser) for security
  - Uses host networking to access dev server on localhost:3000
  - Loads environment variables from .env
  - Uses `profiles: [test]` to keep it optional
  - Mounts node_modules volume to prevent permission issues

- Update documentation in AGENTS.md:
  - Replace standalone Docker commands with docker-compose usage
  - Document two usage patterns: `docker compose run` and `--profile test`
  - Explain benefits of integrated setup

## Usage

```bash
# Start database services
docker compose up -d

# Start dev server
pnpm dev

# Run Playwright tests in Docker
docker compose run --rm playwright
```

Or with profiles:

```bash
# Run tests one-off
docker compose --profile test run --rm playwright
```

## Benefits

- Unified infrastructure setup (database + tests)
- No need for separate Dockerfile build step
- Easier for new developers to run tests
- Consistent with existing docker-compose workflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 13:52:05 +00:00
1ff9a2cf4b feat: Add comprehensive testing infrastructure
Implements robust testing setup with Playwright global auth, reusable test
helpers, Docker support, and CI/CD integration with Gitea Actions.

## Changes

### Playwright Setup
- Add global auth setup with storage state reuse (tests/playwright/auth.setup.ts)
- Fix auth setup to clear existing state before fresh login
- Create reusable performOAuthLogin helper (tests/playwright/helpers.ts)
- Configure dotenv loading for environment variables in playwright.config.ts

### Magnitude Configuration
- Update to use Claude Sonnet 4.5 (claude-sonnet-4-5-20250514)
- Create reusable loginFlow helper (tests/magnitude/helpers.ts)
- Fix smoke test to check login page instead of non-existent homepage

### Docker Support
- Add Dockerfile.playwright with non-root user (pwuser) for security
- Uses official Playwright Docker image (mcr.microsoft.com/playwright:v1.49.1-noble)
- Provides consistent testing environment across users and CI/CD

### CI/CD Integration
- Add Gitea Actions workflow (.gitea/workflows/magnitude.yml)
- Runs Magnitude tests on every push and PR
- Starts SurrealDB and Next.js dev server automatically
- Uploads test results as artifacts (30-day retention)

### Documentation
- Add comprehensive testing setup docs to AGENTS.md:
  - Playwright Docker setup instructions
  - CI/CD with Gitea Actions
  - Testing framework separation (Playwright vs Magnitude)
  - Required secrets for CI/CD

### Testing Best Practices
- Separate Playwright (manual + global auth) from Magnitude (automated E2E)
- Reusable helpers reduce code duplication
- Both frameworks work independently

## Testing

-  Playwright auth setup test passes (5.6s)
-  Magnitude smoke test passes
-  OAuth flow works correctly with helper function

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 13:51:09 +00:00
24 changed files with 898 additions and 28 deletions

View File

@@ -0,0 +1,59 @@
---
name: playwright-test-generator
description: Use this agent when you need to create automated browser tests using Playwright. Examples: <example>Context: User wants to test a login flow on their web application. user: 'I need a test that logs into my app at localhost:3000 with username admin@test.com and password 123456, then verifies the dashboard page loads' assistant: 'I'll use the generator agent to create and validate this login test for you' <commentary> The user needs a specific browser automation test created, which is exactly what the generator agent is designed for. </commentary></example><example>Context: User has built a new checkout flow and wants to ensure it works correctly. user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the order?' assistant: 'I'll use the generator agent to build a comprehensive checkout flow test' <commentary> This is a complex user journey that needs to be automated and tested, perfect for the generator agent. </commentary></example>
tools: Glob, Grep, Read, mcp__playwright-test__browser_click, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_verify_element_visible, mcp__playwright-test__browser_verify_list_visible, mcp__playwright-test__browser_verify_text_visible, mcp__playwright-test__browser_verify_value, mcp__playwright-test__browser_wait_for, mcp__playwright-test__generator_read_log, mcp__playwright-test__generator_setup_page, mcp__playwright-test__generator_write_test
model: sonnet
color: blue
---
You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
application behavior.
# For each test you generate
- Obtain the test plan with all the steps and verification specification
- Run the `generator_setup_page` tool to set up page for the scenario
- For each step and verification in the scenario, do the following:
- Use Playwright tool to manually execute it in real-time.
- Use the step description as the intent for each Playwright tool call.
- Retrieve generator log via `generator_read_log`
- Immediately after reading the test log, invoke `generator_write_test` with the generated source code
- File should contain single test
- File name must be fs-friendly scenario name
- Test must be placed in a describe matching the top-level test plan item
- Test title must match the scenario name
- Includes a comment with the step text before each step execution. Do not duplicate comments if step requires
multiple actions.
- Always use best practices from the log when generating tests.
<example-generation>
For following plan:
```markdown file=specs/plan.md
### 1. Adding New Todos
**Seed:** `tests/seed.spec.ts`
#### 1.1 Add Valid Todo
**Steps:**
1. Click in the "What needs to be done?" input field
#### 1.2 Add Multiple Todos
...
```
Following file is generated:
```ts file=add-valid-todo.spec.ts
// spec: specs/plan.md
// seed: tests/seed.spec.ts
test.describe('Adding New Todos', () => {
test('Add Valid Todo', async { page } => {
// 1. Click in the "What needs to be done?" input field
await page.click(...);
...
});
});
```
</example-generation>

View File

@@ -0,0 +1,45 @@
---
name: playwright-test-healer
description: Use this agent when you need to debug and fix failing Playwright tests. Examples: <example>Context: A developer has a failing Playwright test that needs to be debugged and fixed. user: 'The login test is failing, can you fix it?' assistant: 'I'll use the healer agent to debug and fix the failing login test.' <commentary> The user has identified a specific failing test that needs debugging and fixing, which is exactly what the healer agent is designed for. </commentary></example><example>Context: After running a test suite, several tests are reported as failing. user: 'Test user-registration.spec.ts is broken after the recent changes' assistant: 'Let me use the healer agent to investigate and fix the user-registration test.' <commentary> A specific test file is failing and needs debugging, which requires the systematic approach of the playwright-test-healer agent. </commentary></example>
tools: Glob, Grep, Read, Write, Edit, MultiEdit, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_generate_locator, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_snapshot, mcp__playwright-test__test_debug, mcp__playwright-test__test_list, mcp__playwright-test__test_run
model: sonnet
color: red
---
You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and
resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix
broken Playwright tests using a methodical approach.
Your workflow:
1. **Initial Execution**: Run all tests using playwright_test_run_test tool to identify failing tests
2. **Debug failed tests**: For each failing test run playwright_test_debug_test.
3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
- Examine the error details
- Capture page snapshot to understand the context
- Analyze selectors, timing issues, or assertion failures
4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining:
- Element selectors that may have changed
- Timing and synchronization issues
- Data dependencies or test environment problems
- Application changes that broke test assumptions
5. **Code Remediation**: Edit the test code to address identified issues, focusing on:
- Updating selectors to match current application state
- Fixing assertions and expected values
- Improving test reliability and maintainability
- For inherently dynamic data, utilize regular expressions to produce resilient locators
6. **Verification**: Restart the test after each fix to validate the changes
7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
Key principles:
- Be systematic and thorough in your debugging approach
- Document your findings and reasoning for each fix
- Prefer robust, maintainable solutions over quick hacks
- Use Playwright best practices for reliable test automation
- If multiple errors exist, fix them one at a time and retest
- Provide clear explanations of what was broken and how you fixed it
- You will continue this process until the test runs successfully without any failures or errors.
- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
of the expected behavior.
- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
- Never wait for networkidle or use other discouraged or deprecated apis

View File

@@ -0,0 +1,93 @@
---
name: playwright-test-planner
description: Use this agent when you need to create comprehensive test plan for a web application or website. Examples: <example>Context: User wants to test a new e-commerce checkout flow. user: 'I need test scenarios for our new checkout process at https://mystore.com/checkout' assistant: 'I'll use the planner agent to navigate to your checkout page and create comprehensive test scenarios.' <commentary> The user needs test planning for a specific web page, so use the planner agent to explore and create test scenarios. </commentary></example><example>Context: User has deployed a new feature and wants thorough testing coverage. user: 'Can you help me test our new user dashboard at https://app.example.com/dashboard?' assistant: 'I'll launch the planner agent to explore your dashboard and develop detailed test scenarios.' <commentary> This requires web exploration and test scenario creation, perfect for the planner agent. </commentary></example>
tools: Glob, Grep, Read, Write, mcp__playwright-test__browser_click, mcp__playwright-test__browser_close, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_navigate_back, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_take_screenshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_wait_for, mcp__playwright-test__planner_setup_page
model: sonnet
color: green
---
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage
planning.
You will:
1. **Navigate and Explore**
- Invoke the `planner_setup_page` tool once to set up page before using any other tools
- Explore the browser snapshot
- Do not take screenshots unless absolutely necessary
- Use browser_* tools to navigate and discover interface
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
2. **Analyze User Flows**
- Map out the primary user journeys and identify critical paths through the application
- Consider different user types and their typical behaviors
3. **Design Comprehensive Scenarios**
Create detailed test scenarios that cover:
- Happy path scenarios (normal user behavior)
- Edge cases and boundary conditions
- Error handling and validation
4. **Structure Test Plans**
Each scenario must include:
- Clear, descriptive title
- Detailed step-by-step instructions
- Expected outcomes where appropriate
- Assumptions about starting state (always assume blank/fresh state)
- Success criteria and failure conditions
5. **Create Documentation**
Save your test plan as requested:
- Executive summary of the tested page/application
- Individual scenarios as separate sections
- Each scenario formatted with numbered steps
- Clear expected results for verification
<example-spec>
# TodoMVC Application - Comprehensive Test Plan
## Application Overview
The TodoMVC application is a React-based todo list manager that provides core task management functionality. The
application features:
- **Task Management**: Add, edit, complete, and delete individual todos
- **Bulk Operations**: Mark all todos as complete/incomplete and clear all completed todos
- **Filtering**: View todos by All, Active, or Completed status
- **URL Routing**: Support for direct navigation to filtered views via URLs
- **Counter Display**: Real-time count of active (incomplete) todos
- **Persistence**: State maintained during session (browser refresh behavior not tested)
## Test Scenarios
### 1. Adding New Todos
**Seed:** `tests/seed.spec.ts`
#### 1.1 Add Valid Todo
**Steps:**
1. Click in the "What needs to be done?" input field
2. Type "Buy groceries"
3. Press Enter key
**Expected Results:**
- Todo appears in the list with unchecked checkbox
- Counter shows "1 item left"
- Input field is cleared and ready for next entry
- Todo list controls become visible (Mark all as complete checkbox)
#### 1.2
...
</example-spec>
**Quality Standards**:
- Write steps that are specific enough for any tester to follow
- Include negative testing scenarios
- Ensure scenarios are independent and can be run in any order
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
professional formatting suitable for sharing with development and QA teams.

View File

@@ -0,0 +1,63 @@
# Gitea Actions workflow for running Magnitude tests
# Uses docker-compose.ci.yml for fully containerized testing
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: Create .env file for CI
run: |
cat > .env << EOF
SURREALDB_URL=ws://surrealdb:8000/rpc
SURREALDB_USER=root
SURREALDB_PASS=root
SURREALDB_NS=ponderants
SURREALDB_DB=main
SURREALDB_JWT_SECRET=${{ secrets.SURREALDB_JWT_SECRET }}
ATPROTO_CLIENT_ID=${{ secrets.ATPROTO_CLIENT_ID }}
ATPROTO_REDIRECT_URI=${{ secrets.ATPROTO_REDIRECT_URI }}
GOOGLE_GENERATIVE_AI_API_KEY=${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
DEEPGRAM_API_KEY=${{ secrets.DEEPGRAM_API_KEY }}
TEST_BLUESKY_HANDLE=${{ secrets.TEST_BLUESKY_HANDLE }}
TEST_BLUESKY_PASSWORD=${{ secrets.TEST_BLUESKY_PASSWORD }}
ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}
EOF
- name: Run tests with docker-compose
run: |
docker compose -f docker-compose.ci.yml --profile test up \
--abort-on-container-exit \
--exit-code-from magnitude
- name: Show logs on failure
if: failure()
run: |
echo "=== SurrealDB Logs ==="
docker compose -f docker-compose.ci.yml logs surrealdb
echo "=== Next.js Logs ==="
docker compose -f docker-compose.ci.yml logs nextjs
echo "=== Magnitude Logs ==="
docker compose -f docker-compose.ci.yml logs magnitude
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: magnitude-results
path: test-results/
retention-days: 30
- name: Cleanup
if: always()
run: docker compose -f docker-compose.ci.yml down -v

4
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
.pnpm-store/
# testing
/coverage
@@ -46,3 +47,6 @@ tests/playwright/.auth/
# claude settings (keep .claude/CLAUDE.md but ignore user settings)
.claude/settings.local.json
# surrealdb data
surreal/data/

11
.mcp.json Normal file
View File

@@ -0,0 +1,11 @@
{
"mcpServers": {
"playwright-test": {
"command": "npx",
"args": [
"playwright",
"run-test-mcp-server"
]
}
}
}

107
AGENTS.md
View File

@@ -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,111 @@ 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 using a fully containerized setup:
1. **Configuration**: `.gitea/workflows/magnitude.yml`
2. **Workflow steps** (simplified to just 2 steps!):
- Create `.env` file with secrets
- Run `docker compose -f docker-compose.ci.yml --profile test up`
- Upload test results and show logs on failure
- Cleanup
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
- `ATPROTO_CLIENT_ID`, `ATPROTO_REDIRECT_URI`
- `GOOGLE_API_KEY`, `DEEPGRAM_API_KEY`
- `SURREAL_JWT_SECRET`
4. **CI-specific docker-compose**: `docker-compose.ci.yml`
- Fully containerized (SurrealDB + Next.js + Magnitude)
- Excludes surrealmcp (only needed for local MCP development)
- Health checks ensure services are ready before tests run
- Uses in-memory SurrealDB for speed
- Services dependency chain: magnitude → nextjs → surrealdb
5. **Debugging CI failures locally**:
```bash
# Runs the EXACT same docker-compose setup as CI
./scripts/test-ci-locally.sh
# Or manually:
docker compose -f docker-compose.ci.yml --profile test up \
--abort-on-container-exit \
--exit-code-from magnitude
```
Since CI just runs docker-compose, you can reproduce failures **exactly** without any differences between local and CI environments!
6. **Test results**: Available as workflow artifacts for 30 days
7. **Why this approach is better**:
- ✅ Identical local and CI environments (both use same docker-compose.ci.yml)
- ✅ Fast debugging (no push-test-fail cycles)
- ✅ Self-contained (all dependencies in containers)
- ✅ Simple (just 2 steps in CI workflow)
- ✅ Reproducible (docker-compose ensures consistency)
**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

54
debug-db.mjs Normal file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
import Surreal from 'surrealdb';
const USER_DID = 'did:plc:sypdx6a4u2fblmclv6wbxjl3';
async function main() {
const db = new Surreal();
try {
console.log('Connecting to SurrealDB...');
await db.connect('ws://localhost:8000/rpc');
console.log('Signing in...');
await db.signin({
username: 'root',
password: 'root',
});
console.log('Using namespace/database...');
await db.use({
namespace: 'ponderants',
database: 'main',
});
console.log('\n===== ALL NODES IN DATABASE =====');
const allNodes = await db.query('SELECT * FROM node LIMIT 20');
console.log('Total nodes:', allNodes[0]?.length || 0);
console.log('Nodes:', JSON.stringify(allNodes[0], null, 2));
console.log(`\n===== NODES FOR USER ${USER_DID} (WITHOUT coords_3d filter) =====`);
const userNodesNoFilter = await db.query(
'SELECT id, title, user_did, coords_3d FROM node WHERE user_did = $userDid',
{ userDid: USER_DID }
);
console.log('Count:', userNodesNoFilter[0]?.length || 0);
console.log('Nodes:', JSON.stringify(userNodesNoFilter[0], null, 2));
console.log(`\n===== NODES FOR USER ${USER_DID} (WITH coords_3d != NONE filter) =====`);
const userNodesWithFilter = await db.query(
'SELECT id, title, user_did, coords_3d FROM node WHERE user_did = $userDid AND coords_3d != NONE',
{ userDid: USER_DID }
);
console.log('Count:', userNodesWithFilter[0]?.length || 0);
console.log('Nodes:', JSON.stringify(userNodesWithFilter[0], null, 2));
} catch (error) {
console.error('Error:', error);
console.error('Stack:', error.stack);
} finally {
await db.close();
}
}
main();

89
docker-compose.ci.yml Normal file
View File

@@ -0,0 +1,89 @@
# Simplified docker-compose for CI/CD environments
# Only includes services needed for testing (excludes surrealmcp)
services:
surrealdb:
image: surrealdb/surrealdb:latest
ports:
- "8000:8000"
command:
- start
- --log
- trace
- --user
- ${SURREALDB_USER:-root}
- --pass
- ${SURREALDB_PASS:-root}
- memory
environment:
- SURREAL_LOG=trace
nextjs:
image: node:20-alpine
working_dir: /app
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
- /app/.next
environment:
- SURREALDB_URL=ws://surrealdb:8000/rpc
- SURREALDB_USER=${SURREALDB_USER:-root}
- SURREALDB_PASS=${SURREALDB_PASS:-root}
- SURREALDB_NS=${SURREALDB_NS:-ponderants}
- SURREALDB_DB=${SURREALDB_DB:-main}
- SURREALDB_JWT_SECRET=${SURREALDB_JWT_SECRET}
- ATPROTO_CLIENT_ID=${ATPROTO_CLIENT_ID}
- ATPROTO_REDIRECT_URI=${ATPROTO_REDIRECT_URI}
- GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY}
- DEEPGRAM_API_KEY=${DEEPGRAM_API_KEY}
- TEST_BLUESKY_HANDLE=${TEST_BLUESKY_HANDLE}
- TEST_BLUESKY_PASSWORD=${TEST_BLUESKY_PASSWORD}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- NODE_ENV=development
command: >
sh -c "
npm install -g pnpm &&
pnpm install --frozen-lockfile &&
echo 'Waiting for SurrealDB to be ready...' &&
sleep 10 &&
pnpm dev
"
depends_on:
- surrealdb
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000"]
interval: 5s
timeout: 3s
retries: 20
start_period: 40s
magnitude:
image: mcr.microsoft.com/playwright:v1.56.1-noble
working_dir: /app
user: root
network_mode: "service:nextjs"
volumes:
- .:/app
- node_modules:/app/node_modules
environment:
- TEST_BLUESKY_HANDLE=${TEST_BLUESKY_HANDLE}
- TEST_BLUESKY_PASSWORD=${TEST_BLUESKY_PASSWORD}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- HOME=/root
command: >
sh -c "
npm install -g pnpm &&
pnpm install --frozen-lockfile &&
npx wait-on http://localhost:3000 --timeout 120000 &&
xvfb-run --auto-servernum --server-args='-screen 0 1280x960x24' npx magnitude
"
depends_on:
nextjs:
condition: service_healthy
profiles:
- test
volumes:
node_modules:

View File

@@ -32,3 +32,26 @@ services:
- "8080:8080"
depends_on:
- surrealdb
playwright:
image: mcr.microsoft.com/playwright:v1.56.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

View File

@@ -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-20250929',
};

View File

@@ -49,6 +49,7 @@
"@types/react": "latest",
"@types/react-dom": "latest",
"@types/three": "^0.181.0",
"dotenv": "^17.2.3",
"eslint": "latest",
"eslint-config-next": "latest",
"jiti": "^2.6.1",

View File

@@ -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',
@@ -12,6 +16,7 @@ export default defineConfig({
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
headless: true,
},
projects: [

9
pnpm-lock.yaml generated
View File

@@ -105,6 +105,9 @@ importers:
'@types/three':
specifier: ^0.181.0
version: 0.181.0
dotenv:
specifier: ^17.2.3
version: 17.2.3
eslint:
specifier: latest
version: 9.39.1(jiti@2.6.1)
@@ -1710,6 +1713,10 @@ packages:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
dotenv@17.2.3:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
engines: {node: '>=12'}
draco3d@1.5.7:
resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==}
@@ -5034,6 +5041,8 @@ snapshots:
dotenv@16.6.1: {}
dotenv@17.2.3: {}
draco3d@1.5.7: {}
dunder-proto@1.0.1:

85
scripts/README.md Normal file
View File

@@ -0,0 +1,85 @@
# Development Scripts
## test-ci-locally.sh
Tests the CI workflow locally by running the **exact same docker-compose command** that the Gitea Actions workflow runs.
### Purpose
When CI tests fail, this script reproduces the exact CI environment locally to debug issues without repeatedly pushing to trigger CI runs. It runs `docker-compose.ci.yml` with the same parameters as the CI workflow, so you're testing in an identical environment.
### Usage
```bash
./scripts/test-ci-locally.sh
```
Or run docker-compose directly (this is what the script does):
```bash
docker compose -f docker-compose.ci.yml --profile test up \
--abort-on-container-exit \
--exit-code-from magnitude
```
### What it does
1. Checks that `.env` file exists
2. Runs `docker compose -f docker-compose.ci.yml --profile test up`
3. This starts all services:
- **surrealdb**: In-memory database with health check
- **nextjs**: Node.js container running `pnpm dev` with health check
- **magnitude**: Playwright container running the test suite
4. Waits for tests to complete
5. Exits with magnitude's exit code
6. Shows service logs on failure
7. Cleans up containers and volumes
### Requirements
- Docker and docker-compose installed
- `.env` file with test credentials
### Services Architecture
The script starts a containerized test environment with proper health checks and dependencies:
```
magnitude (Playwright container - runs tests)
↓ depends on (waits for health check)
nextjs (Node.js container - runs pnpm dev)
↓ depends on (waits for health check)
surrealdb (SurrealDB container - in-memory mode)
```
All services share the same network:
- Next.js accesses SurrealDB via `ws://surrealdb:8000/rpc`
- Magnitude accesses Next.js via `http://localhost:3000`
### Why This Approach?
This is simpler and more accurate than using workflow runner tools like `act` or `act_runner` because:
1. **Identical to CI**: The CI workflow (`.gitea/workflows/magnitude.yml`) literally runs this docker-compose command, so you're testing the exact same thing
2. **No Additional Tools**: Doesn't require `act`, `act_runner`, or any workflow execution tools
3. **Direct Debugging**: Runs the actual test commands directly, making it easier to see what's happening
4. **Faster**: No overhead from workflow interpretation or runner setup
### Debugging CI Failures
If Gitea Actions fail:
1. Check the workflow logs for errors in Gitea UI
2. Run `./scripts/test-ci-locally.sh` to reproduce **exactly**
3. The script will show the same output as CI
4. Debug with docker-compose logs if needed:
```bash
docker compose -f docker-compose.ci.yml logs surrealdb
docker compose -f docker-compose.ci.yml logs nextjs
docker compose -f docker-compose.ci.yml logs magnitude
```
5. Fix issues locally
6. Run script again to verify fix
7. Commit and push once tests pass locally
This is **much** faster than debugging via CI push cycles and gives you identical results!

62
scripts/test-ci-locally.sh Executable file
View File

@@ -0,0 +1,62 @@
#!/bin/bash
# Script to test CI workflow locally by running the exact same docker-compose command as CI
# This runs docker-compose.ci.yml which is what the Gitea Actions workflow uses
set -e # Exit on error
echo "========================================="
echo "Testing CI Workflow Locally"
echo "========================================="
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check if .env exists
if [ ! -f .env ]; then
echo -e "${RED}Error: .env file not found!${NC}"
echo "Please create .env file with required variables"
exit 1
fi
echo -e "${YELLOW}Running the exact same docker-compose command as CI${NC}"
echo -e "${YELLOW}This executes: docker compose -f docker-compose.ci.yml --profile test up${NC}"
echo ""
# Cleanup function
cleanup() {
echo -e "${YELLOW}Cleaning up containers and volumes...${NC}"
docker compose -f docker-compose.ci.yml down -v
}
# Trap cleanup on exit
trap cleanup EXIT
# Run the exact same command that CI runs
docker compose -f docker-compose.ci.yml --profile test up \
--abort-on-container-exit \
--exit-code-from magnitude || {
echo ""
echo -e "${RED}=========================================${NC}"
echo -e "${RED}Tests failed!${NC}"
echo -e "${RED}=========================================${NC}"
echo ""
echo -e "${YELLOW}Showing service logs:${NC}"
echo ""
echo "=== SurrealDB Logs ==="
docker compose -f docker-compose.ci.yml logs --tail=50 surrealdb
echo ""
echo "=== Next.js Logs ==="
docker compose -f docker-compose.ci.yml logs --tail=50 nextjs
echo ""
echo "=== Magnitude Logs ==="
docker compose -f docker-compose.ci.yml logs --tail=50 magnitude
exit 1
}
echo ""
echo -e "${GREEN}=========================================${NC}"
echo -e "${GREEN}All tests passed!${NC}"
echo -e "${GREEN}=========================================${NC}"

View File

@@ -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');
});

View File

@@ -2,7 +2,7 @@ import { test } from 'magnitude-test';
test('[Happy Path] User can have a full voice conversation with AI', async (agent) => {
// Act: Navigate to chat page (assumes user is already authenticated)
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Check: Initial state - voice button shows "Start Voice Conversation"
await agent.check('A button with text "Start Voice Conversation" is visible');
@@ -76,7 +76,7 @@ test('[Happy Path] User can have a full voice conversation with AI', async (agen
});
test('[Unhappy Path] Voice mode handles errors gracefully', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Act: Start voice mode
await agent.act('Click the "Start Voice Conversation" button');
@@ -93,7 +93,7 @@ test('[Unhappy Path] Voice mode handles errors gracefully', async (agent) => {
});
test('[Happy Path] Text input is disabled during voice mode', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Check: Text input is enabled initially
await agent.check('The text input field "Or type your thoughts here..." is enabled');
@@ -112,7 +112,7 @@ test('[Happy Path] Text input is disabled during voice mode', async (agent) => {
});
test('[Happy Path] User can type a message while voice mode is idle', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Act: Type a message in the text input
await agent.act('Type "This is a text message" into the text input field');

View File

@@ -8,7 +8,7 @@
import { test } from 'magnitude-test';
test('Node publishes successfully with cache (no warnings)', async (agent) => {
await agent.open('http://localhost:3000');
await agent.act('Navigate to http://localhost:3000');
// Login
await agent.act('Click the "Log in with Bluesky" button');

View 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;

View File

@@ -12,7 +12,7 @@ import { test } from 'magnitude-test';
// ============================================================================
test('User can publish a node from conversation', async (agent) => {
await agent.open('http://localhost:3000');
await agent.act('Navigate to http://localhost:3000');
// Step 1: Login with Bluesky
await agent.act('Click the "Log in with Bluesky" button');
@@ -48,7 +48,7 @@ test('User can publish a node from conversation', async (agent) => {
test('User can edit node draft before publishing', async (agent) => {
// Assumes user is already logged in from previous test
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Start conversation
await agent.act('Type "Testing the edit flow" and press Enter');
@@ -71,7 +71,7 @@ test('User can edit node draft before publishing', async (agent) => {
});
test('User can cancel node draft without publishing', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Start conversation
await agent.act('Type "Test cancellation" and press Enter');
@@ -93,7 +93,7 @@ test('User can cancel node draft without publishing', async (agent) => {
test('Cannot publish node without authentication', async (agent) => {
// Open edit page directly without being logged in
await agent.open('http://localhost:3000/edit');
await agent.act('Navigate to http://localhost:3000/edit');
await agent.check('Shows empty state message');
await agent.check('Message says "No Node Draft"');
@@ -101,7 +101,7 @@ test('Cannot publish node without authentication', async (agent) => {
});
test('Cannot publish node with empty title', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Create draft
await agent.act('Type "Test empty title validation" and press Enter');
@@ -116,7 +116,7 @@ test('Cannot publish node with empty title', async (agent) => {
});
test('Cannot publish node with empty content', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Create draft
await agent.act('Type "Test empty content validation" and press Enter');
@@ -131,7 +131,7 @@ test('Cannot publish node with empty content', async (agent) => {
});
test('Shows error notification if publish fails', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Create draft
await agent.act('Type "Test error handling" and press Enter');
@@ -149,7 +149,7 @@ test('Shows error notification if publish fails', async (agent) => {
});
test('Handles long content with truncation', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
// Create a very long message
const longMessage = 'A'.repeat(500) + ' This is a test of long content truncation for Bluesky posts.';
@@ -168,7 +168,7 @@ test('Handles long content with truncation', async (agent) => {
});
test('Shows warning when cache fails but publish succeeds', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Navigate to http://localhost:3000/chat');
await agent.act('Type "Test cache failure graceful degradation" and press Enter');
await agent.check('AI responds');
@@ -190,7 +190,7 @@ test('Shows warning when cache fails but publish succeeds', async (agent) => {
test('Complete user journey: Login → Converse → Publish → View', async (agent) => {
// Full end-to-end test
await agent.open('http://localhost:3000');
await agent.act('Navigate to http://localhost:3000');
// Login
await agent.act('Login with Bluesky')

View File

@@ -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}`);
});

View 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;

View File

@@ -0,0 +1,7 @@
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
test('seed', async ({ page }) => {
// generate code here.
});
});