feat(e2e): Playwright E2E for all 6 Mothership question types + CI pipeline (#423)#444
Conversation
Add a new GitHub Actions job (test-mothership) to run Layer 5 Mothership E2E tests with Playwright: sets up .NET/Node, caches Playwright browsers, installs Azurite, seeds blob containers, starts DotbotServer in test mode and runs the Playwright suite, uploading reports on failure. Extend server test endpoints to accept and persist RankedItems (validation, response model, and injection support). Update the local E2E PS runner (tests/Test-E2E-Mothership-QA.ps1) to mark Layer 5, add a --Headed flag, and seed additional scenarios (multiChoice, freeText, priorityRanking). Update the Playwright spec to handle multiChoice, freeText and priorityRanking flows, inject rankedItems, and verify persisted responses. Update docs to list all six question types covered by the tests.
…ARIOS not set Prevents the spec from throwing at load time in the standard Layer 5 UI regression run, which does not seed the scenario manifest. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set PLAYWRIGHT_HEADED in the PowerShell test runner instead of passing --headed, and clean it up after the run. The Playwright config now reads process.env.PLAYWRIGHT_HEADED to determine headless mode (headless = !PLAYWRIGHT_HEADED). This centralizes headed control via an environment variable and removes the previous --headed CLI argument handling.
Modify the GitHub Actions test workflow to redirect the Dotbot server stdout/stderr to /tmp/dotbot-server.log and run it in the background, saving its PID to /tmp/dotbot-server.pid. On the health-check timeout path, print the server startup log to aid debugging of CI failures. This adds visibility into server startup issues when E2E tests fail to connect.
1. CI secrets in workflowThe
These are committed in plain text in
2. Why this was discovered — server startup error logThe server was silently crashing during CI. Added log capture to the health-check step to surface it. The actual error was: Root cause:
|
There was a problem hiding this comment.
@OgnjenGligoric Two issues worth fixing.
1. CI failure — missing Azurite connection string
Server crashes immediately with:
System.InvalidOperationException: Either BlobStorage:AccountUri or BlobStorage:ConnectionString must be configured
BlobStorage__Backend=Local is set but Program.cs:100 still validates that AccountUri or ConnectionString exists regardless of backend.
Fix: Add to the Start DotbotServer env block in test.yml:
BlobStorage__ConnectionString: 'UseDevelopmentStorage=true'
This is the standard Azurite connection string — validation passes naturally, no server code changes needed.
2. Layer inconsistency
Test-E2E-Mothership-QA.ps1 header says Layer 5. test.yml job name says Layer 5. But Run-Tests.ps1 puts it in the Layer 4 block alongside the real Claude E2E tests. This means Run-Tests.ps1 -Layer 5 does not run the Mothership tests, and -Layer 4 runs them only if ANTHROPIC_API_KEY is present (scheduled/manual CI only).
Since this test needs Playwright + Azurite but not Claude API credentials, it belongs in Layer 5. Move $mothershipExit from the if (4 -in $layersToRun) block to a if (5 -in $layersToRun) block in Run-Tests.ps1.
There was a problem hiding this comment.
Pull request overview
Adds a new Layer 5 end-to-end test suite for the Mothership “respond via magic link” web flow using Playwright, expands server test-mode support to inject priorityRanking responses, and wires the E2E suite into CI so it runs automatically on PRs.
Changes:
- Added a PowerShell seeder/runner that creates templates + instances for all 6 Mothership question types and runs Playwright assertions.
- Extended
/api/test/responses(test mode) to accept and persistrankedItemspayloads. - Added a dedicated GitHub Actions job to spin up Azurite + DotbotServer and run the Playwright suite; added local setup docs.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
tests/Test-E2E-Mothership-QA.ps1 |
New seeding + Playwright runner script for the Mothership respond flow across all question types. |
tests/Run-Tests.ps1 |
Hooks the new Mothership E2E script into the layered test runner. |
tests/e2e/specs/mothership-question-flow.spec.ts |
New Playwright spec validating render/submit/storage for each seeded scenario. |
tests/e2e/playwright.config.ts |
Adds PLAYWRIGHT_HEADED-controlled headless toggle for Chromium runs. |
server/src/Dotbot.Server/TestModeEndpoints.cs |
Extends response injection validation/model to support rankedItems. |
server/docs/MOTHERSHIP-E2E-SETUP.md |
New/updated local setup documentation for running the Playwright + Azurite E2E suite. |
.gitignore |
Ignores Azurite local artifacts. |
.github/workflows/test.yml |
Adds test-mothership CI job to run the new Mothership Playwright E2E suite on PRs. |
Comments suppressed due to low confidence (2)
tests/Test-E2E-Mothership-QA.ps1:244
- For question types without Options (e.g., freeText),
$Qt.Optionsis$null, so$optionsbecomes$nullandoptions = @($options)serializes as[null]. The server validator rejects this (options[0] must not be null) becauseQuestionTemplate.Optionsis required. Buildoptionsas an empty array when there are no options (or conditionally omit the ForEach and setoptions = @()).
$options = $Qt.Options | ForEach-Object {
@{
optionId = [guid]::NewGuid().ToString()
key = $_.key
title = $_.label
}
}
$body = @{
questionId = [guid]::NewGuid().ToString()
version = 1
type = $Qt.Type
title = $Qt.Title
context = "Playwright E2E test fixture"
deliverableSummary = if ($Qt.ContainsKey('DeliverableSummary')) { $Qt.DeliverableSummary } else { $null }
options = @($options)
project = @{
tests/Test-E2E-Mothership-QA.ps1:272
New-Instancehard-codeschannel = "email", butDeliveryChannels:Email:Enabledisfalseby default (and the CI job doesn’t enable it), so/api/instanceswill return 400 “Delivery channel 'email' is not enabled…”. Use the default channel (omitchannel) or switch toteams, or explicitly enable email in the CI server env if email is intended.
# Use 'email' channel — delivery will fail (no SMTP) but the instance record
# is persisted before delivery attempts, so /respond can still render it.
$body = @{
projectId = $projectId
questionId = $QuestionId
questionVersion = $Version
channel = "email"
recipients = @{ emails = @($testRecipient) }
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Move the Layer 5 Mothership Playwright job out of .github/workflows/test.yml into .github/workflows/server.yml and remove the duplicate job. The new job provisions .NET/Node/PowerShell, installs Playwright (with browser cache and deps), seeds Azurite, starts the Dotbot server, waits for health, runs the Mothership E2E PowerShell runner, and uploads Playwright artifacts on failure. Also update docs and test scripts: clarify how to set BlobStorage connection string and how to run headed Playwright locally in server/docs/MOTHERSHIP-E2E-SETUP.md; adjust tests/Run-Tests.ps1 to move the Mothership test from layer 4 into layer 5 and update layer result logic; and improve tests/Test-E2E-Mothership-QA.ps1 to generate optionIds for templates, return OptionIds with the template, build rankedItems from those OptionIds for priorityRanking submits, and change the default test channel to 'teams' (so instances persist reliably).
Include tests/e2e/** and tests/Test-E2E-Mothership-QA.ps1 in the server workflow triggers for push and pull_request so CI runs when E2E test files change. Update .gitignore to ignore Azurite local storage DB files created during local E2E testing and add explanatory comments.
Add ASPNETCORE_ENVIRONMENT='Development' to the Start DotbotServer job in .github/workflows/server.yml so the app runs with the Development configuration during CI. This ensures development-specific settings are used when running the server in tests.
Rename and relocate the Playwright E2E suite from tests/e2e to tests/e2e-server. Add package.json, package-lock.json, playwright.config.ts, tsconfig.json, and .gitignore for the new suite, and move the mothership question flow spec. Update the spec to use DOTBOT_SERVER_URL (with a localhost fallback). Adjust GitHub Actions workflow to watch the new path, update npm cache dependency path, Playwright cache key, and artifact paths. Also update the PowerShell test runner to point at the new e2e-server directory.
Update .github/workflows/server.yml to use the tests/e2e-server directory for Playwright-related steps. Changed working-directory for: Install Playwright npm dependencies, Install Playwright Chromium + system deps, and Install Playwright system deps only (cached browser). No behavioral changes beyond using the new test folder path.
Summary
Closes #423.
This PR adds end-to-end Playwright coverage for all six Mothership question delivery types and wires the test suite into CI so it runs automatically on every PR.
What was done
New question type coverage (Layer 5 E2E)
Previously only
singleChoice,approval, anddocumentReviewwere covered. Added the remaining three:multiChoice— radio selection (same UI as singleChoice; type distinction handled server-side)freeText— textarea fill + submitpriorityRanking— drag-and-drop list; JS serialisesrankedItemsJsonon submit; tests use default order (no drag simulation needed in headless)Server:
TestModeEndpoints.csExtended
TestResponseRequestwith an optionalList<RankedItem>? RankedItemsfield so the/api/test/responsesinject endpoint can persist priority ranking payloads directly, matching what the real respond form produces. Validation updated to acceptrankedItemsas a valid payload alongsideselectedKey,freeText, andattachments.Test seeder:
Test-E2E-Mothership-QA.ps1multiChoice,freeText, andpriorityRanking-Headedswitch for local debugging (opens real Chromium window)Playwright spec:
mothership-question-flow.spec.tsScenariointerface withfreeText?andrankedItems?submit fieldstextarea[name="freeText"]and.rank-itemfor new typesfreeText→freeTextfield,priorityRanking→rankedItems, others →selectedKeyCI:
test.ymlAdded
test-mothershipjob (Layer 5, Ubuntu) that runs on every PR without manual trigger:answersandconversation-referencescontainersDOTBOT_TEST_MODE=trueandBlobStorage__Backend=LocalTest-E2E-Mothership-QA.ps1— seeds all 6 question types, mints magic-link JWTs, and runs 3 Playwright assertions per type (18 total)Docs:
server/docs/MOTHERSHIP-E2E-SETUP.mdUpdated to document all 6 question types and the
rankedItemsinject support. Running locally: