Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
cc45b7e
test(v1.4): close coverage gaps surfaced during production validation…
MP2EZ May 29, 2026
e9f83f3
docs: mark v1.4 SHIPPED + plan v1.41 Maestro E2E + Post-ship Status
MP2EZ May 29, 2026
1cd555a
feat(v1.42): plan Site Polish + Legal Footing, ship About page (#36)
MP2EZ May 29, 2026
1c756a8
docs(v1.41): switch default plan to self-hosted Maestro (\$0/mo)
MP2EZ May 29, 2026
b4cd5a3
feat(v1.41): Playwright E2E scaffold (pivot from Maestro Web Beta)
MP2EZ May 29, 2026
da765b5
fix(v1.41): two review-found bugs in upgrade smoke
MP2EZ May 30, 2026
1d02ee4
fix(v1.41): use single campnw-staging app, drop preview-per-PR
MP2EZ May 30, 2026
3661e2b
fix(v1.41): correct flyctl ssh env var passing in seed step
MP2EZ May 30, 2026
2ee3a94
fix(v1.41): bootstrap WatchDB schema in fixture seeder
MP2EZ May 30, 2026
873fe57
fix(v1.41): subscription_expires_at uses empty string, not None
MP2EZ May 30, 2026
88440bf
fix(v1.41): wait for dialog to close, not 'Sign in' button
MP2EZ May 30, 2026
d9bdec7
fix(v1.41): paginate admin users lookup + clear stale fixture rows
MP2EZ May 30, 2026
b6fef09
fix(v1.41): scope auth-modal close assertion + bake in onboarding skip
MP2EZ May 30, 2026
a8ef1a9
fix(v1.41): force-click Skip in onboarding to bypass hit-test
MP2EZ May 30, 2026
1f51d07
fix(v1.41): rewrite Stripe payWithCard for real Checkout DOM
MP2EZ May 30, 2026
7e2da2a
fix(v1.41): scope Stripe form selectors to textbox role to dodge dupes
MP2EZ May 30, 2026
24f2079
chore(v1.41): allow workflow_dispatch to trigger nightly (all-flows) job
MP2EZ May 30, 2026
332a696
chore(v1.41): concurrency control + nightly checkout ref + issues:write
MP2EZ May 30, 2026
4d2aa97
diag(v1.41): verify seed writes via read-back
MP2EZ May 30, 2026
1d0b472
fix(v1.41): seed writes to /app/data/watches.db, not registry.db
MP2EZ May 30, 2026
741468b
feat(v1.41): real Stripe customer for pro fixture + name-filter search
MP2EZ May 30, 2026
edfbe52
fix(v1.41): rewrite cancel-reactivate to self-upgrade; revert Stripe …
MP2EZ May 30, 2026
36ea7c9
fix(v1.41): extend onboarding probe + correct Stripe Portal buttons
MP2EZ May 30, 2026
2a8aeae
fix(v1.41): make skipOnboarding more aggressive + call after navigation
MP2EZ May 30, 2026
9892095
fix(v1.41): fixture onboarding_complete=1 + Stripe Portal text selectors
MP2EZ May 30, 2026
0e42f3d
fix(v1.41): looser cancel regex + exact-match Watch button
MP2EZ May 30, 2026
60ebcc0
fix(v1.41): use 'Watch this search' + permissive reactivate regex
MP2EZ May 30, 2026
609ae02
fix(v1.41): broad search for Watch buttons; single-click reactivate
MP2EZ May 30, 2026
92a9811
fix(v1.41): .watch-cta-btn selector + wait for renew network idle
MP2EZ May 31, 2026
1bb6de4
fix(v1.41): drop force-click on Watch; tighter reactivate regex
MP2EZ May 31, 2026
9e78824
fix(v1.41): expand result card before clicking Watch; broader reactiv…
MP2EZ May 31, 2026
047439b
fix(v1.41): drop reactivate half of cancel-reactivate test
MP2EZ May 31, 2026
617555c
Merge pull request #38 from MP2EZ/feat/v1.41-playwright-e2e
MP2EZ May 31, 2026
2eac3c8
docs(v1.41): mark Playwright E2E as SHIPPED 2026-05-31
MP2EZ May 31, 2026
8ac8fe8
Merge pull request #53 from MP2EZ/chore/v1.41-shipped
MP2EZ May 31, 2026
677473f
feat(v1.42): privacy + terms pages, global footer
MP2EZ May 31, 2026
7f351b7
Merge pull request #54 from MP2EZ/feat/v1.42-legal-and-footer
MP2EZ May 31, 2026
3c2dac0
chore(v1.42): copy pass on About + Privacy + Terms
MP2EZ May 31, 2026
2db7e28
Merge pull request #55 from MP2EZ/chore/v1.42-copy-pass
MP2EZ May 31, 2026
7410ebd
chore(v1.42): swap support email to hello@campable.co
MP2EZ May 31, 2026
c4d6491
Merge pull request #56 from MP2EZ/chore/v1.42-contact-email
MP2EZ May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Playwright E2E

# Runs Playwright flows against a single long-lived staging app
# (campnw-staging.fly.dev). Each PR's smoke job deploys the PR branch
# to staging, seeds fixtures, and runs the upgrade smoke flow.
# Nightly cron deploys the current dev HEAD and runs all four flows.
#
# Why single staging instead of preview-per-PR:
# - Stripe test-mode webhooks deliver to ONE configured URL, so
# N preview apps can't all receive webhook events anyway
# - Solo dev → no concurrent PR mutex needed
# - One Fly app instead of N (cleaner dashboard, simpler secrets)
#
# Required repo secrets:
# FLY_API_TOKEN (org-scoped — deploys to staging)
# E2E_FIXTURE_PASSWORD (shared password for fixture users)
# SUPABASE_SERVICE_ROLE_KEY (admin JWT for fixture seeding)
# VITE_PUBLIC_SUPABASE_URL (existing — build-time + seed env)
# VITE_PUBLIC_SUPABASE_ANON_KEY (existing — build-time)
# VITE_PUBLIC_POSTHOG_PROJECT_TOKEN (existing — build-time)

on:
pull_request:
branches: [main, dev]
schedule:
- cron: '0 8 * * *' # nightly 08:00 UTC
workflow_dispatch:

env:
STAGING_APP: campnw-staging
STAGING_URL: https://campnw-staging.fly.dev

# Serialize concurrent runs — they share one Fly staging app, and two
# parallel deploys race on the same machine.
concurrency:
group: playwright-staging
cancel-in-progress: false

jobs:
smoke:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Build frontend
working-directory: web
env:
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN: ${{ secrets.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN }}
VITE_PUBLIC_POSTHOG_HOST: https://eu.i.posthog.com
VITE_PUBLIC_SUPABASE_URL: ${{ secrets.VITE_PUBLIC_SUPABASE_URL }}
VITE_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.VITE_PUBLIC_SUPABASE_ANON_KEY }}
run: |
npm ci
npm run build

