This specification addresses two failing Playwright Firefox tests in tests/core/domain-dns-management.spec.ts:
This specification addresses only the failing Playwright assertion:
1. DNS Providers - list providers after API seed
2. DNS Providers - delete provider via API and verify removal
-`[firefox] tests/core/data-consistency.spec.ts:327 Failed transaction prevents partial data updates`
Observed failures are deterministic across retries and happen before deletion assertions. The plan below focuses on root-cause correction with minimal API request overhead and stable UI semantics.
Failure signal:
- Expected: original domain remains unchanged after invalid update attempt.
- Received: empty string (`''`) for domain.
Primary objective:
-Make DNS provider cards discoverable and testable again without regressing Manual DNS Challenge behavior.
Anchor from provided failure report:
-Stack trace lines 369-372 (poll/assertion block in this scenario).
Secondary objective:
-Reduce unnecessary DNS page requests while preserving UX and accessibility behavior.
Current file anchor (same logical block in workspace revision):
-`tests/core/data-consistency.spec.ts` lines around 379-382:
- UI form prevents blank input for normal interactions, but API path still allows blank when called directly (as in test).
### 2.5 Confidence
Confidence score: 95%
Rationale:
- Failure output, snapshots, and rendering condition align.
-Issue reproduces across retries.
- Backend CRUD path appears healthy; failure is in frontend display gating.
- Failure output and inspected code paths align directly with persisted empty domain.
-Missing domain validation and non-transactional update semantics are explicit in code.
---
## 3) Technical Specifications
## 3) Root Cause Hypothesis (Primary)
## 3.1 EARS requirements
Primary root cause:
- Backend update contract allows `domain_names: ''` and persists it.
- WHEN the DNS Providers page loads and no active manual challenge exists, THE SYSTEM SHALL render provider cards from GET /api/v1/dns-providers.
-WHEN provider cards exist, THE SYSTEM SHALL keep them visible regardless of manual challenge fetch failures.
-WHEN stabilizing the current failure, THE SYSTEM SHALL apply frontend root-cause fixes before any E2E test edits.
- IF the same E2E assertion still fails after the frontend fix is applied, THEN THE SYSTEM SHALL permit minimal, targeted test adjustments.
- IF manual challenge retrieval returns not found, THEN THE SYSTEM SHALL treat it as no active challenge and SHALL NOT inject fallback challenge content.
- WHEN a person explicitly requests Manual DNS Challenge view, THE SYSTEM SHALL fetch/display challenge data and SHALL preserve keyboard/screen-reader semantics.
- WHEN a provider is created via API seed in tests, THE SYSTEM SHALL expose it in UI heading text within the provider card grid.
- WHEN a provider is deleted via API and page is refreshed, THE SYSTEM SHALL remove the matching card from the grid.
Contributing factors:
-Test allows `200` on invalid update attempt, reducing strictness of expected failure contract.
-Route semantics are not transactional despite scenario wording.
## 3.2 Request-minimization strategy (least amount of requests)
Current behavior issues:
- DNS page performs challenge fetch eagerly and converts errors into fallback UI state.
Planned behavior:
- One baseline request on page load: GET /api/v1/dns-providers.
- Zero manual challenge requests on initial load unless manual challenge panel is opened explicitly.
- Optional follow-up requests only on user action:
- GET /api/v1/dns-providers/:id/manual-challenge/active (new optional endpoint), or
- GET /api/v1/dns-providers/:id/manual-challenge/:challengeId only if challengeId is known.
Net effect:
- Fewer initial requests and deterministic provider list rendering.
## 3.3 API and handler design options
Option A (preferred for least backend change):
- Frontend-only behavior correction.
- In frontend/src/pages/DNSProviders.tsx:
- Remove synthetic fallback challenge injection from catch block in loadManualChallenge.
- Track manual panel visibility separately from challenge data.
- Only call loadManualChallenge from manual action button or explicit deep-link flow.
Option B (clean API contract enhancement):
- Add explicit active challenge endpoint:
- GET /api/v1/dns-providers/:id/manual-challenge/active
- Handler addition in backend/internal/api/handlers/manual_challenge_handler.go:
- GetActiveChallenge(c *gin.Context)
- Service addition in backend/internal/services/manual_challenge_service.go:
- Route registration in backend/internal/api/routes/routes.go.
Recommendation:
- Implement Option A first (fastest unblock).
- Option B can follow if active-challenge UX remains core and reused broadly.
## 3.4 Component-level design changes
Primary component:
- frontend/src/pages/DNSProviders.tsx
Current critical symbols:
- loadManualChallenge
- showManualChallenge
- manualChallenge
- manualProviderId
Planned symbol responsibilities:
- isManualPanelOpen (new): controls panel visibility as explicit UI state.
- manualChallenge: nullable real challenge only (no synthetic fallback).
- loadManualChallenge(providerId):
- on 404/not-found, set manualChallenge = null and keep provider cards visible.
- on transport/server errors, surface toast warning without replacing page mode.
Render rules:
- Provider grid visibility should not be blocked by challenge lookup failure.
- Manual panel visibility must be driven by `isManualPanelOpen`, not by presence/absence of `manualChallenge`.
-`manualChallenge` data existence must not implicitly switch overall page mode.
Accessibility continuity:
- Preserve existing button names and aria labeling in ManualDNSChallenge.
- Ensure focus flow remains predictable when opening/closing panel.
## 3.5 Test strategy updates
E2E tests to stabilize:
- tests/core/domain-dns-management.spec.ts
E2E test-edit guard:
- Do not edit failing E2E tests during initial fix.
- Only after frontend fix attempt, if the same failure still reproduces deterministically, allow minimal assertion/wait adjustments limited to the failing steps.
Related E2E tests for regression guard:
- tests/dns-provider-crud.spec.ts
- tests/manual-dns-provider.spec.ts
- tests/dns-provider-types.spec.ts
Frontend unit test additions (new):
- frontend/src/pages/__tests__/DNSProviders.test.tsx (new file)
- Case 1: providers render when manual challenge fetch returns 404.
- Case 2: manual panel opens only after explicit button action.
- Case 3: provider grid remains visible after manual challenge fetch error.
Backend unit tests (only if Option B implemented):
- UI rendering logic is not primary here; final assertion reads API detail directly.
---
## 4) Implementation Plan (Phased, minimal-request first)
## 4) EARS Requirements (Focused)
## Phase 1 - Reproduction and baseline lock
- WHEN a proxy host update request includes `domain_names` as an empty string, THE SYSTEM SHALL reject the request with validation error (`400` or `422`).
- WHEN validation rejects that request, THE SYSTEM SHALL NOT persist any change to `domain_names`.
- WHEN the invalid update is attempted in the failing scenario, THE SYSTEM SHALL preserve the previously stored domain value.
- IF update processing fails after persistence-related steps, THEN THE SYSTEM SHALL provide deterministic behavior that prevents partial state for this field path.
- WHEN this failure is fixed, THE SYSTEM SHALL pass the targeted Playwright scenario deterministically across repeated runs.
### Decision - 2026-02-15
**Decision**: For this single flaky-failure fix scope, the Supervisor blocker on post-persistence failure behavior is closed by enforcing pre-persistence validation rejection for empty `domain_names` and treating this as the required failure path for the targeted scenario.
**Context**: The failing scenario (`Failed transaction prevents partial data updates`) currently reproduces because `domain_names: ''` is accepted and persisted. The route does not implement an explicit transaction boundary for persistence + post-persistence Caddy apply, so broad rollback semantics are a separate concern.
**Options**:
- **Option A (selected):** Enforce validation rejection before persistence for empty-domain updates in this scope.
- **Option B (deferred):** Implement broader transactional rollback hardening for post-persistence failures in the update flow.
**Rationale**: The reported flaky failure is fully addressed by preventing invalid writes before persistence, which directly satisfies the scenario’s failure expectation with the smallest safe change surface. Expanding into transaction rollback hardening would increase scope and coupling for this fix and is not required to close the current blocker.
**Impact**: The targeted failure path is deterministic for this issue. Broader post-persistence rollback guarantees remain unchanged and are explicitly deferred as follow-up technical hardening.
**Review**: Reassess deferred rollback hardening in a dedicated follow-up item after this flaky-failure fix lands and stabilizes.
### Supervisor Blocker Closure Note
- **Status**: Closed for this issue scope.
- **Closure basis**: Enforced validation rejection path (empty domain rejected pre-persistence) is the approved failure mechanism for this scenario.
- **Deferred follow-up**: Broader transaction rollback hardening for post-persistence failures is out-of-scope for this fix and tracked as subsequent hardening work.
---
## 5) Remediation Plan (Phased)
### Phase 1: Contract lock and deterministic repro
Goal:
-Lock failing behavior and ensure deterministic reproduction path.
-Reproduce only this failure and lock expected contract.
Actions:
1. Run targeted failing tests only in tests/core/domain-dns-management.spec.ts.
2. Capture failure screenshots, traces, and assertions.
3. Capture request timeline for /api/v1/dns-providers and manual challenge endpoints.
1. Run targeted Playwright case only:
-`tests/core/data-consistency.spec.ts` test: `Failed transaction prevents partial data updates`.
-`Update` sequence (`service.Update` then `caddyManager.ApplyConfig`).
-`backend/internal/database/database.go`
-`SkipDefaultTransaction` implications.
Expected output:
-Stable UI contract for providers + manual challenge coexistence.
Deferred hardening follow-up (out-of-scope for this fix):
-Evaluate wrapping update path in an explicit transaction for persistence + post-persistence operations, or introduce compensating rollback strategy on downstream failure where feasible.
- Produce a dedicated implementation spec for rollback hardening to avoid coupling with this flaky-failure patch.
- Record explicit risk acceptance for this issue: post-persistence failure rollback behavior remains unchanged until follow-up lands.
## Phase 4 - Backend/API enhancement (deferred by default)
### Phase 4: Test contract tightening (only after product fix)
Goal:
-Normalize active challenge retrieval contract, reduce 404 control-flow usage.
-Align E2E assertion with backend contract.
Scope rule:
-Out of scope for this spec by default.
- Enter this phase only if the frontend-only fix fails to resolve the two target failures after deterministic re-runs.
-Narrow accepted status list for invalid update from `[200, 400, 422]` to failure-only status set, but only after backend fix is in place and confirmed.
Actions:
1. Add explicit active challenge endpoint.
2. Return 204 or structured null payload for no-active state.
3. Update frontend manual flow to consume explicit endpoint.
Not allowed:
- Masking failure by weakening final unchanged-data assertion.
Expected output:
- Cleaner API semantics and fewer error-as-control-flow branches.
awaittest.step('Verify original data unchanged',async()=>{
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.