feat: add Playwright E2E testing setup with POM and testids
- Install @playwright/test and Chromium browser - Create playwright.config.ts with dev server integration - Add data-testid attributes to LoginPage, DashboardPage, ServerCard, Sidebar - Exclude tests-e2e from vitest config - Create Page Object Models: LoginPage, DashboardPage - Add 18 E2E tests: 6 login flow, 12 dashboard (happy, empty, error states) - Add test:e2e and test:e2e:ui scripts to package.json
This commit is contained in:
@@ -11,7 +11,7 @@ export function Sidebar() {
|
||||
const activeId = serverId ? parseInt(serverId) : null;
|
||||
|
||||
return (
|
||||
<nav className="w-64 h-screen bg-surface-base border-r border-surface-raised flex flex-col">
|
||||
<nav className="w-64 h-screen bg-surface-base border-r border-surface-raised flex flex-col" data-testid="sidebar">
|
||||
<div className="px-6 py-5 border-b border-surface-raised">
|
||||
<h1 className="text-text-primary font-bold text-lg tracking-tight">
|
||||
<span className="text-accent">Languard</span>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ServerCard({ server }: ServerCardProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="neu-card p-5 flex flex-col gap-4">
|
||||
<div className="neu-card p-5 flex flex-col gap-4" data-testid={`server-card-${server.id}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<StatusLed status={server.status} />
|
||||
|
||||
@@ -12,7 +12,7 @@ export function DashboardPage() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="flex items-center justify-center h-64" data-testid="dashboard-loading">
|
||||
<div className="text-text-secondary">Loading servers...</div>
|
||||
</div>
|
||||
);
|
||||
@@ -20,14 +20,14 @@ export function DashboardPage() {
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="flex items-center justify-center h-64" data-testid="dashboard-error">
|
||||
<div className="text-status-crashed">Failed to load servers</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="p-6" data-testid="dashboard-content">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-text-primary font-bold text-xl">Dashboard</h1>
|
||||
@@ -35,14 +35,14 @@ export function DashboardPage() {
|
||||
{servers?.length ?? 0} server{servers?.length !== 1 ? "s" : ""} configured
|
||||
</p>
|
||||
</div>
|
||||
<Link to="/servers/new" className="btn-primary flex items-center gap-1.5 text-sm">
|
||||
<Link to="/servers/new" className="btn-primary flex items-center gap-1.5 text-sm" data-testid="add-server-btn">
|
||||
<Plus size={14} />
|
||||
Add Server
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{servers?.length === 0 ? (
|
||||
<div className="neu-card p-12 text-center">
|
||||
<div className="neu-card p-12 text-center" data-testid="dashboard-empty">
|
||||
<p className="text-text-secondary">No servers configured yet.</p>
|
||||
<Link to="/servers/new" className="btn-primary mt-4 inline-flex items-center gap-2">
|
||||
<Plus size={14} />
|
||||
|
||||
@@ -45,7 +45,7 @@ export function LoginPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-surface-base flex items-center justify-center p-4">
|
||||
<div className="neu-card p-8 w-full max-w-sm">
|
||||
<div className="neu-card p-8 w-full max-w-sm" data-testid="login-card">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-text-primary">
|
||||
<span className="text-accent">Languard</span>
|
||||
@@ -53,7 +53,7 @@ export function LoginPage() {
|
||||
<p className="text-text-secondary text-sm mt-1">Server Manager</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4" data-testid="login-form">
|
||||
<div>
|
||||
<label className="block text-text-secondary text-sm mb-1.5" htmlFor="username">
|
||||
Username
|
||||
@@ -63,6 +63,7 @@ export function LoginPage() {
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
className="neu-input w-full"
|
||||
data-testid="login-username"
|
||||
{...register("username")}
|
||||
/>
|
||||
{errors.username && (
|
||||
@@ -79,6 +80,7 @@ export function LoginPage() {
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
className="neu-input w-full"
|
||||
data-testid="login-password"
|
||||
{...register("password")}
|
||||
/>
|
||||
{errors.password && (
|
||||
@@ -87,7 +89,7 @@ export function LoginPage() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-surface-recessed border border-status-crashed rounded-lg px-3 py-2">
|
||||
<div className="bg-surface-recessed border border-status-crashed rounded-lg px-3 py-2" data-testid="login-error">
|
||||
<p className="text-status-crashed text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -96,6 +98,7 @@ export function LoginPage() {
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="btn-primary w-full mt-2"
|
||||
data-testid="login-submit"
|
||||
>
|
||||
{isSubmitting ? "Signing in..." : "Sign In"}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user