- name: Copy design tokens for SEO pages
run: cp web/src/tokens.css src/pnw_campsites/seo-static/tokens.css

- name: Setup Fly CLI
uses: superfly/flyctl-actions/setup-flyctl@63da3ecc5e2793b98a3f2519b3d75d4f4c11cec2

- name: Deploy PR branch to staging
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
run: |
flyctl deploy --remote-only --app "$STAGING_APP"
for i in $(seq 1 30); do
if curl -sf -o /dev/null "$STAGING_URL/healthz"; then
echo "Staging ready"
exit 0
fi
sleep 10
done
echo "::error::Staging never became healthy"
exit 1

- name: Seed E2E fixtures
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
E2E_FIXTURE_PASSWORD: ${{ secrets.E2E_FIXTURE_PASSWORD }}
run: |
# SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY are already Fly
# secrets on campnw-staging (mirrored from prod/.env). Only
# E2E_FIXTURE_PASSWORD needs to be passed inline.
flyctl ssh console -a "$STAGING_APP" \
-C "env E2E_FIXTURE_PASSWORD='$E2E_FIXTURE_PASSWORD' python scripts/seed_e2e_fixtures.py"

- name: Install Playwright + browser
working-directory: e2e
run: |
npm ci
npx playwright install --with-deps chromium

- name: Run smoke flow
working-directory: e2e
env:
E2E_BASE_URL: ${{ env.STAGING_URL }}
E2E_FIXTURE_PASSWORD: ${{ secrets.E2E_FIXTURE_PASSWORD }}
CI: "true"
run: npx playwright test tests/upgrade.spec.ts

- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report-smoke
path: e2e/playwright-report/
retention-days: 14

nightly:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 40
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
# No explicit ref — defaults to github.ref. For schedule that's
# the default branch (dev); for workflow_dispatch it's whatever
# branch the dispatch targeted.

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Build frontend
working-directory: web
env:
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN: ${{ secrets.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN }}
VITE_PUBLIC_POSTHOG_HOST: https://eu.i.posthog.com
VITE_PUBLIC_SUPABASE_URL: ${{ secrets.VITE_PUBLIC_SUPABASE_URL }}
VITE_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.VITE_PUBLIC_SUPABASE_ANON_KEY }}
run: |
npm ci
npm run build

- name: Copy design tokens for SEO pages
run: cp web/src/tokens.css src/pnw_campsites/seo-static/tokens.css

- name: Setup Fly CLI
uses: superfly/flyctl-actions/setup-flyctl@63da3ecc5e2793b98a3f2519b3d75d4f4c11cec2

- name: Deploy dev HEAD to staging
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
run: |
flyctl deploy --remote-only --app "$STAGING_APP"
for i in $(seq 1 30); do
if curl -sf -o /dev/null "$STAGING_URL/healthz"; then
exit 0
fi
sleep 10
done
exit 1

- name: Seed E2E fixtures
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
E2E_FIXTURE_PASSWORD: ${{ secrets.E2E_FIXTURE_PASSWORD }}
run: |
# SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY are already Fly
# secrets on campnw-staging (mirrored from prod/.env). Only
# E2E_FIXTURE_PASSWORD needs to be passed inline.
flyctl ssh console -a "$STAGING_APP" \
-C "env E2E_FIXTURE_PASSWORD='$E2E_FIXTURE_PASSWORD' python scripts/seed_e2e_fixtures.py"

- name: Install Playwright + browser
working-directory: e2e
run: |
npm ci
npx playwright install --with-deps chromium

- name: Run all flows
working-directory: e2e
env:
E2E_BASE_URL: ${{ env.STAGING_URL }}
E2E_FIXTURE_PASSWORD: ${{ secrets.E2E_FIXTURE_PASSWORD }}
CI: "true"
run: npx playwright test

- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report-nightly
path: e2e/playwright-report/
retention-days: 30

- name: Open issue on nightly failure
if: failure()
uses: actions/github-script@v7
with:
script: |
const date = new Date().toISOString().split('T')[0];
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Nightly Playwright E2E failed (${date})`,
body: `Run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}\n\nThe Playwright HTML report + trace are attached as the run's artifact.`,
labels: ['e2e-failure'],
});
Loading
Loading