102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
|
|
import { test, expect } from '@playwright/test';
|
|||
|
|
|
|||
|
|
import { login, apiHeaders, navigateTo, PORT_SLUG } from '../smoke/helpers';
|
|||
|
|
import { IPHONE_DEVICES } from '../fixtures/devices';
|
|||
|
|
import { seedFullyLoadedDeal, changeStage, readStage } from '../fixtures/sales-journey';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Mobile parity for the core sales journey.
|
|||
|
|
*
|
|||
|
|
* Re-runs the deterministic pipeline walk at a phone viewport
|
|||
|
|
* (iPhone 15/16, 393×852 — the "common" anchor in
|
|||
|
|
* `tests/e2e/fixtures/devices.ts`) to confirm the stage transitions and
|
|||
|
|
* the interest-detail surface behave identically on mobile. Uses the
|
|||
|
|
* existing device descriptor + `test.use({ viewport, … })` rather than
|
|||
|
|
* introducing a new Playwright project, since the exhaustive project
|
|||
|
|
* already runs the rest of the journey on desktop.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const phone = IPHONE_DEVICES.iphone16;
|
|||
|
|
|
|||
|
|
test.use({
|
|||
|
|
viewport: phone.viewport,
|
|||
|
|
deviceScaleFactor: phone.deviceScaleFactor,
|
|||
|
|
isMobile: phone.isMobile,
|
|||
|
|
hasTouch: phone.hasTouch,
|
|||
|
|
userAgent: phone.userAgent,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test.describe(`exhaustive: sales journey on ${phone.name}`, () => {
|
|||
|
|
test.beforeEach(async ({ page }) => {
|
|||
|
|
await login(page, 'super_admin');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('pipeline walk reaches contract on a mobile viewport', async ({ page }) => {
|
|||
|
|
const headers = await apiHeaders(page);
|
|||
|
|
const deal = await seedFullyLoadedDeal(page, 'Mobile Journey');
|
|||
|
|
|
|||
|
|
expect(await readStage(page.request, headers, deal.interestId)).toBe('enquiry');
|
|||
|
|
|
|||
|
|
for (const stage of [
|
|||
|
|
'qualified',
|
|||
|
|
'nurturing',
|
|||
|
|
'eoi',
|
|||
|
|
'reservation',
|
|||
|
|
'deposit_paid',
|
|||
|
|
'contract',
|
|||
|
|
] as const) {
|
|||
|
|
await changeStage(page.request, headers, deal.interestId, stage);
|
|||
|
|
expect(
|
|||
|
|
await readStage(page.request, headers, deal.interestId),
|
|||
|
|
`mobile: interest should be at ${stage}`,
|
|||
|
|
).toBe(stage);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('interest detail renders its stage on a mobile viewport', async ({ page }) => {
|
|||
|
|
const headers = await apiHeaders(page);
|
|||
|
|
const deal = await seedFullyLoadedDeal(page, 'Mobile Detail');
|
|||
|
|
await changeStage(page.request, headers, deal.interestId, 'qualified');
|
|||
|
|
|
|||
|
|
await navigateTo(page, `/interests/${deal.interestId}`);
|
|||
|
|
await page.waitForURL(new RegExp(`/${PORT_SLUG}/interests/${deal.interestId}`), {
|
|||
|
|
timeout: 10_000,
|
|||
|
|
});
|
|||
|
|
await page.waitForLoadState('networkidle');
|
|||
|
|
|
|||
|
|
// The viewport is genuinely mobile-width.
|
|||
|
|
expect(page.viewportSize()?.width).toBe(phone.viewport.width);
|
|||
|
|
|
|||
|
|
// The stage label is present on the mobile-rendered detail page.
|
|||
|
|
await expect(page.getByText(/qualified/i).first()).toBeVisible({ timeout: 10_000 });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('interests list is reachable and renders on a mobile viewport', async ({ page }) => {
|
|||
|
|
await navigateTo(page, '/interests');
|
|||
|
|
await page.waitForLoadState('networkidle');
|
|||
|
|
|
|||
|
|
// The page heading is present regardless of whether the mobile layout
|
|||
|
|
// shows a table or a card/board view.
|
|||
|
|
await expect(page.getByText(/interests/i).first()).toBeVisible({ timeout: 15_000 });
|
|||
|
|
|
|||
|
|
await expect
|
|||
|
|
.poll(
|
|||
|
|
async () => {
|
|||
|
|
const table = await page
|
|||
|
|
.locator('table')
|
|||
|
|
.first()
|
|||
|
|
.isVisible()
|
|||
|
|
.catch(() => false);
|
|||
|
|
const cards = await page
|
|||
|
|
.locator('[data-testid="pipeline-board"], [class*="board"], [class*="card"]')
|
|||
|
|
.first()
|
|||
|
|
.isVisible()
|
|||
|
|
.catch(() => false);
|
|||
|
|
return table || cards;
|
|||
|
|
},
|
|||
|
|
{ timeout: 15_000 },
|
|||
|
|
)
|
|||
|
|
.toBe(true);
|
|||
|
|
});
|
|||
|
|
});
|