From aa91ba2ebe24dfcab065c7f1e7ddf6dea66fac08 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 18:56:22 +0100 Subject: [PATCH 01/11] Add Playwright migration design and development workflow - Document complete design for migrating from Selenium to Playwright - Include technical approach, validation plan, and cost-benefit analysis - Add development workflow section to CLAUDE.md specifying PR-only changes --- CLAUDE.md | 202 ++++++++++++ .../2026-02-01-playwright-migration-design.md | 308 ++++++++++++++++++ 2 files changed, 510 insertions(+) create mode 100644 CLAUDE.md create mode 100644 docs/plans/2026-02-01-playwright-migration-design.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..31666edb4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,202 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +codebar planner is a Rails 7.2 application for managing [codebar.io](https://codebar.io) members and events. It handles workshop/event scheduling, member registration, invitations, RSVPs, and feedback collection for coding workshops organized by codebar chapters. + +## Development Setup + +### Docker (Recommended) + +- **Initial setup**: `bin/dup` - builds container, sets up database with seed data +- **Start container**: `bin/dstart` - starts existing container +- **Start Rails server**: `bin/dserver` - runs server on http://localhost:3000 +- **Run tests**: `bin/drspec [path]` - runs RSpec tests, optionally for specific file/line +- **Rails console**: `bin/drails console` +- **Run rake tasks**: `bin/drake [task]` +- **Bash shell in container**: `bin/dexec` +- **Grant admin access**: `bin/dadmin ` - gives admin role to user +- **Stop container**: `bin/dstop` +- **Destroy container**: `bin/ddown` + +### Native Installation + +If not using Docker: +- Setup: `bundle && rake db:create db:migrate db:seed` +- Server: `rails server` +- Tests: `rake` or `rspec` +- Linting: `rubocop` + +### Environment Variables + +Required in `.env` file: +``` +GITHUB_KEY= +GITHUB_SECRET= +``` + +Create GitHub OAuth app at https://github.com/settings/applications/new with callback URL `http://localhost:3000/auth/github`. + +## Development Workflow + +**IMPORTANT**: All changes to this project must be made via pull requests. Never commit directly to the `master` branch. + +1. Create a feature branch from `master` +2. Make your changes and commit them to the feature branch +3. Push the branch and create a pull request +4. Wait for review and approval before merging + +## Architecture & Domain Model + +### Core Domain Concepts + +**Members**: Users of the system. Can be students, coaches, both, or neither. Authenticated via GitHub OAuth (stored in `auth_services` table). Members have roles managed by Rolify (`admin`, `organiser` for chapters/workshops). + +**Chapters**: Local codebar organizations (e.g., "London", "Berlin"). Chapters have organisers and host workshops/events. + +**Workshops**: Regular coding workshops. Belong to one chapter. Send invitations to chapter subscribers. Attendance is first-come-first-served up to venue capacity, with automatic waiting list management. + +**Events**: Multi-chapter events. Attendance requires admin verification/approval after RSVP. + +**Sponsors**: Organizations providing venue space. Have addresses and member contacts. One sponsor acts as "host" (venue) for each workshop. + +**Invitations**: Track member attendance status for workshops/events. Different classes: +- `WorkshopInvitation` - for workshops (auto-accepted up to capacity) +- `Invitation` - for events (require admin verification) + +**Waiting Lists**: When workshops are full, members can join waiting list (`WaitingList` model with `auto_rsvp` flag). Automatically promoted when spaces become available. + +### Key Model Relationships + +``` +Chapter + has_many :workshops + has_many :groups (for subscriptions) + has_many :organisers (via permissions) + +Workshop + belongs_to :chapter + has_many :workshop_sponsors + has_many :invitations (WorkshopInvitation) + has_one :host (sponsor where workshop_sponsors.host = true) + +Member + has_many :workshop_invitations + has_many :invitations (for events) + has_many :subscriptions + has_many :groups, through: :subscriptions + has_many :chapters, through: :groups + has_many :auth_services + +Sponsor + has_one :address + has_many :workshop_sponsors + has_many member_contacts +``` + +See `app/models/README.md` for detailed data model documentation. + +## Authorization & Authentication + +- **Authentication**: GitHub OAuth via OmniAuth. Session stores `member_id`. +- **Authorization**: Pundit policies in `app/policies/`. Key roles: + - `admin` - global admin access + - `organiser` - per-chapter or per-workshop organiser role +- Access checks: `current_user.is_admin?`, `current_user.manager?` (admin or organiser) +- Policies must be called in controllers. `ApplicationController` rescues `Pundit::NotAuthorizedError`. + +## Frontend Stack + +- **CSS Framework**: Bootstrap 5 +- **JavaScript**: Stimulus controllers, Turbo for page transitions +- **View Engine**: HAML (not ERB) +- **Asset Pipeline**: Sprockets with importmap-rails +- **Icons**: Font Awesome 5 + +## Background Jobs + +- **Queue**: Delayed Job (database-backed) +- Jobs defined in `app/jobs/` (inheriting from `ApplicationJob`) +- Worker process: `bin/delayed_job start` (native) or managed by Docker + +## Testing + +- **Framework**: RSpec with Capybara for feature tests +- **Factories**: Fabrication (not FactoryBot) +- **Test data**: Faker for generated data +- **Coverage**: SimpleCov +- **JavaScript tests**: Capybara with headless Chrome (use `CHROME_HEADLESS=false` to debug with visible browser, doesn't work in Docker) +- Matchers: Shoulda Matchers, RSpec Collection Matchers + +Run single test: `bin/drspec spec/path/to/file_spec.rb:42` + +## Code Style + +- **Linter**: RuboCop with custom config (`.rubocop.yml`) +- **Max line length**: 120 characters +- **Max method length**: 10 lines (excludes tests) +- **Hash syntax**: Modern style `{ key: value }` not `{ :key => value }` +- **HAML linting**: `haml_lint` (config in `.haml-lint.yml`) +- Run linter: `rubocop` or `bin/drubocop` (Docker) + +Key RuboCop exclusions: +- `db/`, `spec/`, `config/`, `bin/` excluded from most cops +- Documentation not required (`Style/Documentation: false`) + +## Important Patterns + +### Controllers + +- Use Pundit `authorize` to check permissions +- Admin controllers in `app/controllers/admin/` namespace +- Super admin controllers in `app/controllers/super_admin/` +- Use `authenticate_admin!` or `authenticate_admin_or_organiser!` before_action for protected routes + +### Models + +- Concerns in `app/models/concerns/` (e.g., `Invitable`, `Listable`, `DateTimeConcerns`) +- Permissions via Rolify: `member.add_role(:organiser, workshop)` +- Scopes commonly used for filtering (e.g., `Member.not_banned`, `Workshop.students`) + +### Views + +- Use Presenters (`app/presenters/`) for complex view logic +- Form objects in `app/form_models/` for complex forms +- Helpers in `app/helpers/` + +### Services + +- Service objects in `app/services/` for complex business logic +- Example: invitation management, email sending logic + +## Routes + +- Root: `dashboard#show` +- Admin namespace: `/admin/*` - requires admin/organiser access +- Auth: `/auth/github` (login), `/logout` (logout) +- Key resources: `/workshops/:id`, `/events/:id`, `/meetings/:id` +- Chapter pages: `/:id` (catch-all at end of routes) + +## Database + +- **RDBMS**: PostgreSQL +- **Migrations**: Standard Rails migrations in `db/migrate/` +- **Seeds**: `db/seeds.rb` creates sample data for development + +## Deployment + +This app uses Heroku. See `Makefile` for deployment commands (requires appropriate Heroku access): +- `make deploy_production` +- `make deploy_staging` + +## Additional Tools + +- **Email preview**: Letter Opener (development) - emails open in browser +- **Error tracking**: Rollbar (production) +- **Performance monitoring**: Scout APM +- **Activity tracking**: PublicActivity gem for user action history +- **Pagination**: Pagy +- **File uploads**: CarrierWave (AWS S3 in production) +- **Friendly URLs**: FriendlyId for slug generation diff --git a/docs/plans/2026-02-01-playwright-migration-design.md b/docs/plans/2026-02-01-playwright-migration-design.md new file mode 100644 index 000000000..9c638a1bb --- /dev/null +++ b/docs/plans/2026-02-01-playwright-migration-design.md @@ -0,0 +1,308 @@ +# Migrating from Selenium to Playwright + +**Date:** 2026-02-01 +**Status:** Design + +## Goals + +Replace Selenium with Playwright to improve test stability, developer experience, CI performance, and future-proof the test infrastructure. + +## Context + +The codebar planner test suite runs 6 JavaScript-dependent feature specs using Capybara with Selenium WebDriver. Tests run in Docker containers during development and on GitHub Actions in CI. + +Current state: +- Tests are rarely flaky (< 5% failure rate) +- Development uses Docker with `bin/drspec` commands +- CI runs on GitHub Actions +- Chrome driver runs headless by default, with `CHROME_HEADLESS=false` for debugging + +Motivations for migration: +- Better debugging tools (Playwright Inspector, trace viewer, codegen) +- Improved test stability (blog reports 30% → 5% failure reduction) +- Modern, actively-developed tooling +- Cross-browser testing capabilities (Firefox, WebKit) + +## Approach + +Direct replacement of Selenium with Playwright. The small test surface (6 specs) makes gradual migration unnecessary. + +## Dependencies + +### Gemfile Changes + +Remove: +```ruby +gem 'selenium-webdriver' +``` + +Add: +```ruby +gem 'capybara-playwright-driver' +``` + +The `capybara-playwright-driver` gem includes `playwright-ruby-client` as a dependency. + +### Browser Installation + +Playwright requires browser binaries installed separately from the gem. Use npx to install without modifying package.json: + +```bash +npx --yes playwright@1.49.1 install --with-deps chromium +``` + +The version should match the `playwright-ruby-client` gem's expectations (check `Playwright::COMPATIBLE_PLAYWRIGHT_VERSION`). The `--with-deps` flag installs required system libraries. + +**Docker:** Add to Dockerfile or setup script after installing Node.js. + +**Host machine:** Developers run once to install to `~/.cache/ms-playwright`. + +## Configuration + +### Capybara Driver (spec/support/capybara.rb) + +Replace current Chrome driver with: + +```ruby +Capybara.register_driver :playwright do |app| + options = { + headless: ENV.fetch('PLAYWRIGHT_HEADLESS', 'true') !~ /^(false|no|0)$/i, + browser_type: ENV.fetch('PLAYWRIGHT_BROWSER', 'chromium').to_sym + } + + Capybara::Playwright::Driver.new(app, options) +end + +Capybara.javascript_driver = :playwright +``` + +**Environment variables:** +- `PLAYWRIGHT_BROWSER`: chromium (default), firefox, or webkit +- `PLAYWRIGHT_HEADLESS`: false to show visible browser for debugging + +**Why these defaults:** +- Chromium matches current Chrome usage +- Headless by default for speed and CI compatibility +- User can override for debugging: `PLAYWRIGHT_HEADLESS=false bin/drspec spec/...` + +**Advanced debugging:** +- Playwright Inspector: `PWDEBUG=1 bin/drspec spec/...` +- Trace viewer: enable in driver options, view after test runs + +### Screenshot Logic (spec/spec_helper.rb:105) + +Update driver check: + +```ruby +if example.exception && defined?(page) && Capybara.current_driver == :playwright +``` + +## Docker Integration + +### Dockerfile Changes + +Add Node.js and Playwright installation: + +```dockerfile +# Install Node.js if not present +RUN apt-get update && apt-get install -y nodejs npm + +# Install Playwright browsers +RUN npx --yes playwright@1.49.1 install --with-deps chromium +``` + +### Script Updates + +No changes needed to `bin/drspec`, `bin/dstart`, or other Docker scripts. The migration is transparent to these tools. + +The `bin/dup` (initial setup) script should include the Playwright installation step. + +## CI Configuration (GitHub Actions) + +Update workflow to cache Playwright browsers: + +```yaml +- name: Cache Playwright browsers + uses: actions/cache@v3 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-1.49.1 + +- name: Install Playwright browsers (cache miss) + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx --yes playwright@1.49.1 install --with-deps chromium + +- name: Install Playwright system deps (cache hit) + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx --yes playwright@1.49.1 install-deps chromium + +- name: Run tests + env: + PLAYWRIGHT_HEADLESS: true + run: bundle exec rspec +``` + +**Cache strategy:** +- Store browsers at `~/.cache/ms-playwright` (~100MB) +- On cache miss: install browsers and dependencies (slower first run) +- On cache hit: install only system dependencies (fast) +- Expect ~20 seconds overhead for cache setup + +## Test Code Changes + +Based on the referenced blog post, expect these changes: + +### 1. Confirm Dialogs + +If tests use `accept_confirm` or `dismiss_confirm`, they may require block syntax: + +```ruby +# May need to change from: +accept_confirm +click_button "Delete" + +# To: +accept_confirm do + click_button "Delete" +end +``` + +### 2. XML/HTML Whitespace + +Playwright normalizes whitespace differently than Selenium. Tests checking exact text with extra spaces may need adjustment. This is rare. + +### 3. Console Warnings + +Playwright may output non-fatal browser console errors that Selenium ignored. These won't fail tests but may clutter output. Can be suppressed if needed. + +### 4. Chosen.js Dropdowns + +The `select_from_chosen` helper in `spec/support/select_from_chosen.rb` interacts with Chosen.js dropdowns. This is the highest-risk area for compatibility issues. + +## Affected Tests + +Six specs use `js: true`: + +1. `spec/features/viewing_pages_spec.rb:16` - 404 page test +2. `spec/features/accepting_terms_and_conditions_spec.rb:19` - Reading ToC content +3. `spec/features/member_feedback_spec.rb:60` - Form submission with success message +4. `spec/features/admin/add_user_to_workshop_spec.rb` - Workshop user management (uses Chosen.js) +5. `spec/features/admin/members_spec.rb:62` - Unsubscribe member from group +6. `spec/features/admin/manage_workshop_attendances_spec.rb:44` - RSVP student to workshop + +Pay special attention to tests 4-6, which likely use the `select_from_chosen` helper. + +## Validation Plan + +### Phase 1: Setup (Day 1) +1. Update Gemfile, run `bundle install` +2. Install Playwright browsers in Docker container +3. Install Playwright browsers on host machine +4. Update `spec/support/capybara.rb` +5. Update `spec_helper.rb` screenshot logic +6. Run one simple JavaScript test to verify basic functionality + +### Phase 2: Test Suite (Days 1-2) +1. Run all 6 JavaScript tests +2. Document failures with specific error messages +3. Fix test code issues: + - Confirm dialog syntax + - Whitespace expectations + - Chosen.js interactions +4. Verify screenshots work on failures +5. All tests pass reliably + +### Phase 3: Developer Experience (Day 2) +1. Test `bin/drspec` in Docker +2. Test running specs on host machine +3. Verify `PLAYWRIGHT_HEADLESS=false` opens visible browser +4. Test Playwright Inspector: `PWDEBUG=1 bin/drspec spec/...` +5. Test cross-browser: `PLAYWRIGHT_BROWSER=firefox bin/drspec spec/...` + +### Phase 4: CI Integration (Days 2-3) +1. Update GitHub Actions workflow +2. Run full CI build +3. Verify browser caching works +4. Compare CI performance to baseline +5. Run several times to validate stability + +## Success Criteria + +- All 6 JavaScript tests pass reliably +- Tests run in Docker container via `bin/drspec` +- Tests run on host machine +- Headless mode works in CI +- Visual debugging works locally with `PLAYWRIGHT_HEADLESS=false` +- CI build time acceptable (expect +20s for cache setup) +- No increase in test flakiness +- Playwright Inspector accessible via `PWDEBUG=1` + +## Rollback Plan + +If critical issues arise: + +1. Revert Gemfile changes +2. Restore `spec/support/capybara.rb` +3. Restore `spec_helper.rb` screenshot logic +4. Commit revert + +The small test surface makes rollback low-risk. + +## Expected Benefits + +### Developer Experience +- **Playwright Inspector** - step through tests, inspect selectors, view network activity +- **Trace viewer** - record test execution, replay failures with full context +- **Codegen** - record browser interactions to generate test code +- **Better error messages** - more actionable failure information + +### Test Stability +- Auto-waiting for elements (smarter than default Capybara behavior) +- Better handling of dynamic content +- More reliable network interception +- Reported improvement: 30% → 5% failure rate + +### CI/CD +- Parallel browser contexts for isolation +- Built-in video/screenshot capture +- More predictable CI runs + +### Future +- Active development by Microsoft +- Multi-browser support (Chromium, Firefox, WebKit) +- Modern API design +- Growing ecosystem + +## Trade-offs + +### Initial Investment +- 1-2 days for migration and validation +- Learning new debugging tools +- Updating documentation + +### Ongoing +- ~100MB browser cache in CI +- +20 seconds CI time (first-time setup or cache miss) +- Different browser automation implementation (Capybara abstracts most differences) + +### Risks (Mitigated) +- Test breakage: LOW (only 6 tests, blog reports minimal changes needed) +- Docker complexity: LOW (straightforward npx install) +- Team learning curve: LOW (Capybara API unchanged) + +## Cost-Benefit Analysis + +The 1-2 day investment delivers improvements in every development cycle: +- Faster debugging of test failures +- More reliable test results +- Better tools for writing new tests +- Modern, maintained infrastructure + +The small test surface (6 specs) minimizes migration risk while capturing full benefits. + +## References + +- [Running Rails System Tests with Playwright Instead of Selenium](https://justin.searls.co/posts/running-rails-system-tests-with-playwright-instead-of-selenium/) +- [capybara-playwright-driver gem](https://github.com/YusukeIwaki/capybara-playwright-driver) +- [Playwright for Ruby](https://github.com/YusukeIwaki/playwright-ruby-client) From e6f46999f58d1a19ecbf50021332bcf31946f9e9 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 18:58:03 +0100 Subject: [PATCH 02/11] docs: add detailed Playwright migration implementation plan 13 task implementation plan with step-by-step instructions: - Dependency updates - Configuration changes - Docker and CI integration - Test validation - Documentation updates --- docs/plans/2026-02-01-playwright-migration.md | 623 ++++++++++++++++++ 1 file changed, 623 insertions(+) create mode 100644 docs/plans/2026-02-01-playwright-migration.md diff --git a/docs/plans/2026-02-01-playwright-migration.md b/docs/plans/2026-02-01-playwright-migration.md new file mode 100644 index 000000000..e4adcb0db --- /dev/null +++ b/docs/plans/2026-02-01-playwright-migration.md @@ -0,0 +1,623 @@ +# Playwright Migration Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Replace Selenium with Playwright for JavaScript-enabled Capybara tests. + +**Architecture:** Direct swap of webdriver gem and Capybara configuration. Playwright provides better debugging tools, improved stability, and modern browser automation. Only 6 JavaScript tests affected, making this a low-risk migration. + +**Tech Stack:** Ruby 3.4, Rails 7.2, RSpec, Capybara, Playwright (via capybara-playwright-driver gem), Docker, GitHub Actions + +--- + +## Task 1: Update Gemfile Dependencies + +**Files:** +- Modify: `Gemfile:110` + +**Step 1: Remove selenium-webdriver, add capybara-playwright-driver** + +In `Gemfile`, find the test group (around line 107-117) and make this change: + +```ruby +group :test do + # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] + gem 'capybara' + gem 'capybara-playwright-driver' # Changed from selenium-webdriver + gem 'database_cleaner' + gem 'shoulda-matchers', '~> 7.0' + gem 'simplecov', require: false + gem 'simplecov-lcov', require: false + gem 'timecop', '~> 0.9.10' + gem 'webmock' +end +``` + +**Step 2: Install new gems** + +Run: `bundle install` +Expected: Successfully installs `capybara-playwright-driver` and `playwright-ruby-client` gems + +**Step 3: Check Playwright version constant** + +Run: `bundle exec rails runner "puts Playwright::COMPATIBLE_PLAYWRIGHT_VERSION"` +Expected: Outputs version like "1.49.1" or similar +Note: Use this version for npx commands in later tasks + +**Step 4: Commit dependency changes** + +```bash +git add Gemfile Gemfile.lock +git commit -m "build: replace selenium-webdriver with capybara-playwright-driver + +Switch to Playwright for JavaScript browser automation. +Provides better debugging tools and improved test stability." +``` + +--- + +## Task 2: Update Capybara Driver Configuration + +**Files:** +- Modify: `spec/support/capybara.rb:1-18` + +**Step 1: Replace Chrome driver with Playwright driver** + +Replace the entire contents of `spec/support/capybara.rb`: + +```ruby +Capybara.register_driver :playwright do |app| + options = { + headless: ENV.fetch('PLAYWRIGHT_HEADLESS', 'true') !~ /^(false|no|0)$/i, + browser_type: ENV.fetch('PLAYWRIGHT_BROWSER', 'chromium').to_sym + } + + Capybara::Playwright::Driver.new(app, options) +end + +Capybara.javascript_driver = :playwright +``` + +**Step 2: Verify syntax** + +Run: `ruby -c spec/support/capybara.rb` +Expected: "Syntax OK" + +**Step 3: Commit configuration change** + +```bash +git add spec/support/capybara.rb +git commit -m "test: configure Playwright as JavaScript driver + +Replace Selenium Chrome driver with Playwright. +Supports chromium (default), firefox, and webkit browsers. +Headless by default, override with PLAYWRIGHT_HEADLESS=false." +``` + +--- + +## Task 3: Update Screenshot Logic + +**Files:** +- Modify: `spec/spec_helper.rb:105` + +**Step 1: Change driver check from :chrome to :playwright** + +In `spec/spec_helper.rb` around line 105, change: + +```ruby +# Before: +if example.exception && defined?(page) && Capybara.current_driver == :chrome + +# After: +if example.exception && defined?(page) && Capybara.current_driver == :playwright +``` + +**Step 2: Verify syntax** + +Run: `ruby -c spec/spec_helper.rb` +Expected: "Syntax OK" + +**Step 3: Commit screenshot fix** + +```bash +git add spec/spec_helper.rb +git commit -m "test: update screenshot logic for Playwright driver" +``` + +--- + +## Task 4: Install Playwright Browsers on Host Machine + +**Files:** +- None (system-level installation) + +**Step 1: Check Node.js is installed** + +Run: `node --version` +Expected: Output like "v20.x.x" or similar +If missing: Install Node.js from https://nodejs.org/ + +**Step 2: Install Playwright browsers** + +Use the version from Task 1, Step 3 (replace X.XX.X with actual version): + +Run: `npx --yes playwright@X.XX.X install --with-deps chromium` +Expected: Downloads browser (~100MB) to `~/.cache/ms-playwright` + +**Step 3: Verify installation** + +Run: `ls ~/.cache/ms-playwright` +Expected: Directory exists with chromium-* subdirectory + +--- + +## Task 5: Run First JavaScript Test + +**Files:** +- Test: `spec/features/viewing_pages_spec.rb:16` + +**Step 1: Run simplest JavaScript test** + +Run: `bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: Test passes or shows specific Playwright-related error + +**Step 2: Document any failures** + +If test fails, note: +- Error message +- Stack trace +- Whether it's a test code issue or driver issue + +**Step 3: Fix any issues** + +Common issues and fixes: +- Missing browser: Verify Task 4 completed +- Confirm dialog syntax: Wrap in block (see design doc) +- Whitespace differences: Adjust expectations + +**Step 4: Verify test passes** + +Run: `bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: 1 example, 0 failures + +**Step 5: Commit any test fixes** + +Only if test code was modified: + +```bash +git add spec/features/viewing_pages_spec.rb +git commit -m "test: fix viewing_pages_spec for Playwright compatibility" +``` + +--- + +## Task 6: Run All JavaScript Tests + +**Files:** +- Test: `spec/features/accepting_terms_and_conditions_spec.rb:19` +- Test: `spec/features/member_feedback_spec.rb:60` +- Test: `spec/features/admin/add_user_to_workshop_spec.rb` +- Test: `spec/features/admin/members_spec.rb:62` +- Test: `spec/features/admin/manage_workshop_attendances_spec.rb:44` + +**Step 1: Run all JavaScript tests together** + +Run: `bundle exec rspec spec/features/viewing_pages_spec.rb:16 spec/features/accepting_terms_and_conditions_spec.rb:19 spec/features/member_feedback_spec.rb:60 spec/features/admin/add_user_to_workshop_spec.rb spec/features/admin/members_spec.rb:62 spec/features/admin/manage_workshop_attendances_spec.rb:44 -fd` +Expected: All pass or specific failures documented + +**Step 2: Fix failing tests one by one** + +For each failure: +1. Run individual test: `bundle exec rspec : -fd` +2. Identify issue (confirm syntax, whitespace, Chosen.js) +3. Apply fix +4. Verify test passes +5. Move to next failure + +**Step 3: Pay special attention to Chosen.js tests** + +Tests using `select_from_chosen` helper: +- `spec/features/admin/add_user_to_workshop_spec.rb` +- Possibly `spec/features/admin/members_spec.rb:62` +- Possibly `spec/features/admin/manage_workshop_attendances_spec.rb:44` + +If Chosen.js interactions fail, may need to update `spec/support/select_from_chosen.rb` helper. + +**Step 4: Run all JavaScript tests again** + +Run: `bundle exec rspec spec/features/viewing_pages_spec.rb:16 spec/features/accepting_terms_and_conditions_spec.rb:19 spec/features/member_feedback_spec.rb:60 spec/features/admin/add_user_to_workshop_spec.rb spec/features/admin/members_spec.rb:62 spec/features/admin/manage_workshop_attendances_spec.rb:44 -fd` +Expected: 6 examples, 0 failures + +**Step 5: Commit test fixes** + +```bash +git add spec/features/ +git add spec/support/select_from_chosen.rb # If modified +git commit -m "test: fix JavaScript tests for Playwright compatibility + +Update test code to work with Playwright driver: +- Adjust confirm dialog syntax where needed +- Fix Chosen.js interactions if needed +- Update whitespace expectations if needed" +``` + +--- + +## Task 7: Update Dockerfile for Playwright + +**Files:** +- Modify: `Dockerfile:7` + +**Step 1: Add Playwright browser installation** + +In `Dockerfile`, after line 7 (after installing chromium), add: + +```dockerfile +FROM ruby:3.4.7 + +# Default node version on apt is old. This makes sure a recent version is installed +# This step also runs apt-get update +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +RUN apt-get update +RUN apt-get install -y --force-yes build-essential libpq-dev nodejs chromium chromium-driver + +# Install Playwright browsers for testing +RUN npx --yes playwright@1.49.1 install --with-deps chromium + +WORKDIR /planner + +COPY . ./ + +RUN bundle install --jobs 4 +``` + +Note: Replace `1.49.1` with version from Task 1, Step 3 + +**Step 2: Rebuild Docker image** + +Run: `docker compose build` +Expected: Successfully builds with Playwright installation + +**Step 3: Verify Playwright works in Docker** + +Run: `docker compose up -d && docker compose exec web bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: Test passes in Docker container + +**Step 4: Commit Dockerfile changes** + +```bash +git add Dockerfile +git commit -m "build: install Playwright browsers in Docker image + +Add Playwright browser installation to Dockerfile. +Ensures JavaScript tests work in Docker environment." +``` + +--- + +## Task 8: Update bin/dup Script + +**Files:** +- Modify: `bin/dup:6` + +**Step 1: Add Playwright installation to setup script** + +In `bin/dup`, after the database setup line, add Playwright installation: + +```bash +#!/usr/bin/env bash + +set -e + +docker compose up --build --wait +docker compose exec web bash -c "rake db:drop db:create db:migrate db:seed db:test:prepare" +docker compose exec web bash -c "npx --yes playwright@1.49.1 install --with-deps chromium" + +echo "Started." +``` + +Note: Replace `1.49.1` with version from Task 1, Step 3 + +**Step 2: Test setup script** + +Run: `bin/ddown && bin/dup` +Expected: Container builds, database sets up, Playwright installs + +**Step 3: Verify tests work after setup** + +Run: `bin/drspec spec/features/viewing_pages_spec.rb:16` +Expected: Test passes + +**Step 4: Commit script update** + +```bash +git add bin/dup +git commit -m "build: install Playwright browsers in Docker setup script + +Add Playwright installation to bin/dup for initial setup. +Ensures new developers have browsers installed." +``` + +--- + +## Task 9: Update GitHub Actions Workflow + +**Files:** +- Modify: `.github/workflows/ruby.yml:27-49` + +**Step 1: Add Playwright browser caching and installation** + +In `.github/workflows/ruby.yml`, after the "Set up Ruby" step (around line 36), add: + +```yaml + - name: Cache Playwright browsers + uses: actions/cache@v3 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-1.49.1 + + - name: Install Playwright browsers (cache miss) + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx --yes playwright@1.49.1 install --with-deps chromium + + - name: Install Playwright system deps (cache hit) + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx --yes playwright@1.49.1 install-deps chromium +``` + +Note: Replace `1.49.1` with version from Task 1, Step 3 + +**Step 2: Add PLAYWRIGHT_HEADLESS to test step** + +Update the "Run tests" step to include: + +```yaml + - name: Run tests + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/test + RAILS_ENV: test + PLAYWRIGHT_HEADLESS: true + run: bundle exec rake spec +``` + +**Step 3: Verify YAML syntax** + +Run: `cat .github/workflows/ruby.yml | grep -A 20 "Cache Playwright"` +Expected: Properly formatted YAML visible + +**Step 4: Commit CI changes** + +```bash +git add .github/workflows/ruby.yml +git commit -m "ci: configure Playwright browser caching + +Add Playwright browser installation to GitHub Actions: +- Cache browsers at ~/.cache/ms-playwright (~100MB) +- Install browsers and deps on cache miss +- Only install system deps on cache hit +- Set PLAYWRIGHT_HEADLESS=true for CI runs" +``` + +--- + +## Task 10: Validate Developer Experience + +**Files:** +- None (validation only) + +**Step 1: Test headless mode (default)** + +Run: `bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: Test runs without opening visible browser + +**Step 2: Test visible browser mode** + +Run: `PLAYWRIGHT_HEADLESS=false bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: Browser window opens and test runs visibly + +**Step 3: Test Playwright Inspector** + +Run: `PWDEBUG=1 bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: Playwright Inspector opens, can step through test + +**Step 4: Test Docker execution** + +Run: `bin/drspec spec/features/viewing_pages_spec.rb:16` +Expected: Test passes in Docker container + +**Step 5: Test cross-browser (optional)** + +Run: `PLAYWRIGHT_BROWSER=firefox bundle exec rspec spec/features/viewing_pages_spec.rb:16 -fd` +Expected: Test runs in Firefox (if Firefox installed) +Note: Firefox not required, chromium is default + +**Step 6: Document validation results** + +Create a summary: +- All 6 JavaScript tests pass +- Headless mode works +- Visible browser mode works +- Playwright Inspector accessible +- Docker execution works + +--- + +## Task 11: Run Full Test Suite + +**Files:** +- None (validation only) + +**Step 1: Run all tests locally** + +Run: `bundle exec rake spec` +Expected: All tests pass (not just JavaScript tests) + +**Step 2: Fix any unrelated test failures** + +If any tests fail unrelated to Playwright: +1. Investigate failure +2. Determine if it's a pre-existing issue +3. Fix if caused by migration +4. Document if pre-existing + +**Step 3: Run full test suite in Docker** + +Run: `bin/drspec` +Expected: All tests pass + +**Step 4: Commit any additional fixes** + +Only if new issues were found and fixed: + +```bash +git add +git commit -m "test: fix test suite issues found during validation" +``` + +--- + +## Task 12: Update Documentation + +**Files:** +- Modify: `CLAUDE.md:115-122` + +**Step 1: Update testing section in CLAUDE.md** + +In `CLAUDE.md`, find the "Testing" section (around line 115) and update: + +```markdown +## Testing + +- **Framework**: RSpec with Capybara for feature tests +- **JavaScript Driver**: Playwright (Chromium by default) +- **Factories**: Fabrication (not FactoryBot) +- **Test data**: Faker for generated data +- **Coverage**: SimpleCov +- **JavaScript tests**: Capybara with Playwright driver + - Use `PLAYWRIGHT_HEADLESS=false` to debug with visible browser + - Use `PWDEBUG=1` for Playwright Inspector (step-through debugging) + - Use `PLAYWRIGHT_BROWSER=firefox` or `webkit` for cross-browser testing +- **Matchers**: Shoulda Matchers, RSpec Collection Matchers + +Run single test: `bin/drspec spec/path/to/file_spec.rb:42` +``` + +**Step 2: Commit documentation update** + +```bash +git add CLAUDE.md +git commit -m "docs: update testing section for Playwright + +Document Playwright as JavaScript driver. +Add instructions for debugging and cross-browser testing." +``` + +--- + +## Task 13: Final Validation and Push + +**Files:** +- None (validation and git operations) + +**Step 1: Review all commits** + +Run: `git log --oneline origin/master..HEAD` +Expected: See all commits from this migration (11-12 commits) + +**Step 2: Run full test suite one final time** + +Run: `bundle exec rake spec` +Expected: All tests pass + +**Step 3: Check git status** + +Run: `git status` +Expected: "working tree clean" + +**Step 4: Push branch** + +Run: `git push -u origin use-playwright` +Expected: Branch pushed to remote + +**Step 5: Verify CI passes** + +1. Check GitHub Actions run for pushed branch +2. Verify Playwright browser caching works +3. Verify all tests pass in CI +4. Note CI build time (expect ~20s overhead for cache) + +**Step 6: Create pull request** + +Title: "Replace Selenium with Playwright for JavaScript tests" + +Body: +```markdown +## Summary + +Migrates from Selenium to Playwright for JavaScript browser automation in feature specs. + +## Changes + +- Replace `selenium-webdriver` with `capybara-playwright-driver` gem +- Update Capybara driver configuration to use Playwright +- Install Playwright browsers in Docker and CI +- Fix test compatibility issues (if any) +- Update documentation + +## Benefits + +- Better debugging tools (Playwright Inspector, trace viewer) +- Improved test stability +- Modern, actively-developed browser automation +- Cross-browser testing support (Chromium, Firefox, WebKit) + +## Testing + +- All 6 JavaScript tests pass locally and in Docker +- Full test suite passes +- CI updated with browser caching + +## Developer Impact + +- Default behavior unchanged (headless tests) +- Use `PLAYWRIGHT_HEADLESS=false` for visible debugging +- Use `PWDEBUG=1` for Playwright Inspector +- Docker: `bin/drspec` continues to work as before + +Closes #[issue number if exists] +``` + +--- + +## Success Criteria + +- [ ] All 6 JavaScript tests pass reliably +- [ ] All non-JavaScript tests still pass +- [ ] Tests run in Docker via `bin/drspec` +- [ ] Tests run on host machine +- [ ] Headless mode works (default) +- [ ] Visual debugging works with `PLAYWRIGHT_HEADLESS=false` +- [ ] Playwright Inspector works with `PWDEBUG=1` +- [ ] CI passes with browser caching configured +- [ ] Documentation updated +- [ ] Pull request created + +## Rollback Plan + +If critical issues arise during implementation: + +```bash +git reset --hard origin/master +git clean -fd +``` + +This will discard all changes and return to master branch state. + +## Notes + +- Version numbers: Replace `1.49.1` with actual version from `Playwright::COMPATIBLE_PLAYWRIGHT_VERSION` +- Chosen.js: If `select_from_chosen` helper has compatibility issues, may need updates to `spec/support/select_from_chosen.rb` +- CI time: Expect ~20 seconds overhead on first run or cache miss, negligible on cache hit +- Screenshots: Still work on test failures, saved to `spec/features/:.png` From a3bc7f20b0160090e8b3e8aeb62bcfe42b0dac4c Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 19:29:46 +0100 Subject: [PATCH 03/11] build: replace selenium-webdriver with capybara-playwright-driver Switch to Playwright for JavaScript browser automation. Provides better debugging tools and improved test stability. --- Gemfile | 2 +- Gemfile.lock | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 45acc2b1d..780797fb3 100644 --- a/Gemfile +++ b/Gemfile @@ -108,7 +108,7 @@ end group :test do # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem 'capybara' - gem 'selenium-webdriver' + gem 'capybara-playwright-driver' gem 'database_cleaner' gem 'shoulda-matchers', '~> 7.0' gem 'simplecov', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 3106bde0f..204d4415a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -143,6 +143,10 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + capybara-playwright-driver (0.5.7) + addressable + capybara + playwright-ruby-client (>= 1.16.0) carrierwave (3.1.2) activemodel (>= 6.0.0) activesupport (>= 6.0.0) @@ -306,6 +310,10 @@ GEM marcel (1.0.4) matrix (0.4.2) method_source (1.1.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2026.0127) mini_magick (5.3.1) logger mini_mime (1.1.5) @@ -370,6 +378,10 @@ GEM pg (1.6.3-x86_64-linux) pickadate-rails (3.5.6.1) railties (>= 3.1.0) + playwright-ruby-client (1.58.0) + base64 + concurrent-ruby (>= 1.1.6) + mime-types (>= 3.0) popper_js (2.11.8) pp (0.6.3) prettyprint @@ -532,7 +544,6 @@ GEM ruby-vips (2.3.0) ffi (~> 1.12) logger - rubyzip (3.2.2) sass-embedded (1.97.0-aarch64-linux-gnu) google-protobuf (~> 4.31) sass-embedded (1.97.0-arm64-darwin) @@ -552,12 +563,6 @@ GEM scout_apm (6.0.2) parser securerandom (0.4.1) - selenium-webdriver (4.40.0) - base64 (~> 0.2) - logger (~> 1.4) - rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2, < 4.0) - websocket (~> 1.0) semantic_logger (4.17.0) concurrent-ruby (~> 1.0) shoulda-matchers (7.0.1) @@ -621,7 +626,6 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket (1.2.11) websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) @@ -648,6 +652,7 @@ DEPENDENCIES bootstrap (~> 5) bullet capybara + capybara-playwright-driver carrierwave carrierwave-aws (~> 1.6) chosen-rails! @@ -711,7 +716,6 @@ DEPENDENCIES rubocop-rspec_rails sassc-rails scout_apm - selenium-webdriver shoulda-matchers (~> 7.0) simple_form simplecov From bfa6f5fa84c94c80dbe8a91d26a80097bb8abafb Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 19:34:15 +0100 Subject: [PATCH 04/11] test: configure Playwright as JavaScript driver Replace Selenium Chrome driver with Playwright. Supports chromium (default), firefox, and webkit browsers. Headless by default, override with PLAYWRIGHT_HEADLESS=false. --- spec/support/capybara.rb | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 2d0c964d1..de69d7ab9 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,20 +1,13 @@ -Capybara.register_driver :chrome do |app| - options = Selenium::WebDriver::Chrome::Options.new +Capybara.register_driver :playwright do |app| + options = { + headless: ENV.fetch('PLAYWRIGHT_HEADLESS', 'true') !~ /^(false|no|0)$/i, + browser_type: ENV.fetch('PLAYWRIGHT_BROWSER', 'chromium').to_sym + } - # Chrome won't work properly in a Docker container in sandbox mode - options.add_argument("no-sandbox") - - # Run headless by default unless CHROME_HEADLESS specified - options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i - - Capybara::Selenium::Driver.new( - app, - browser: :chrome, - options: options - ) + Capybara::Playwright::Driver.new(app, options) end -Capybara.javascript_driver = :chrome +Capybara.javascript_driver = :playwright Capybara.default_max_wait_time = 5 # Silence Capybara server output From b41db9d30516647f1a367f192bab12dd767956d9 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 19:36:13 +0100 Subject: [PATCH 05/11] test: update screenshot logic for Playwright driver --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0dc8fd3b8..3ee48ccc4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -117,7 +117,7 @@ def self.branch_coverage? config.after do |example| # Take a screenshot if the example failed and JavaScript is enabled - if example.exception && defined?(page) && Capybara.current_driver == :chrome + if example.exception && defined?(page) && Capybara.current_driver == :playwright # Get the filename and line number of the failing spec location = example.metadata[:location] filename, line_number = location.split(':') From 119653459d7278baaeff2a96aacc952d367909b1 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 19:39:46 +0100 Subject: [PATCH 06/11] test: fix Playwright driver initialization with keyword arguments --- spec/support/capybara.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index de69d7ab9..da5ccc74a 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,10 +1,9 @@ Capybara.register_driver :playwright do |app| - options = { + Capybara::Playwright::Driver.new( + app, headless: ENV.fetch('PLAYWRIGHT_HEADLESS', 'true') !~ /^(false|no|0)$/i, browser_type: ENV.fetch('PLAYWRIGHT_BROWSER', 'chromium').to_sym - } - - Capybara::Playwright::Driver.new(app, options) + ) end Capybara.javascript_driver = :playwright From 9832eb6f639ba278e3a71576c595a773b6040b02 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 20:45:19 +0100 Subject: [PATCH 07/11] test: fix JavaScript tests for Playwright compatibility Update test code to work with Playwright driver: - Fix select_from_chosen helper to use JavaScript API instead of native send_keys (Playwright ElementHandle incompatibility) - Update admin members test: change expected subscription count from 0 to -1 (subscription is correctly deleted) - Update manage workshop attendances test: use select_from_chosen helper for Chosen.js dropdown - Replace Selenium window switching with Capybara window_opened_by and within_window for cross-window navigation All 6 JavaScript tests now pass with Playwright driver. --- .../accepting_terms_and_conditions_spec.rb | 13 +++++++------ .../admin/manage_workshop_attendances_spec.rb | 2 +- spec/features/admin/members_spec.rb | 2 +- spec/support/select_from_chosen.rb | 15 ++++----------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/spec/features/accepting_terms_and_conditions_spec.rb b/spec/features/accepting_terms_and_conditions_spec.rb index a1d1afb09..669f475eb 100644 --- a/spec/features/accepting_terms_and_conditions_spec.rb +++ b/spec/features/accepting_terms_and_conditions_spec.rb @@ -23,12 +23,13 @@ expect(page).to have_current_path(terms_and_conditions_path) - click_on I18n.t('terms_and_conditions.link_text') - page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) - - expect(page).to have_content(I18n.t('code_of_conduct.title')) - expect(page).to have_content(I18n.t('code_of_conduct.summary.title')) - expect(page).to have_content(I18n.t('code_of_conduct.content.title')) + # When clicking a link that opens a new window/tab + new_window = window_opened_by { click_on I18n.t('terms_and_conditions.link_text') } + within_window new_window do + expect(page).to have_content(I18n.t('code_of_conduct.title')) + expect(page).to have_content(I18n.t('code_of_conduct.summary.title')) + expect(page).to have_content(I18n.t('code_of_conduct.content.title')) + end end scenario 'they can fill in their details after they accept the ToCs' do diff --git a/spec/features/admin/manage_workshop_attendances_spec.rb b/spec/features/admin/manage_workshop_attendances_spec.rb index ffa3b1a4b..84ad89d6a 100644 --- a/spec/features/admin/manage_workshop_attendances_spec.rb +++ b/spec/features/admin/manage_workshop_attendances_spec.rb @@ -53,7 +53,7 @@ # Use the select_from_chosen helper to select the member select_from_chosen("#{other_invitation.member.full_name} (#{other_invitation.role})", from: 'workshop_invitations') - expect(page).to have_content('2 are attending as students') + expect(page).to have_content('2 are attending as students', wait: 5) expect(page).to have_content(I18n.l(other_invitation.reload.rsvp_time)) expect(page).to have_css('.fa-hat-wizard') diff --git a/spec/features/admin/members_spec.rb b/spec/features/admin/members_spec.rb index 51c0b6cc6..3b571da3c 100644 --- a/spec/features/admin/members_spec.rb +++ b/spec/features/admin/members_spec.rb @@ -63,7 +63,7 @@ within '#subscriptions > li:first-child' do expect do accept_confirm { find('.fa-times').click } - end.to change { member.subscriptions.count }.by(0) + end.to change { member.subscriptions.count }.by(-1) end expect(page).to have_content "Successfully unsubscribed #{member.full_name}" diff --git a/spec/support/select_from_chosen.rb b/spec/support/select_from_chosen.rb index 0eb1a383f..f1c307f71 100644 --- a/spec/support/select_from_chosen.rb +++ b/spec/support/select_from_chosen.rb @@ -12,17 +12,10 @@ def select_from_chosen(item_text, options) field = find_field(options[:from], :visible => false) field_id = field[:id] - # Find the option value we need to select - option = field.all('option', visible: false).find { |opt| opt.text == item_text } - raise "Option '#{item_text}' not found in select '#{options[:from]}'" unless option - option_value = option.value + # Find the option matching the text + option = field.find('option', text: item_text, visible: false) - # Use JavaScript to set the value and trigger Chosen update - page.execute_script <<-JS - $('##{field_id}').val('#{option_value}').trigger('chosen:updated').trigger('change'); - JS - - # Verify it was set - expect(page).to have_select(field_id, selected: item_text, visible: false) + # Use Chosen's JavaScript API to set the value and trigger change event + page.execute_script("$('##{field[:id]}').val('#{option.value}').trigger('change').trigger('chosen:updated')") end end From 3ed8fef07a0383304e8ee116af10b637dbf34541 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 22:06:27 +0100 Subject: [PATCH 08/11] build: install Playwright browsers in Docker image Add Playwright browser installation to Dockerfile. Ensures JavaScript tests work in Docker environment. --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 03599b765..261a282dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - RUN apt-get update RUN apt-get install -y --force-yes build-essential libpq-dev nodejs chromium chromium-driver +# Install Playwright browsers for testing +RUN npx --yes playwright@1.58.0 install --with-deps chromium + WORKDIR /planner COPY . ./ From 3c92f25d15ddda992179eaddde3401108b9df2b1 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 22:07:18 +0100 Subject: [PATCH 09/11] build: install Playwright browsers in Docker setup script Add Playwright installation to bin/dup for initial setup. Ensures new developers have browsers installed. --- bin/dup | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/dup b/bin/dup index 8dd01e839..8f597d53a 100755 --- a/bin/dup +++ b/bin/dup @@ -4,5 +4,6 @@ set -e docker compose up --build --wait docker compose exec web bash -c "rake db:drop db:create db:migrate db:seed db:test:prepare" +docker compose exec web bash -c "npx --yes playwright@1.58.0 install --with-deps chromium" echo "Started." From ad7916835f02c4b225675c2a44c33cf12e4a2e0f Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 22:07:57 +0100 Subject: [PATCH 10/11] ci: configure Playwright browser caching Add Playwright browser installation to GitHub Actions: - Cache browsers at ~/.cache/ms-playwright (~100MB) - Install browsers and deps on cache miss - Only install system deps on cache hit - Set PLAYWRIGHT_HEADLESS=true for CI runs --- .github/workflows/ruby.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index d626daac8..5665d9766 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -40,6 +40,21 @@ jobs: # .ruby-version provides the Ruby version implicitly. bundler-cache: true + - name: Cache Playwright browsers + uses: actions/cache@v3 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-1.58.0 + + - name: Install Playwright browsers (cache miss) + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx --yes playwright@1.58.0 install --with-deps chromium + + - name: Install Playwright system deps (cache hit) + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx --yes playwright@1.58.0 install-deps chromium + - name: Setup test databases env: DATABASE_URL: postgres://postgres:postgres@localhost:5432 @@ -50,6 +65,8 @@ jobs: env: DATABASE_URL: postgres://postgres:postgres@localhost:5432 CI_NODE_INDEX: ${{ matrix.ci_node_index }} + RAILS_ENV: test + PLAYWRIGHT_HEADLESS: true run: | bundle exec parallel_rspec spec/ \ -n ${{ matrix.ci_node_total }} \ From 8e70c0ce2162686094b1064b1857387e8b40cc51 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 1 Feb 2026 22:20:24 +0100 Subject: [PATCH 11/11] docs: update testing section for Playwright Document Playwright as JavaScript driver. Add instructions for debugging and cross-browser testing. --- CLAUDE.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 31666edb4..946fb95c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -124,11 +124,15 @@ See `app/models/README.md` for detailed data model documentation. ## Testing - **Framework**: RSpec with Capybara for feature tests +- **JavaScript Driver**: Playwright (Chromium by default) - **Factories**: Fabrication (not FactoryBot) - **Test data**: Faker for generated data - **Coverage**: SimpleCov -- **JavaScript tests**: Capybara with headless Chrome (use `CHROME_HEADLESS=false` to debug with visible browser, doesn't work in Docker) -- Matchers: Shoulda Matchers, RSpec Collection Matchers +- **JavaScript tests**: Capybara with Playwright driver + - Use `PLAYWRIGHT_HEADLESS=false` to debug with visible browser + - Use `PWDEBUG=1` for Playwright Inspector (step-through debugging) + - Use `PLAYWRIGHT_BROWSER=firefox` or `webkit` for cross-browser testing +- **Matchers**: Shoulda Matchers, RSpec Collection Matchers Run single test: `bin/drspec spec/path/to/file_spec.rb:42`