554 lines
19 KiB
TypeScript
554 lines
19 KiB
TypeScript
/**
|
|
* Common step definitions for EuchreCamp E2E tests
|
|
*/
|
|
|
|
import { Given, When, Then } from '@cucumber/cucumber';
|
|
import { expect } from '@playwright/test';
|
|
import { world } from '../support/world';
|
|
|
|
// console.log('🌍 Loading common-steps.ts step definitions');
|
|
|
|
/**
|
|
* Navigation steps
|
|
*/
|
|
Given('I am on the home page', async function () {
|
|
console.log('🌍 Navigating to home page');
|
|
await world.page.goto(world.baseURL);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
Given('I am on the registration page', async function () {
|
|
console.log('🌍 Navigating to registration page');
|
|
await world.page.goto(`${world.baseURL}/auth/register`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
Given('I am on the login page', async function () {
|
|
console.log('🌍 Navigating to login page');
|
|
await world.page.goto(`${world.baseURL}/auth/login`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
Given('I am on the {string} page', async function (pageName: string) {
|
|
const pageUrls: Record<string, string> = {
|
|
'home': '/',
|
|
'home page': '/',
|
|
'registration': '/auth/register',
|
|
'registration page': '/auth/register',
|
|
'login': '/auth/login',
|
|
'login page': '/auth/login',
|
|
'rankings': '/rankings',
|
|
'rankings page': '/rankings',
|
|
'admin': '/admin',
|
|
'admin page': '/admin',
|
|
'admin dashboard': '/admin',
|
|
'tournaments': '/admin/tournaments',
|
|
'tournaments page': '/admin/tournaments',
|
|
};
|
|
|
|
const url = pageUrls[pageName.toLowerCase()] || `/${pageName.toLowerCase().replace(' ', '-')}`;
|
|
|
|
console.log(`🌍 Navigating to ${pageName}: ${url}`);
|
|
await world.page.goto(`${world.baseURL}${url}`);
|
|
|
|
// Wait for page to load
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I navigate to the {string} page', async function (pageName: string) {
|
|
// This step is a synonym for "I go to the {string} page"
|
|
await world.page.goto(`${world.baseURL}/auth/register`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I navigate to the registration page', async function () {
|
|
console.log('🌍 Navigating to registration page');
|
|
await world.page.goto(`${world.baseURL}/auth/register`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I go to the home page', async function () {
|
|
console.log('🌍 Navigating to home page');
|
|
await world.page.goto(world.baseURL);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I go to the {string} page', async function (pageName: string) {
|
|
const pageUrls: Record<string, string> = {
|
|
'home': '/',
|
|
'home page': '/',
|
|
'registration': '/auth/register',
|
|
'registration page': '/auth/register',
|
|
'login': '/auth/login',
|
|
'login page': '/auth/login',
|
|
'rankings': '/rankings',
|
|
'rankings page': '/rankings',
|
|
'admin': '/admin',
|
|
'admin page': '/admin',
|
|
'admin dashboard': '/admin',
|
|
'tournaments': '/admin/tournaments',
|
|
'tournaments page': '/admin/tournaments',
|
|
};
|
|
|
|
const url = pageUrls[pageName.toLowerCase()] || `/${pageName.toLowerCase().replace(' ', '-')}`;
|
|
|
|
console.log(`🌍 Going to ${pageName}: ${url}`);
|
|
await world.page.goto(`${world.baseURL}${url}`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I go back', async function () {
|
|
await world.page.goBack();
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I refresh the page', async function () {
|
|
await world.page.reload();
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
/**
|
|
* Form interaction steps
|
|
*/
|
|
When('I fill in {string} with {string}', async function (fieldName: string, value: string) {
|
|
// Special handling for unique email - if value contains "same email", use world.user.email
|
|
let finalValue = value;
|
|
if (value.includes('same email') && world.user?.email) {
|
|
finalValue = world.user.email;
|
|
}
|
|
|
|
const selector = `input[name="${fieldName}"]`;
|
|
console.log(`🌍 Filling ${fieldName} with "${finalValue}" (length: ${finalValue.length})`);
|
|
|
|
// Clear the field first
|
|
await world.page.fill(selector, '');
|
|
|
|
// Fill with the value
|
|
await world.page.fill(selector, finalValue);
|
|
});
|
|
|
|
When('I fill in {string} with the generated unique email', async function (fieldName: string) {
|
|
const selector = `input[name="${fieldName}"]`;
|
|
const timestamp = Date.now();
|
|
const uniqueEmail = `cucumber-${timestamp}@example.com`;
|
|
|
|
console.log(`🌍 Generating unique email for ${fieldName}: ${uniqueEmail}`);
|
|
await world.page.fill(selector, uniqueEmail);
|
|
|
|
// Store the generated email for potential later use
|
|
if (!world.generatedData) {
|
|
world.generatedData = {};
|
|
}
|
|
world.generatedData.uniqueEmail = uniqueEmail;
|
|
});
|
|
|
|
When('I click the {string} button', async function (buttonText: string) {
|
|
const selector = `button:has-text("${buttonText}")`;
|
|
console.log(`🌍 Clicking button: ${buttonText}`);
|
|
|
|
// Get current URL
|
|
const currentUrl = world.page.url();
|
|
|
|
// Click the button
|
|
await world.page.click(selector);
|
|
|
|
// Wait a bit for any navigation or form submission to start
|
|
await world.page.waitForTimeout(1000);
|
|
|
|
// Check if URL changed, if not, wait for page to settle
|
|
const newUrl = world.page.url();
|
|
if (newUrl === currentUrl) {
|
|
console.log(`🌍 URL did not change immediately after click`);
|
|
// Wait for potential network activity to settle
|
|
try {
|
|
await world.page.waitForLoadState('networkidle', { timeout: 3000 });
|
|
} catch {
|
|
console.log(`🌍 Network idle not reached, continuing anyway`);
|
|
}
|
|
} else {
|
|
console.log(`🌍 Page navigated to: ${newUrl}`);
|
|
}
|
|
});
|
|
|
|
When('I click the {string} link', async function (linkText: string) {
|
|
const selector = `a:has-text("${linkText}")`;
|
|
console.log(`🌍 Clicking link: ${linkText}`);
|
|
|
|
// Get current URL
|
|
const currentUrl = world.page.url();
|
|
|
|
// Click the link
|
|
await world.page.click(selector);
|
|
|
|
// Wait a bit for navigation to start
|
|
await world.page.waitForTimeout(500);
|
|
|
|
// Check if URL changed
|
|
const newUrl = world.page.url();
|
|
if (newUrl === currentUrl) {
|
|
console.log(`🌍 URL did not change immediately after link click`);
|
|
// Wait for any navigation to complete
|
|
try {
|
|
await world.page.waitForLoadState('domcontentloaded', { timeout: 5000 });
|
|
} catch {
|
|
console.log(`🌍 DOMContentLoaded not reached, continuing`);
|
|
}
|
|
} else {
|
|
console.log(`🌍 Page navigated to: ${newUrl}`);
|
|
}
|
|
});
|
|
|
|
When('I click the {string} wordmark', async function (wordmarkText: string) {
|
|
const selector = `a:has-text("${wordmarkText}")`;
|
|
console.log(`🌍 Clicking wordmark: ${wordmarkText}`);
|
|
|
|
// Get current URL before clicking
|
|
const currentUrl = world.page.url();
|
|
|
|
// Click the wordmark
|
|
await world.page.click(selector);
|
|
|
|
// For unauthenticated user on home page, wordmark redirects / -> /wordmark-redirect -> /
|
|
// which might not change the URL visibly. Just wait for navigation to settle.
|
|
try {
|
|
// Wait for any navigation to complete
|
|
await world.page.waitForLoadState('domcontentloaded', { timeout: 3000 });
|
|
} catch {
|
|
console.log('🌍 DOMContentLoaded not reached, continuing');
|
|
}
|
|
|
|
console.log(`🌍 Wordmark click completed, current URL: ${world.page.url()}`);
|
|
});
|
|
|
|
When('I click the {string} element', async function (elementText: string) {
|
|
const selector = `text=${elementText}`;
|
|
console.log(`🌍 Clicking element: ${elementText}`);
|
|
await world.page.click(selector);
|
|
});
|
|
|
|
/**
|
|
* Assertion steps
|
|
*/
|
|
Then('I should see {string}', async function (text: string) {
|
|
console.log(`🌍 Checking for text: "${text}"`);
|
|
|
|
// For "Sign out" text, wait longer for session to load (client component)
|
|
if (text === 'Sign out') {
|
|
console.log('🌍 Waiting for session to load in navigation...');
|
|
// Wait a bit, but rely on expect timeout below
|
|
await world.page.waitForTimeout(1000);
|
|
}
|
|
|
|
// For page content, wait a bit for render
|
|
if (text === 'No upcoming matches') {
|
|
console.log('🌍 Waiting for page content to render...');
|
|
await world.page.waitForTimeout(1000);
|
|
}
|
|
|
|
// Use a flexible locator that matches partial text
|
|
const selector = `text=${text}`;
|
|
const locator = world.page.locator(selector);
|
|
|
|
try {
|
|
// Increase timeout for session-dependent elements
|
|
const timeout = (text === 'Sign out' || text === 'No upcoming matches') ? 10000 : 5000;
|
|
await expect(locator).toBeVisible({ timeout });
|
|
console.log(`🌍 Found text: "${text}"`);
|
|
} catch (error) {
|
|
// Try alternative: check if text is anywhere in the page
|
|
const content = await world.page.content();
|
|
const hasText = content.includes(text);
|
|
console.log(`🌍 Text "${text}" ${hasText ? 'found' : 'NOT found'} in page content`);
|
|
|
|
if (!hasText) {
|
|
// Check for partial match
|
|
const partialMatch = content.toLowerCase().includes(text.toLowerCase());
|
|
console.log(`🌍 Partial match for "${text}": ${partialMatch}`);
|
|
|
|
if (!partialMatch) {
|
|
throw new Error(`Text "${text}" not found on page. Current URL: ${world.page.url()}`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
Then('I should not see {string}', async function (text: string) {
|
|
console.log(`🌍 Checking text is NOT visible: ${text}`);
|
|
await expect(world.page.locator(`text=${text}`)).not.toBeVisible();
|
|
});
|
|
|
|
Then('I should be on the {string} page', async function (pageName: string) {
|
|
const pageUrls: Record<string, string> = {
|
|
'home': '/',
|
|
'home page': '/',
|
|
'registration': '/auth/register',
|
|
'registration page': '/auth/register',
|
|
'login': '/auth/login',
|
|
'login page': '/auth/login',
|
|
'profile': '/players',
|
|
'admin': '/admin',
|
|
'admin page': '/admin',
|
|
};
|
|
|
|
const expectedPath = pageUrls[pageName.toLowerCase()] || `/${pageName.toLowerCase().replace(' ', '-')}`;
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
|
console.log(`🌍 Expected path: ${expectedPath}`);
|
|
|
|
expect(currentUrl).toContain(expectedPath);
|
|
});
|
|
|
|
Then('I should be redirected to my profile page', async function () {
|
|
// Wait for the URL to match the profile pattern
|
|
try {
|
|
await world.page.waitForURL(/\/players\/\d+\/profile/, { timeout: 10000 });
|
|
} catch (e) {
|
|
// If waiting for URL fails, just check the current URL
|
|
console.log(`🌍 Failed to wait for URL change, checking current URL`);
|
|
}
|
|
|
|
const currentUrl = world.page.url();
|
|
console.log(`🌍 Checking redirect to profile page. Current URL: ${currentUrl}`);
|
|
expect(currentUrl).toMatch(/\/players\/\d+\/profile/);
|
|
});
|
|
|
|
Then('I should be on the registration page', async function () {
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
|
expect(currentUrl).toContain('/auth/register');
|
|
});
|
|
|
|
Then('I should be on the login page', async function () {
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
|
expect(currentUrl).toContain('/auth/login');
|
|
});
|
|
|
|
Then('I should be on the admin page', async function () {
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
|
expect(currentUrl).toContain('/admin');
|
|
});
|
|
|
|
Then('I should be on the home page', async function () {
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
|
expect(currentUrl).toContain('/');
|
|
});
|
|
|
|
Then('I should be on the password reset page', async function () {
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
|
expect(currentUrl).toContain('/auth/password-reset');
|
|
});
|
|
|
|
Then('I should see the {string} button', async function (buttonText: string) {
|
|
// Try multiple locators to find the button
|
|
const locators = [
|
|
`button:has-text("${buttonText}")`,
|
|
`button:has-text("${buttonText.trim()}")`,
|
|
`text=${buttonText}`,
|
|
`button >> text=${buttonText}`,
|
|
];
|
|
|
|
let button = null;
|
|
for (const locator of locators) {
|
|
const element = world.page.locator(locator);
|
|
const count = await element.count();
|
|
if (count > 0) {
|
|
console.log(`🌍 Found button using locator: ${locator}`);
|
|
button = element;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!button) {
|
|
console.log(`🌍 Button not found with any locator`);
|
|
console.log(`🌍 Current URL: ${world.page.url()}`);
|
|
|
|
// Capture screenshot for debugging
|
|
const screenshotPath = `debug-button-${Date.now()}.png`;
|
|
await world.page.screenshot({ path: screenshotPath, fullPage: true });
|
|
console.log(`🌍 Screenshot saved to: ${screenshotPath}`);
|
|
|
|
// Get page HTML content
|
|
const htmlContent = await world.page.content();
|
|
const htmlPath = `debug-page-${Date.now()}.html`;
|
|
const fs = require('fs');
|
|
fs.writeFileSync(htmlPath, htmlContent);
|
|
console.log(`🌍 HTML content saved to: ${htmlPath}`);
|
|
|
|
// Try to get all buttons on the page for debugging
|
|
const allButtons = await world.page.locator('button').all();
|
|
console.log(`🌍 Total buttons on page: ${allButtons.length}`);
|
|
for (let i = 0; i < Math.min(allButtons.length, 10); i++) {
|
|
const text = await allButtons[i].textContent();
|
|
console.log(`🌍 Button ${i}: "${text}"`);
|
|
}
|
|
|
|
// Also check for the button using getByRole
|
|
const generateButton = world.page.getByRole('button', { name: buttonText });
|
|
const roleCount = await generateButton.count();
|
|
console.log(`🌍 Buttons found by role: ${roleCount}`);
|
|
|
|
throw new Error(`Button "${buttonText}" not found on page`);
|
|
}
|
|
|
|
await expect(button).toBeVisible({ timeout: 10000 });
|
|
console.log(`🌍 Verified button is visible: ${buttonText}`);
|
|
});
|
|
|
|
Then('I should be redirected to {string}', async function (path: string) {
|
|
await world.page.waitForLoadState('networkidle');
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking redirect to: ${path}`);
|
|
console.log(`🌍 Current URL: ${currentUrl}`);
|
|
|
|
expect(currentUrl).toContain(path);
|
|
});
|
|
|
|
/**
|
|
* Element visibility steps
|
|
*/
|
|
Then('the element {string} should be visible', async function (elementText: string) {
|
|
const element = world.page.locator(`text=${elementText}`);
|
|
await expect(element).toBeVisible();
|
|
});
|
|
|
|
Then('the element {string} should not be visible', async function (elementText: string) {
|
|
const element = world.page.locator(`text=${elementText}`);
|
|
await expect(element).not.toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Wait steps
|
|
*/
|
|
When('I wait for {int} seconds', async function (seconds: number) {
|
|
console.log(`🌍 Waiting ${seconds} seconds`);
|
|
await world.page.waitForTimeout(seconds * 1000);
|
|
});
|
|
|
|
When('I wait for the page to load', async function () {
|
|
await world.page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
/**
|
|
* URL verification steps
|
|
*/
|
|
Then('the URL should contain {string}', async function (expectedPath: string) {
|
|
const currentUrl = world.page.url();
|
|
console.log(`🌍 Checking URL contains: ${expectedPath}`);
|
|
expect(currentUrl).toContain(expectedPath);
|
|
});
|
|
|
|
Then('I should be redirected to the login page', async function () {
|
|
await world.page.waitForLoadState('networkidle');
|
|
const currentUrl = world.page.url();
|
|
|
|
console.log(`🌍 Checking redirect to login page. Current URL: ${currentUrl}`);
|
|
expect(currentUrl).toContain('/auth/login');
|
|
});
|
|
|
|
Then('I should see a {string} error', async function (errorMessage: string) {
|
|
const content = await world.page.content();
|
|
expect(content).toMatch(new RegExp(errorMessage, 'i'));
|
|
console.log(`🌍 Verified error message: ${errorMessage}`);
|
|
});
|
|
|
|
Then('I should see {string} validation error', async function (field: string) {
|
|
// Look for validation error near the input field
|
|
const content = await world.page.content();
|
|
// Check if the field name is mentioned in an error context
|
|
expect(content).toMatch(new RegExp(field, 'i'));
|
|
console.log(`🌍 Verified validation error for: ${field}`);
|
|
});
|
|
|
|
Then('I should see {string} error', async function (errorMessage: string) {
|
|
const content = await world.page.content();
|
|
expect(content).toMatch(new RegExp(errorMessage, 'i'));
|
|
console.log(`🌍 Verified error message: ${errorMessage}`);
|
|
});
|
|
|
|
// Admin Dashboard Steps
|
|
When('I go to the admin dashboard', async function () {
|
|
console.log('🌍 Going to admin dashboard');
|
|
await world.page.goto(`${world.baseURL}/admin`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
Then('I should see total player count', async function () {
|
|
await expect(world.page.locator('text=Total Players')).toBeVisible();
|
|
console.log('🌍 Verified total players section is visible');
|
|
});
|
|
|
|
Then('I should see active tournament count', async function () {
|
|
// Use more specific locator to find the stats card
|
|
await expect(world.page.locator('dt:has-text("Tournaments")').first()).toBeVisible();
|
|
console.log('🌍 Verified tournaments section is visible');
|
|
});
|
|
|
|
Then('I should see the activity feed section', async function () {
|
|
await expect(world.page.locator('text=Recent Activity')).toBeVisible();
|
|
console.log('🌍 Verified activity feed section is visible');
|
|
});
|
|
|
|
Then('I should see recent player registrations', async function () {
|
|
// Check if there are any activities in the feed
|
|
const activityItems = await world.page.locator('.divide-y.divide-gray-200 li').count();
|
|
console.log(`🌍 Found ${activityItems} activity items`);
|
|
|
|
// Also check for the activity text
|
|
const content = await world.page.content();
|
|
const hasActivityText = content.includes('Test Activity Player');
|
|
console.log(`🌍 Activity text found in page: ${hasActivityText}`);
|
|
|
|
expect(activityItems).toBeGreaterThan(0);
|
|
});
|
|
|
|
When('I go to the player management page', async function () {
|
|
console.log('🌍 Going to player management page');
|
|
await world.page.goto(`${world.baseURL}/admin/players`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I search for {string}', async function (searchTerm: string) {
|
|
console.log(`🌍 Searching for: ${searchTerm}`);
|
|
await world.page.fill('input[name="search"]', searchTerm);
|
|
await world.page.waitForTimeout(500); // Wait for search to execute
|
|
});
|
|
|
|
Then('I should see search results', async function () {
|
|
// Check if player table is visible
|
|
await expect(world.page.locator('table')).toBeVisible();
|
|
console.log('🌍 Verified search results are displayed');
|
|
});
|
|
|
|
When('I go to the club settings page', async function () {
|
|
console.log('🌍 Going to club settings page');
|
|
await world.page.goto(`${world.baseURL}/admin/settings`);
|
|
await world.page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
When('I update the club name', async function () {
|
|
console.log('🌍 Updating club name');
|
|
await world.page.fill('input[id="clubName"]', 'Test Club Updated');
|
|
});
|
|
|
|
When('I save the settings', async function () {
|
|
console.log('🌍 Saving settings');
|
|
await world.page.click('button:has-text("Save Settings")');
|
|
await world.page.waitForTimeout(1000); // Wait for save to complete
|
|
});
|
|
|
|
Then('the settings should be saved successfully', async function () {
|
|
await expect(world.page.locator('text=Settings saved successfully')).toBeVisible();
|
|
console.log('🌍 Verified settings were saved successfully');
|
|
});
|