142 lines
5.2 KiB
TypeScript
142 lines
5.2 KiB
TypeScript
import { Given, When, Then } from '@cucumber/cucumber'
|
|
import { expect } from '@playwright/test'
|
|
import { BrowserWorld } from '../../support/browser-world.js'
|
|
import { browserConfig } from '../../support/browser-config.js'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Given
|
|
// ---------------------------------------------------------------------------
|
|
|
|
Given('I am on the login page', async function (this: BrowserWorld) {
|
|
await this.navigateTo('/login')
|
|
})
|
|
|
|
Given('I am logged in as {string}', async function (this: BrowserWorld, email: string) {
|
|
await this.login(email)
|
|
})
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// When — navigation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
When('I navigate to {string}', async function (this: BrowserWorld, path: string) {
|
|
await this.navigateTo(path)
|
|
})
|
|
|
|
When('I click {string}', async function (this: BrowserWorld, name: string) {
|
|
const page = this.getPage()
|
|
// Try button first, then link
|
|
const button = page.getByRole('button', { name })
|
|
if (await button.isVisible().catch(() => false)) {
|
|
await button.click()
|
|
} else {
|
|
await page.getByRole('link', { name }).click()
|
|
}
|
|
await page.waitForLoadState('networkidle').catch(() => {})
|
|
})
|
|
|
|
When('I click {string} in the sidebar', async function (this: BrowserWorld, name: string) {
|
|
const page = this.getPage()
|
|
// Mantine NavLink renders as an <a> WITHOUT href (navigation is via onClick), so
|
|
// it has no implicit "link" role — target the label text inside the navbar.
|
|
// Some labels are duplicated (e.g. "My Stays" maps to both /my/stays and /stays
|
|
// due to the nav.stays == nav.my_stays i18n collision); first() picks the
|
|
// personal-section entry, which appears first in DOM order.
|
|
const navItem = page
|
|
.locator('.mantine-AppShell-navbar')
|
|
.getByText(name, { exact: true })
|
|
.first()
|
|
await navItem.scrollIntoViewIfNeeded()
|
|
await navItem.click()
|
|
await page.waitForLoadState('networkidle').catch(() => {})
|
|
})
|
|
|
|
When('I click tab {string}', async function (this: BrowserWorld, name: string) {
|
|
const page = this.getPage()
|
|
await page.getByRole('tab', { name }).click()
|
|
await page.waitForLoadState('networkidle').catch(() => {})
|
|
})
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// When — form interactions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
When(
|
|
'I fill in {string} with {string}',
|
|
async function (this: BrowserWorld, label: string, value: string) {
|
|
await this.fillField(label, value)
|
|
}
|
|
)
|
|
|
|
When(
|
|
'I select {string} from {string}',
|
|
async function (this: BrowserWorld, value: string, label: string) {
|
|
await this.selectOption(label, value)
|
|
}
|
|
)
|
|
|
|
When(
|
|
'I pick date {string} for {string}',
|
|
async function (this: BrowserWorld, day: string, label: string) {
|
|
const page = this.getPage()
|
|
// Close any open calendar/dropdown first
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
// Mantine DatePickerInput: the label text and button are wrapped in the same container
|
|
// Use a CSS selector to find the right DatePickerInput by its label
|
|
const wrapper = page.locator(`text="${label}" >> xpath=ancestor::div[contains(@class, "mantine-DatePickerInput-root") or contains(@class, "mantine-InputWrapper-root")][1]`)
|
|
const btn = wrapper.locator('button').first()
|
|
if (await btn.count() > 0) {
|
|
await btn.click({ timeout: 10_000 })
|
|
} else {
|
|
// Fallback: just try getByLabel
|
|
await page.getByLabel(label).click({ timeout: 10_000 })
|
|
}
|
|
// Wait for calendar popover to appear
|
|
await page.waitForTimeout(500)
|
|
// Click the day number — try table buttons with matching text
|
|
await page.locator('table button').filter({ hasText: new RegExp(`^${day}$`) }).first().click()
|
|
// Close the calendar popover
|
|
await page.waitForTimeout(300)
|
|
}
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Then — assertions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
Then('I should see {string}', async function (this: BrowserWorld, text: string) {
|
|
await this.expectText(text)
|
|
})
|
|
|
|
Then('I should not see {string}', async function (this: BrowserWorld, text: string) {
|
|
await this.expectNoText(text)
|
|
})
|
|
|
|
Then('I should see heading {string}', async function (this: BrowserWorld, text: string) {
|
|
await this.expectHeading(text)
|
|
})
|
|
|
|
Then('I should be on {string}', async function (this: BrowserWorld, path: string) {
|
|
const page = this.getPage()
|
|
await page.waitForURL((url) => url.pathname.includes(path), { timeout: 10_000 })
|
|
expect(page.url()).toContain(path)
|
|
})
|
|
|
|
Then(
|
|
'the sidebar should show {string}',
|
|
async function (this: BrowserWorld, text: string) {
|
|
const page = this.getPage()
|
|
const sidebar = page.locator('.mantine-AppShell-navbar')
|
|
await expect(sidebar.getByText(text)).toBeVisible({ timeout: 10_000 })
|
|
}
|
|
)
|
|
|
|
Then(
|
|
'the {string} field should contain {string}',
|
|
async function (this: BrowserWorld, label: string, value: string) {
|
|
const page = this.getPage()
|
|
await expect(page.getByLabel(label)).toHaveValue(value, { timeout: 5_000 })
|
|
}
|
|
)
|