feat(fp): WP-19 — Playwright e2e smoke against the real FE+backend

Adds a happy-path spec (login → dashboard → registratie wizard, including
a real identity-document upload → real submit) and a degraded-path spec
(?scenario=error → <app-async> error slot → retry), both driving the real
app against the real .NET backend, plus a CI job that boots both.

Writing the retry spec surfaced a real bug: AsyncComponent's retry() only
reloads a [resource]-fed instance, so every real page (all [data]-fed via
a store's RemoteData) had a silently no-op retry button. Added a
retryClicked output and wired it on the dashboard's two async blocks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-04 10:13:40 +02:00
co-authored by Claude Sonnet 5
parent e272869f00
commit 26c2c5acd0
14 changed files with 754 additions and 498 deletions
+14 -1
View File
@@ -1,4 +1,12 @@
import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';
import {
Component,
Directive,
TemplateRef,
computed,
contentChild,
input,
output,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import type { Resource } from '@angular/core';
import { SpinnerComponent } from '@shared/ui/spinner/spinner.component';
@@ -130,11 +138,16 @@ export class AsyncComponent<T> {
}),
);
// [resource]-fed callers get reload() for free. [data]-fed callers (a store's
// combined RemoteData — the component doesn't own that resource) must reload
// it themselves; retryClicked is how they find out a retry was requested.
retryClicked = output<void>();
retry = () => {
const r = this.resource();
if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {
(r as { reload: () => void }).reload();
}
this.retryClicked.emit();
};
}