Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_BASE_URL=http://localhost:3000
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ yarn-error.log*
/.vscode
*.log

.cache
.cache

e2e/test-results/
e2e/.playwright/
17 changes: 17 additions & 0 deletions e2e/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { chromium, FullConfig } from '@playwright/test'

async function globalSetup(config: FullConfig) {
const browser = await chromium.launch()
const page = await browser.newPage()

try {
await page.goto(config.projects[0].use.baseURL)
await page.waitForLoadState('domcontentloaded')
} catch (error) {
console.warn('Failed to pre-warm application:', error)
}

await browser.close()
}

export default globalSetup
119 changes: 119 additions & 0 deletions e2e/helpers/page-objects/homepage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Page, expect } from '@playwright/test'
import { SELECTORS } from '../selectors'
import { UIInteractions } from '../ui-interactions'

export class HomePage {
private ui: UIInteractions

constructor(private page: Page) {
this.ui = new UIInteractions(page)
}

// Navigation methods
async visit() {
await this.ui.navigateToHomepage()
await this.dismissOnboardingIfPresent()
}

async dismissOnboardingIfPresent() {
await this.ui.dismissOnboardingIfPresent()
}

// Element getters
get totalTVLElement() {
return this.page.locator(SELECTORS.HOMEPAGE.TOTAL_TVL)
}

get protocolsTable() {
return this.page.locator(SELECTORS.HOMEPAGE.PROTOCOLS_TABLE)
}

get protocolRows() {
return this.page.locator(SELECTORS.HOMEPAGE.PROTOCOL_ROW)
}

get chartContainer() {
return this.page.locator(SELECTORS.HOMEPAGE.CHART_CONTAINER)
}

get searchInput() {
return this.page.locator(SELECTORS.SEARCH.INPUT)
}

get chainLinks() {
return this.page.locator(SELECTORS.CHAIN.LINK)
}

// Action methods
async getTotalTVL() {
await expect(this.totalTVLElement).toBeVisible()
const text = await this.totalTVLElement.textContent()
return text?.trim() || ''
}

async selectChain(chainName: string) {
await this.chainLinks.filter({ hasText: chainName }).first().click()
await this.ui.waitForPageToLoad()
}

async searchForProtocol(protocolName: string) {
await this.searchInput.click()
await this.searchInput.fill(protocolName)

// Wait for search results
await this.page.waitForSelector(SELECTORS.SEARCH.RESULTS, { timeout: 10000 })

// Click on first result
await this.page.locator(SELECTORS.SEARCH.RESULT_ITEM).first().click()
}

async getProtocolCount() {
await expect(this.protocolsTable).toBeVisible()
const rows = await this.protocolRows.count()
return rows
}

async clickProtocolByName(protocolName: string) {
await this.protocolRows.filter({ hasText: protocolName }).first().click()
}

// Assertion methods
async expectPageToLoad() {
await expect(this.page).toHaveTitle(/DefiLlama - DeFi Dashboard/)
await expect(this.totalTVLElement).toBeVisible({ timeout: 30000 })
await expect(this.protocolsTable).toBeVisible({ timeout: 30000 })
}

async expectTVLToBeDisplayed() {
await expect(this.totalTVLElement).toBeVisible()
const tvlText = await this.totalTVLElement.textContent()
expect(tvlText).toMatch(/\$[\d,]+\.?\d*[KMB]?/i)
}

async expectProtocolsTableToBeVisible() {
await expect(this.protocolsTable).toBeVisible()
await expect(this.protocolRows.first()).toBeVisible()
}

async expectChartToBeVisible() {
await expect(this.chartContainer).toBeVisible({ timeout: 15000 })
}

async expectSearchToBeWorking() {
await expect(this.searchInput).toBeVisible()
await expect(this.searchInput).toBeEnabled()
}

async expectChainFilterToBeVisible() {
await expect(this.chainLinks.first()).toBeVisible()
}

async expectProtocolToBeInTable(protocolName: string) {
await expect(this.protocolRows.filter({ hasText: protocolName })).toBeVisible()
}

async expectMinimumProtocolCount(minCount: number) {
const actualCount = await this.getProtocolCount()
expect(actualCount).toBeGreaterThanOrEqual(minCount)
}
}
150 changes: 150 additions & 0 deletions e2e/helpers/page-objects/protocol-detail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Page, expect } from '@playwright/test'
import { SELECTORS } from '../selectors'
import { UIInteractions } from '../ui-interactions'

