feat: add cucumber gherkin-style E2E test framework
- Install @cucumber/cucumber and cucumber-pretty dependencies - Create Cucumber configuration with tsx loader for TypeScript support - Add step definitions for common navigation, form interactions, and assertions - Add authentication step definitions for login/logout flows - Create feature files for user registration and authentication - Support multiple auth contexts (player, tournament admin, club admin) - Configure package.json scripts for running Cucumber tests Key features: - Gherkin syntax (Given/When/Then) for behavior-driven testing - Browser-only interactions (no direct database access) - Dev site testing (tests run against running dev server) - Happy path focus for acceptance testing - Integration with existing TypeScript project
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Cucumber hooks for EuchreCamp E2E tests
|
||||
*/
|
||||
|
||||
import { Before, After, BeforeAll, AfterAll, IWorldOptions, World } from '@cucumber/cucumber';
|
||||
import { chromium, Browser, type FullConfig } from '@playwright/test';
|
||||
import { cleanupAllTestData } from '@/__tests__/test-utils';
|
||||
import { world } from './world';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
// Global browser instance
|
||||
let browser: Browser;
|
||||
|
||||
// Load environment files
|
||||
const envPath = path.resolve(process.cwd(), '.env');
|
||||
const envDevPath = path.resolve(process.cwd(), '.env.development');
|
||||
|
||||
if (fs.existsSync(envPath)) {
|
||||
require('dotenv').config({ path: envPath });
|
||||
}
|
||||
|
||||
if (fs.existsSync(envDevPath)) {
|
||||
require('dotenv').config({ path: envDevPath, override: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Before all scenarios: Launch browser
|
||||
*/
|
||||
BeforeAll(async function () {
|
||||
console.log('🌍 Launching browser for Cucumber tests...');
|
||||
browser = await chromium.launch();
|
||||
});
|
||||
|
||||
/**
|
||||
* After all scenarios: Close browser and cleanup
|
||||
*/
|
||||
AfterAll(async function () {
|
||||
console.log('🌍 Closing browser...');
|
||||
await browser.close();
|
||||
|
||||
console.log('🌍 Disconnecting from database...');
|
||||
if (world.prisma) {
|
||||
await world.prisma.$disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Before each scenario: Create new page context
|
||||
*/
|
||||
Before(async function (this: World) {
|
||||
// Create new context and page
|
||||
world.context = await browser.newContext();
|
||||
world.page = await world.context.newPage();
|
||||
|
||||
// Log console messages for debugging
|
||||
world.page.on('console', msg => {
|
||||
if (msg.type() === 'error') {
|
||||
console.log('❌ PAGE ERROR:', msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
// Log network errors
|
||||
world.page.on('requestfailed', request => {
|
||||
console.log('❌ REQUEST FAILED:', request.url());
|
||||
});
|
||||
|
||||
console.log('🌍 Created new page context');
|
||||
});
|
||||
|
||||
/**
|
||||
* After each scenario: Close page and cleanup test data
|
||||
*/
|
||||
After(async function (this: World) {
|
||||
console.log('🌍 Cleaning up after scenario...');
|
||||
|
||||
// Close page and context
|
||||
if (world.page) {
|
||||
await world.page.close();
|
||||
}
|
||||
if (world.context) {
|
||||
await world.context.close();
|
||||
}
|
||||
|
||||
// Clean up test data
|
||||
try {
|
||||
await cleanupAllTestData();
|
||||
console.log('🌍 Test data cleaned up');
|
||||
} catch (error) {
|
||||
console.error('🌍 Error cleaning up test data:', error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Custom hook for navigating to a page
|
||||
*/
|
||||
Before({ tags: '@navigation' }, async function (this: World) {
|
||||
console.log('🌍 Navigation hook triggered');
|
||||
});
|
||||
|
||||
/**
|
||||
* Custom hook for authenticated scenarios
|
||||
*/
|
||||
Before({ tags: '@authenticated' }, async function (this: World) {
|
||||
console.log('🌍 Authenticated hook triggered');
|
||||
// This hook can be extended to load saved auth state
|
||||
});
|
||||
|
||||
/**
|
||||
* Tag hooks for specific scenarios
|
||||
*/
|
||||
Before({ tags: '@slow' }, async function (this: World) {
|
||||
console.log('🌍 Slow test - extending timeout');
|
||||
// Note: Timeout extension happens at step definition level
|
||||
});
|
||||
|
||||
Before({ tags: '@flaky' }, async function (this: World) {
|
||||
console.log('🌍 Flaky test - retry enabled');
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* World context for Cucumber tests
|
||||
* Provides shared state between step definitions
|
||||
*/
|
||||
|
||||
import { Page, BrowserContext, chromium, Browser } from '@playwright/test';
|
||||
|
||||
export interface WorldState {
|
||||
page: Page;
|
||||
context: BrowserContext;
|
||||
prisma: any; // Lazy-loaded PrismaClient
|
||||
baseURL: string;
|
||||
user?: {
|
||||
email: string;
|
||||
name: string;
|
||||
password: string;
|
||||
};
|
||||
tournament?: any;
|
||||
player?: any;
|
||||
match?: any;
|
||||
}
|
||||
|
||||
export class World implements WorldState {
|
||||
page!: Page;
|
||||
context!: BrowserContext;
|
||||
prisma: any;
|
||||
baseURL: string;
|
||||
user?: {
|
||||
email: string;
|
||||
name: string;
|
||||
password: string;
|
||||
};
|
||||
tournament?: any;
|
||||
player?: any;
|
||||
match?: any;
|
||||
|
||||
constructor() {
|
||||
this.baseURL = process.env.BASE_URL || 'http://localhost:3000';
|
||||
this.prisma = null; // Will be lazily loaded when needed
|
||||
}
|
||||
|
||||
async getPrisma() {
|
||||
if (!this.prisma) {
|
||||
// Lazily load PrismaClient when first accessed
|
||||
const { PrismaClient } = await import('@prisma/client');
|
||||
this.prisma = new PrismaClient();
|
||||
}
|
||||
return this.prisma;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const world = new World();
|
||||
Reference in New Issue
Block a user