/** * 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 password reset page', async function () { console.log('🌍 Navigating to password reset page'); await world.page.goto(`${world.baseURL}/auth/password-reset`); await world.page.waitForLoadState('domcontentloaded'); }); Given('I am on the {string} page', async function (pageName: string) { const pageUrls: Record = { '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 = { '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 () { console.log('🌍 About to refresh page from URL:', world.page.url()); await world.page.reload({ waitUntil: 'load' }); console.log('🌍 Page refreshed, new URL:', world.page.url()); // Wait extra time for full render await world.page.waitForTimeout(2000); const content = await world.page.content(); console.log('🌍 After refresh - has "Round":', content.includes('Round')); console.log('🌍 After refresh - has "Generated":', content.includes('Generated')); }); /** * 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('domcontentloaded', { 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}`); // Click the link await world.page.click(selector); // Wait for navigation to complete try { await world.page.waitForLoadState('domcontentloaded', { timeout: 10000 }); } catch { console.log(`🌍 Networkidle not reached, continuing`); } const newUrl = world.page.url(); 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 = { '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('domcontentloaded'); 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('domcontentloaded'); }); /** * 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('domcontentloaded'); 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'); }); // Rankings Page Steps When('I go to the rankings page', async function () { console.log('🌍 Going to rankings page'); await world.page.goto(`${world.baseURL}/rankings`); await world.page.waitForLoadState('domcontentloaded'); }); Then('I should see {string} in the page heading', async function (heading: string) { // Use a more flexible selector that matches text content await expect(world.page.locator(`text=${heading}`)).toBeVisible(); console.log(`🌍 Verified heading "${heading}" is visible`); }); Then('I should see a rankings table', async function () { await expect(world.page.locator('table')).toBeVisible(); console.log('🌍 Verified rankings table is visible'); }); Then('I should see a rankings table with columns', async function () { const table = world.page.locator('table'); await expect(table).toBeVisible(); const headerCount = await world.page.locator('th').count(); expect(headerCount).toBeGreaterThan(0); console.log(`🌍 Verified rankings table has ${headerCount} columns`); }); Then('the table should have column headers', async function () { const headerCount = await world.page.locator('th').count(); expect(headerCount).toBeGreaterThan(0); console.log(`🌍 Verified table has ${headerCount} column headers`); }); Given('I am not logged in', async function () { // This is just a documentation step - the test environment starts fresh console.log('🌍 User is not logged in (fresh session)'); }); Then('I should be on the rankings page', async function () { const currentUrl = world.page.url(); console.log(`🌍 Checking current URL: ${currentUrl}`); expect(currentUrl).toContain('/rankings'); }); Then('I should see the rankings table', async function () { await expect(world.page.locator('table')).toBeVisible(); console.log('🌍 Verified rankings table is visible'); }); // Player Schedule Steps Then('I should see the match date', async function () { const content = await world.page.content(); const hasDate = content.match(/\d{1,2}\/\d{1,2}\/\d{4}/) || content.match(/\w+ \d{1,2}, \d{4}/); expect(hasDate).toBeTruthy(); console.log('🌍 Verified match date is visible'); }); Then('I should see my opponent\'s name', async function () { const content = await world.page.content(); const hasOpponent = content.includes('Opponent'); expect(hasOpponent).toBe(true); console.log('🌍 Verified opponent name is visible'); }); Then('I should see my partner\'s name', async function () { const content = await world.page.content(); const hasPartner = content.includes('Partner'); expect(hasPartner).toBe(true); console.log('🌍 Verified partner name is visible'); }); Then('I should see the tournament name', async function () { const content = await world.page.content(); const hasTournament = content.includes('Test Schedule Tournament'); expect(hasTournament).toBe(true); console.log('🌍 Verified tournament name is visible'); }); When('I click on a match', async function () { const matchLink = world.page.locator('a[href*="/matches/"]').first(); await matchLink.click(); await world.page.waitForLoadState('domcontentloaded'); console.log('🌍 Clicked on match'); }); Then('I should be on the match detail page', async function () { const currentUrl = world.page.url(); console.log(`🌍 Checking current URL: ${currentUrl}`); expect(currentUrl).toMatch(/\/matches\/\d+/); }); // Tournament Schedule Steps Then('I should see round {int} matchups', async function (roundNumber: number) { const roundText = `Round ${roundNumber}`; const roundHeader = world.page.locator(`h3:has-text("${roundText}")`); await expect(roundHeader).toBeVisible({ timeout: 30000 }); console.log(`🌍 Verified round ${roundNumber} matchups are visible`); }); Then('I should see {int} rounds', async function (expectedRounds: number) { await world.page.waitForLoadState('domcontentloaded'); await world.page.waitForTimeout(2000); const roundHeaders = await world.page.locator('h3:has-text("Round")').count(); expect(roundHeaders).toBe(expectedRounds); console.log(`🌍 Verified ${expectedRounds} rounds are visible`); }); Then('each team should play every other team exactly once', async function () { const content = await world.page.content(); expect(content).toMatch(/schedule|round|matchup/i); console.log('🌍 Verified schedule exists with matchups'); }); When('I click on a matchup', async function () { const matchup = world.page.locator('[data-testid="matchup"]').first(); await matchup.waitFor({ state: 'visible', timeout: 15000 }); const href = await matchup.getAttribute('href'); console.log(`🌍 Matchup link href: ${href}`); if (href) { await world.page.goto(`${world.baseURL}${href}`); } else { await matchup.click(); } await world.page.waitForLoadState('domcontentloaded'); console.log(`🌍 Navigated to: ${world.page.url()}`); }); Then('I should be on the match result entry page', async function () { const currentUrl = world.page.url(); console.log(`🌍 Checking current URL: ${currentUrl}`); expect(currentUrl).toMatch(/\/matches\/|\/admin\/tournaments\/\d+\/(entry|results)/); }); // View As Role Steps When('I view the navigation', async function () { await world.page.waitForLoadState('domcontentloaded'); await world.page.waitForTimeout(1000); console.log('🌍 Viewing navigation'); }); Then('I should see the role switcher dropdown', async function () { const switcher = world.page.locator('[data-testid="role-switcher"]'); await expect(switcher).toBeVisible({ timeout: 5000 }); console.log('🌍 Verified role switcher dropdown is visible'); }); Then('the role switcher should default to {string}', async function (expectedText: string) { const switcher = world.page.locator('[data-testid="role-switcher"]'); const selectedValue = await switcher.inputValue(); const selectedText = await switcher.locator('option:checked').textContent(); console.log(`🌍 Dropdown selected text: "${selectedText}", value: "${selectedValue}"`); expect(selectedText?.trim()).toBe(expectedText); }); When('I select {string} from the role switcher', async function (optionText: string) { const switcher = world.page.locator('[data-testid="role-switcher"]'); await switcher.selectOption({ label: optionText }); await world.page.waitForTimeout(500); console.log(`🌍 Selected "${optionText}" from role switcher`); }); Then('I should see the player navigation links', async function () { await expect(world.page.locator('nav a:has-text("Rankings")')).toBeVisible(); await expect(world.page.locator('nav a:has-text("Tournaments")')).toBeVisible(); console.log('🌍 Verified player navigation links are visible'); }); Then('I should not see the {string} link', async function (linkText: string) { const link = world.page.locator(`nav a:has-text("${linkText}")`); await expect(link).not.toBeVisible({ timeout: 3000 }); console.log(`🌍 Verified "${linkText}" nav link is not visible`); }); Then('I should see the {string} link', async function (linkText: string) { const link = world.page.locator(`nav a:has-text("${linkText}")`); await expect(link).toBeVisible({ timeout: 5000 }); console.log(`🌍 Verified "${linkText}" nav link is visible`); }); Then('I should see a banner indicating I am viewing as {string}', async function (roleName: string) { const banner = world.page.locator(`text=Viewing as ${roleName}`); await expect(banner).toBeVisible({ timeout: 5000 }); console.log(`🌍 Verified viewing as ${roleName} banner is visible`); }); Then('I should not see the viewing as banner', async function () { const banner = world.page.locator('[data-testid="reset-view-as"]'); await expect(banner).not.toBeVisible({ timeout: 3000 }); console.log('🌍 Verified viewing as banner is not visible'); }); // Bracket Visualization Steps When('I go to the tournament detail page', async function () { const tournamentId = world.tournament?.id || 1; await world.page.goto(`${world.baseURL}/admin/tournaments/${tournamentId}`); await world.page.waitForLoadState('domcontentloaded'); await world.page.waitForTimeout(500); console.log(`🌍 Navigated to tournament detail page: ${tournamentId}`); }); When('I click the {string} tab', async function (tabName: string) { const tab = world.page.locator(`button:has-text("${tabName}")`); await tab.click(); await world.page.waitForTimeout(500); console.log(`🌍 Clicked "${tabName}" tab`); }); Then('I should see bracket matchup cards', async function () { const cards = world.page.locator('[data-testid="bracket-matchup"]'); const count = await cards.count(); expect(count).toBeGreaterThan(0); console.log(`🌍 Found ${count} bracket matchup cards`); }); Then('I should see bracket matchup cards with team names', async function () { const cards = world.page.locator('[data-testid="bracket-matchup"]'); const count = await cards.count(); expect(count).toBeGreaterThan(0); const firstCard = cards.first(); const text = await firstCard.textContent(); expect(text).toBeTruthy(); expect(text!.length).toBeGreaterThan(2); console.log(`🌍 Verified bracket matchup cards have team names`); }); Then('I should not see the {string} tab', async function (tabName: string) { const tab = world.page.locator(`button:has-text("${tabName}")`); await expect(tab).not.toBeVisible({ timeout: 3000 }); console.log(`🌍 Verified "${tabName}" tab is not visible`); });