export class ProtocolDetailPage {
private ui: UIInteractions

constructor(private page: Page) {
this.ui = new UIInteractions(page)
}

// Navigation methods
async visit(protocolName: string) {
await this.ui.navigateToProtocol(protocolName)
}

// Element getters
get protocolTitle() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.TITLE)
}

get protocolTVL() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.TVL_VALUE)
}

get protocolChart() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.CHART)
}

get protocolDescription() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.DESCRIPTION)
}

get protocolWebsite() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.WEBSITE_LINK)
}

get protocolAudit() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.AUDIT_LINK)
}

get protocolCategory() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.CATEGORY)
}

get protocolChains() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.CHAINS)
}

get protocolHeader() {
return this.page.locator(SELECTORS.PROTOCOL_DETAIL.HEADER)
}

// Action methods
async getProtocolName() {
await expect(this.protocolTitle).toBeVisible()
const text = await this.protocolTitle.textContent()
return text?.trim() || ''
}

async getProtocolTVL() {
await expect(this.protocolTVL).toBeVisible()
const text = await this.protocolTVL.textContent()
return text?.trim() || ''
}

async getProtocolCategory() {
await expect(this.protocolCategory).toBeVisible()
const text = await this.protocolCategory.textContent()
return text?.trim() || ''
}

async clickWebsiteLink() {
await expect(this.protocolWebsite).toBeVisible()
await this.protocolWebsite.click()
}

async clickAuditLink() {
await expect(this.protocolAudit).toBeVisible()
await this.protocolAudit.click()
}

async waitForChartToLoad() {
await expect(this.protocolChart).toBeVisible({ timeout: 15000 })
}

// Assertion methods
async expectPageToLoad(expectedProtocolName?: string) {
await expect(this.protocolHeader).toBeVisible({ timeout: 30000 })
await expect(this.protocolTitle).toBeVisible({ timeout: 30000 })
await expect(this.protocolTVL).toBeVisible({ timeout: 30000 })

if (expectedProtocolName) {
const actualName = await this.getProtocolName()
expect(actualName.toLowerCase()).toContain(expectedProtocolName.toLowerCase())
}
}

async expectTVLToBeDisplayed() {
await expect(this.protocolTVL).toBeVisible()
const tvlText = await this.protocolTVL.textContent()
expect(tvlText).toMatch(/\$[\d,]+\.?\d*[KMB]?/i)
}

async expectChartToBeVisible() {
await expect(this.protocolChart).toBeVisible({ timeout: 15000 })
}

async expectProtocolInfoToBeVisible() {
await expect(this.protocolTitle).toBeVisible()
await expect(this.protocolTVL).toBeVisible()
await expect(this.protocolCategory).toBeVisible()
}

async expectWebsiteLinkToBeVisible() {
await expect(this.protocolWebsite).toBeVisible()
}

async expectAuditLinkToBeVisible() {
await expect(this.protocolAudit).toBeVisible()
}

async expectChainsToBeVisible() {
await expect(this.protocolChains).toBeVisible()
}

async expectDescriptionToBeVisible() {
await expect(this.protocolDescription).toBeVisible()
}

async expectProtocolNameToBe(expectedName: string) {
const actualName = await this.getProtocolName()
expect(actualName.toLowerCase()).toContain(expectedName.toLowerCase())
}

async expectCategoryToBe(expectedCategory: string) {
const actualCategory = await this.getProtocolCategory()
expect(actualCategory.toLowerCase()).toContain(expectedCategory.toLowerCase())
}

async expectTVLToBeNumeric() {
const tvlText = await this.getProtocolTVL()
expect(tvlText).toMatch(/\$[\d,]+\.?\d*/i)
}

async expectHistoricalChartToBeVisible() {
await this.waitForChartToLoad()
await expect(this.protocolChart).toBeVisible()
}
}
Loading