Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
253 lines
6.9 KiB
TypeScript
253 lines
6.9 KiB
TypeScript
import type { Page, Locator } from '@playwright/test'
|
|
import { expect } from '@playwright/test'
|
|
|
|
/**
|
|
* Page Object Model for the Global Header Navigation
|
|
*
|
|
* Encapsulates interactions with the site-wide navigation header:
|
|
* - Dropdown menu opening/closing
|
|
* - Navigation link clicks
|
|
* - Announcement bar interactions
|
|
* - Menu item assertions
|
|
*
|
|
* Navigation structure:
|
|
* - Home (direct link)
|
|
* - For Workers → Providers, Performers, Fangirls, Camgirls
|
|
* - For Customers → Clients, Fans
|
|
* - Platform → Apps, Values
|
|
* - Company → Investors, Terms, Privacy
|
|
*/
|
|
export class HeaderPage {
|
|
readonly page: Page
|
|
readonly header: Locator
|
|
readonly nav: Locator
|
|
|
|
// Top-level navigation items
|
|
readonly homeLink: Locator
|
|
readonly forWorkersDropdown: Locator
|
|
readonly forCustomersDropdown: Locator
|
|
readonly platformDropdown: Locator
|
|
readonly companyDropdown: Locator
|
|
|
|
// Dropdown menus (when open)
|
|
readonly dropdownMenu: Locator
|
|
|
|
// Announcement bar
|
|
readonly announcementBar: Locator
|
|
readonly announcementCta: Locator
|
|
|
|
constructor(page: Page) {
|
|
this.page = page
|
|
|
|
// Main elements - use role selector for specificity
|
|
this.header = page.locator('.site-header')
|
|
this.nav = page.getByRole('navigation').first()
|
|
|
|
// Top-level navigation links - use getByRole for better specificity
|
|
// The nav items are links inside the first navigation element
|
|
this.homeLink = this.nav.getByRole('link', { name: 'Home' })
|
|
this.forWorkersDropdown = this.nav.getByRole('link', { name: 'For Workers' })
|
|
this.forCustomersDropdown = this.nav.getByRole('link', { name: 'For Customers' })
|
|
this.platformDropdown = this.nav.getByRole('link', { name: 'Platform' })
|
|
this.companyDropdown = this.nav.getByRole('link', { name: 'Company' })
|
|
|
|
// Dropdown menu container
|
|
this.dropdownMenu = page.locator('.nav-dropdown')
|
|
|
|
// Announcement bar
|
|
this.announcementBar = page.locator('.site-header__announcement')
|
|
this.announcementCta = page.locator('.site-header__announcement-cta')
|
|
}
|
|
|
|
/**
|
|
* Assert the header is visible
|
|
*/
|
|
async assertVisible() {
|
|
await expect(this.header).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Assert navigation contains all main items
|
|
*/
|
|
async assertAllNavigationItemsVisible() {
|
|
await expect(this.homeLink).toBeVisible()
|
|
await expect(this.forWorkersDropdown).toBeVisible()
|
|
await expect(this.forCustomersDropdown).toBeVisible()
|
|
await expect(this.platformDropdown).toBeVisible()
|
|
await expect(this.companyDropdown).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Click Home link in navigation
|
|
*/
|
|
async clickHome() {
|
|
await this.homeLink.click()
|
|
}
|
|
|
|
/**
|
|
* Open the "For Workers" dropdown
|
|
*/
|
|
async openWorkersDropdown() {
|
|
await this.forWorkersDropdown.click()
|
|
// Wait for dropdown animation
|
|
await this.page.waitForTimeout(200)
|
|
}
|
|
|
|
/**
|
|
* Open the "For Customers" dropdown
|
|
*/
|
|
async openCustomersDropdown() {
|
|
await this.forCustomersDropdown.click()
|
|
await this.page.waitForTimeout(200)
|
|
}
|
|
|
|
/**
|
|
* Open the "Platform" dropdown
|
|
*/
|
|
async openPlatformDropdown() {
|
|
await this.platformDropdown.click()
|
|
await this.page.waitForTimeout(200)
|
|
}
|
|
|
|
/**
|
|
* Open the "Company" dropdown
|
|
*/
|
|
async openCompanyDropdown() {
|
|
await this.companyDropdown.click()
|
|
await this.page.waitForTimeout(200)
|
|
}
|
|
|
|
/**
|
|
* Navigate to a page via Workers dropdown
|
|
*/
|
|
async navigateToWorkerPage(type: 'provider' | 'performer' | 'fangirl' | 'camgirl') {
|
|
await this.openWorkersDropdown()
|
|
const labelMap = {
|
|
provider: /Providers/i,
|
|
performer: /Performers/i,
|
|
fangirl: /Fangirls/i,
|
|
camgirl: /Camgirls/i,
|
|
}
|
|
const link = this.nav.getByRole('link', { name: labelMap[type] })
|
|
await link.click()
|
|
}
|
|
|
|
/**
|
|
* Navigate to a page via Customers dropdown
|
|
*/
|
|
async navigateToCustomerPage(type: 'client' | 'fan') {
|
|
await this.openCustomersDropdown()
|
|
const labelMap = {
|
|
client: 'Clients',
|
|
fan: 'Fans',
|
|
}
|
|
const link = this.nav.getByRole('link', { name: labelMap[type] })
|
|
await link.click()
|
|
}
|
|
|
|
/**
|
|
* Navigate to a page via Platform dropdown
|
|
*/
|
|
async navigateToPlatformPage(pageName: 'apps' | 'values') {
|
|
await this.openPlatformDropdown()
|
|
const labelMap = {
|
|
apps: 'Apps',
|
|
values: 'Values',
|
|
}
|
|
const link = this.nav.getByRole('link', { name: labelMap[pageName] })
|
|
await link.click()
|
|
}
|
|
|
|
/**
|
|
* Navigate to a page via Company dropdown
|
|
*/
|
|
async navigateToCompanyPage(pageName: 'investor' | 'terms' | 'privacy') {
|
|
await this.openCompanyDropdown()
|
|
const labelMap = {
|
|
investor: 'Investors',
|
|
terms: 'Terms of Service',
|
|
privacy: 'Privacy Policy',
|
|
}
|
|
const link = this.nav.getByRole('link', { name: labelMap[pageName] })
|
|
await link.click()
|
|
}
|
|
|
|
/**
|
|
* Assert dropdown is open and contains expected items
|
|
*/
|
|
async assertWorkersDropdownOpen() {
|
|
// Wait for dropdown items to be visible
|
|
await expect(this.nav.getByRole('link', { name: /Providers/i })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: /Performers/i })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: /Fangirls/i })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: /Camgirls/i })).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Assert Customers dropdown is open
|
|
*/
|
|
async assertCustomersDropdownOpen() {
|
|
await expect(this.nav.getByRole('link', { name: 'Clients' })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: 'Fans' })).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Assert Platform dropdown is open
|
|
*/
|
|
async assertPlatformDropdownOpen() {
|
|
await expect(this.nav.getByRole('link', { name: 'Apps' })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: 'Values' })).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Assert Company dropdown is open
|
|
*/
|
|
async assertCompanyDropdownOpen() {
|
|
await expect(this.nav.getByRole('link', { name: 'Investors' })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: 'Terms of Service' })).toBeVisible()
|
|
await expect(this.nav.getByRole('link', { name: 'Privacy Policy' })).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Assert announcement bar is visible
|
|
*/
|
|
async assertAnnouncementBarVisible() {
|
|
await expect(this.announcementBar).toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Assert announcement bar is NOT visible
|
|
*/
|
|
async assertAnnouncementBarNotVisible() {
|
|
await expect(this.announcementBar).not.toBeVisible()
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Click the announcement bar CTA button
|
|
*/
|
|
async clickAnnouncementCta() {
|
|
await this.announcementCta.click()
|
|
}
|
|
|
|
/**
|
|
* Close any open dropdown by pressing Escape
|
|
*/
|
|
async closeDropdown() {
|
|
await this.page.keyboard.press('Escape')
|
|
}
|
|
|
|
/**
|
|
* Close dropdown by clicking elsewhere
|
|
*/
|
|
async closeDropdownByClickAway() {
|
|
await this.page.locator('body').click({ position: { x: 10, y: 10 } })
|
|
}
|
|
}
|