mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 14:11:18 -05:00
feat: Implement Playwright testing setup
This commit is contained in:
48
tests/helpers/page-objects/LoginPage.ts
Normal file
48
tests/helpers/page-objects/LoginPage.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Login Page Object Model
|
||||
*
|
||||
* Encapsulates interactions with the login page.
|
||||
*/
|
||||
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
export class LoginPage {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/auth');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
await this.page.fill('input[type="email"]', email);
|
||||
await this.page.fill('input[type="password"]', password);
|
||||
await this.page.click('button[type="submit"]');
|
||||
}
|
||||
|
||||
async expectLoginSuccess() {
|
||||
// Wait for navigation away from auth page
|
||||
await this.page.waitForURL('**/', { timeout: 10000 });
|
||||
|
||||
// Verify we're on homepage or dashboard
|
||||
await expect(this.page).not.toHaveURL(/\/auth/);
|
||||
}
|
||||
|
||||
async expectLoginError(message?: string) {
|
||||
// Check for error toast or message
|
||||
if (message) {
|
||||
await expect(this.page.getByText(message)).toBeVisible();
|
||||
} else {
|
||||
// Just verify we're still on auth page
|
||||
await expect(this.page).toHaveURL(/\/auth/);
|
||||
}
|
||||
}
|
||||
|
||||
async clickSignUp() {
|
||||
await this.page.click('text=Sign up');
|
||||
}
|
||||
|
||||
async clickForgotPassword() {
|
||||
await this.page.click('text=Forgot password');
|
||||
}
|
||||
}
|
||||
100
tests/helpers/page-objects/ModerationQueuePage.ts
Normal file
100
tests/helpers/page-objects/ModerationQueuePage.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Moderation Queue Page Object Model
|
||||
*
|
||||
* Encapsulates interactions with the moderation queue.
|
||||
*/
|
||||
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
export class ModerationQueuePage {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/moderation/queue');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async claimSubmission(index: number = 0) {
|
||||
const claimButtons = this.page.locator('button:has-text("Claim")');
|
||||
await claimButtons.nth(index).click();
|
||||
|
||||
// Wait for lock to be acquired
|
||||
await expect(this.page.getByText(/claimed by you/i)).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
|
||||
async approveSubmission(reason?: string) {
|
||||
// Click approve button
|
||||
await this.page.click('button:has-text("Approve")');
|
||||
|
||||
// Fill optional reason if provided
|
||||
if (reason) {
|
||||
await this.page.fill('textarea[placeholder*="reason"]', reason);
|
||||
}
|
||||
|
||||
// Confirm in dialog
|
||||
await this.page.click('button:has-text("Confirm")');
|
||||
|
||||
// Wait for success toast
|
||||
await expect(this.page.getByText(/approved/i)).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
async rejectSubmission(reason: string) {
|
||||
// Click reject button
|
||||
await this.page.click('button:has-text("Reject")');
|
||||
|
||||
// Fill required reason
|
||||
await this.page.fill('textarea[placeholder*="reason"]', reason);
|
||||
|
||||
// Confirm in dialog
|
||||
await this.page.click('button:has-text("Confirm")');
|
||||
|
||||
// Wait for success toast
|
||||
await expect(this.page.getByText(/rejected/i)).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
async extendLock() {
|
||||
await this.page.click('button:has-text("Extend")');
|
||||
await expect(this.page.getByText(/extended/i)).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
|
||||
async releaseLock() {
|
||||
await this.page.click('button:has-text("Release")');
|
||||
await expect(this.page.getByText(/released/i)).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
|
||||
async filterByType(type: string) {
|
||||
await this.page.selectOption('select[name="entity_type"]', type);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async filterByStatus(status: string) {
|
||||
await this.page.selectOption('select[name="status"]', status);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async searchBySubmitter(name: string) {
|
||||
await this.page.fill('input[placeholder*="submitter"]', name);
|
||||
await this.page.waitForTimeout(500); // Debounce
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async expectSubmissionVisible(submissionName: string) {
|
||||
await expect(this.page.getByText(submissionName)).toBeVisible();
|
||||
}
|
||||
|
||||
async expectSubmissionNotVisible(submissionName: string) {
|
||||
await expect(this.page.getByText(submissionName)).not.toBeVisible();
|
||||
}
|
||||
|
||||
async expectLockTimer() {
|
||||
// Check that lock timer is visible (e.g., "14:59 remaining")
|
||||
await expect(this.page.locator('[data-testid="lock-timer"]').or(
|
||||
this.page.getByText(/\d{1,2}:\d{2}.*remaining/i)
|
||||
)).toBeVisible();
|
||||
}
|
||||
|
||||
async expectLockWarning() {
|
||||
// Warning should appear at 2 minutes remaining
|
||||
await expect(this.page.getByText(/lock.*expir/i)).toBeVisible();
|
||||
}
|
||||
}
|
||||
87
tests/helpers/page-objects/ParkCreationPage.ts
Normal file
87
tests/helpers/page-objects/ParkCreationPage.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Park Creation Page Object Model
|
||||
*
|
||||
* Encapsulates interactions with the park creation/editing form.
|
||||
*/
|
||||
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
export class ParkCreationPage {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/parks/new');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async fillBasicInfo(name: string, description: string) {
|
||||
// Fill park name
|
||||
await this.page.fill('input[name="name"]', name);
|
||||
|
||||
// Slug should auto-generate, but we can override if needed
|
||||
// await this.page.fill('input[name="slug"]', slug);
|
||||
|
||||
// Fill description (might be a textarea or rich text editor)
|
||||
const descField = this.page.locator('textarea[name="description"]').first();
|
||||
await descField.fill(description);
|
||||
}
|
||||
|
||||
async selectParkType(type: string) {
|
||||
// Assuming a select or radio group
|
||||
await this.page.click(`[data-park-type="${type}"]`);
|
||||
}
|
||||
|
||||
async selectStatus(status: string) {
|
||||
await this.page.click(`[data-status="${status}"]`);
|
||||
}
|
||||
|
||||
async searchLocation(query: string) {
|
||||
const searchInput = this.page.locator('input[placeholder*="location"]').or(
|
||||
this.page.locator('input[placeholder*="search"]')
|
||||
);
|
||||
await searchInput.fill(query);
|
||||
await this.page.waitForTimeout(500); // Wait for autocomplete
|
||||
}
|
||||
|
||||
async selectLocation(name: string) {
|
||||
await this.page.click(`text=${name}`);
|
||||
}
|
||||
|
||||
async setOpeningDate(date: string, precision: 'day' | 'month' | 'year' = 'day') {
|
||||
await this.page.fill('input[name="opened_date"]', date);
|
||||
await this.page.selectOption('select[name="date_precision"]', precision);
|
||||
}
|
||||
|
||||
async uploadBannerImage(filePath: string) {
|
||||
const fileInput = this.page.locator('input[type="file"][accept*="image"]').first();
|
||||
await fileInput.setInputFiles(filePath);
|
||||
}
|
||||
|
||||
async uploadCardImage(filePath: string) {
|
||||
const fileInputs = this.page.locator('input[type="file"][accept*="image"]');
|
||||
await fileInputs.nth(1).setInputFiles(filePath);
|
||||
}
|
||||
|
||||
async uploadGalleryImages(filePaths: string[]) {
|
||||
const galleryInput = this.page.locator('input[type="file"][multiple]');
|
||||
await galleryInput.setInputFiles(filePaths);
|
||||
}
|
||||
|
||||
async selectOperator(operatorName: string) {
|
||||
await this.page.click('button:has-text("Select operator")');
|
||||
await this.page.click(`text=${operatorName}`);
|
||||
}
|
||||
|
||||
async submitForm() {
|
||||
await this.page.click('button[type="submit"]:has-text("Submit")');
|
||||
}
|
||||
|
||||
async expectSuccess() {
|
||||
// Wait for success toast
|
||||
await expect(this.page.getByText(/submitted.*review/i)).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
async expectValidationError(message: string) {
|
||||
await expect(this.page.getByText(message)).toBeVisible();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user