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:
73
frontend/tests-e2e/pages/DashboardPage.ts
Normal file
73
frontend/tests-e2e/pages/DashboardPage.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
|
||||
export class DashboardPage {
|
||||
readonly page: Page;
|
||||
readonly content: Locator;
|
||||
readonly loading: Locator;
|
||||
readonly errorMessage: Locator;
|
||||
readonly emptyState: Locator;
|
||||
readonly addServerButton: Locator;
|
||||
readonly serverCards: Locator;
|
||||
readonly sidebar: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.content = page.locator('[data-testid="dashboard-content"]');
|
||||
this.loading = page.locator('[data-testid="dashboard-loading"]');
|
||||
this.errorMessage = page.locator('[data-testid="dashboard-error"]');
|
||||
this.emptyState = page.locator('[data-testid="dashboard-empty"]');
|
||||
this.addServerButton = page.locator('[data-testid="add-server-btn"]');
|
||||
this.serverCards = page.locator("[data-testid^='server-card-']");
|
||||
this.sidebar = page.locator('[data-testid="sidebar"]');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto("/");
|
||||
await this.page.waitForLoadState("networkidle");
|
||||
}
|
||||
|
||||
async waitForLoad() {
|
||||
await this.loading.waitFor({ state: "hidden", timeout: 15_000 });
|
||||
}
|
||||
|
||||
async getServerCount() {
|
||||
return this.serverCards.count();
|
||||
}
|
||||
|
||||
async getServerCardName(index: number) {
|
||||
const card = this.serverCards.nth(index);
|
||||
return card.locator("h2").textContent();
|
||||
}
|
||||
|
||||
async getServerCardStatus(index: number) {
|
||||
const card = this.serverCards.nth(index);
|
||||
return card.locator("[data-testid^='server-card-']").getAttribute("data-testid");
|
||||
}
|
||||
|
||||
async clickStartServer(index: number) {
|
||||
const card = this.serverCards.nth(index);
|
||||
const startBtn = card.locator('button[aria-label^="Start"]');
|
||||
await startBtn.click();
|
||||
}
|
||||
|
||||
async clickStopServer(index: number) {
|
||||
const card = this.serverCards.nth(index);
|
||||
const stopBtn = card.locator('button[aria-label^="Stop"]');
|
||||
await stopBtn.click();
|
||||
}
|
||||
|
||||
async clickRestartServer(index: number) {
|
||||
const card = this.serverCards.nth(index);
|
||||
const restartBtn = card.locator('button[aria-label^="Restart"]');
|
||||
await restartBtn.click();
|
||||
}
|
||||
|
||||
async clickAddServer() {
|
||||
await this.addServerButton.click();
|
||||
}
|
||||
|
||||
async clickSidebarServer(name: string) {
|
||||
const link = this.sidebar.locator(`a:has-text("${name}")`);
|
||||
await link.click();
|
||||
}
|
||||
}
|
||||
40
frontend/tests-e2e/pages/LoginPage.ts
Normal file
40
frontend/tests-e2e/pages/LoginPage.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
|
||||
export class LoginPage {
|
||||
readonly page: Page;
|
||||
readonly card: Locator;
|
||||
readonly form: Locator;
|
||||
readonly usernameInput: Locator;
|
||||
readonly passwordInput: Locator;
|
||||
readonly submitButton: Locator;
|
||||
readonly errorMessage: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.card = page.locator('[data-testid="login-card"]');
|
||||
this.form = page.locator('[data-testid="login-form"]');
|
||||
this.usernameInput = page.locator('[data-testid="login-username"]');
|
||||
this.passwordInput = page.locator('[data-testid="login-password"]');
|
||||
this.submitButton = page.locator('[data-testid="login-submit"]');
|
||||
this.errorMessage = page.locator('[data-testid="login-error"]');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto("/login");
|
||||
await this.card.waitFor({ state: "visible" });
|
||||
}
|
||||
|
||||
async login(username: string, password: string) {
|
||||
await this.usernameInput.fill(username);
|
||||
await this.passwordInput.fill(password);
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async getErrorMessage() {
|
||||
return this.errorMessage.textContent();
|
||||
}
|
||||
|
||||
async hasErrorMessage() {
|
||||
return this.errorMessage.isVisible();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user