Files
Neon-Desk/e2e/graphing.spec.ts

256 lines
8.2 KiB
TypeScript

import { expect, test, type Page, type TestInfo } from '@playwright/test';
const PASSWORD = 'Sup3rSecure!123';
function toSlug(value: string) {
return value
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 48);
}
async function signUp(page: Page, testInfo: TestInfo) {
const email = `playwright-graphing-${testInfo.workerIndex}-${toSlug(testInfo.title)}-${Date.now()}@example.com`;
await page.goto('/auth/signup');
await page.locator('input[autocomplete="name"]').fill('Playwright Graphing User');
await page.locator('input[autocomplete="email"]').fill(email);
await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD);
await page.locator('input[autocomplete="new-password"]').nth(1).fill(PASSWORD);
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('heading', { name: 'Command Center' })).toBeVisible({ timeout: 30_000 });
await expect(page).toHaveURL(/\/$/, { timeout: 30_000 });
}
function createFinancialsPayload(input: {
ticker: string;
companyName: string;
cadence: 'annual' | 'quarterly' | 'ltm';
surface: string;
}) {
return {
financials: {
company: {
ticker: input.ticker,
companyName: input.companyName,
cik: null
},
surfaceKind: input.surface,
cadence: input.cadence,
displayModes: ['standardized'],
defaultDisplayMode: 'standardized',
periods: [
{
id: `${input.ticker}-p1`,
filingId: 1,
accessionNumber: `0000-${input.ticker}-1`,
filingDate: '2025-02-01',
periodStart: '2024-01-01',
periodEnd: '2024-12-31',
filingType: '10-K',
periodLabel: 'FY 2024'
},
{
id: `${input.ticker}-p2`,
filingId: 2,
accessionNumber: `0000-${input.ticker}-2`,
filingDate: '2026-02-01',
periodStart: '2025-01-01',
periodEnd: '2025-12-31',
filingType: '10-K',
periodLabel: 'FY 2025'
}
],
statementRows: {
faithful: [],
standardized: [
{
key: 'revenue',
label: 'Revenue',
category: 'revenue',
order: 10,
unit: 'currency',
values: {
[`${input.ticker}-p1`]: input.ticker === 'AAPL' ? 320 : 280,
[`${input.ticker}-p2`]: input.ticker === 'AAPL' ? 360 : 330
},
sourceConcepts: ['revenue'],
sourceRowKeys: ['revenue'],
sourceFactIds: [1],
formulaKey: null,
hasDimensions: false,
resolvedSourceRowKeys: {
[`${input.ticker}-p1`]: 'revenue',
[`${input.ticker}-p2`]: 'revenue'
}
},
{
key: 'total_assets',
label: 'Total Assets',
category: 'asset',
order: 20,
unit: 'currency',
values: {
[`${input.ticker}-p1`]: input.ticker === 'AAPL' ? 410 : 380,
[`${input.ticker}-p2`]: input.ticker === 'AAPL' ? 450 : 420
},
sourceConcepts: ['total_assets'],
sourceRowKeys: ['total_assets'],
sourceFactIds: [2],
formulaKey: null,
hasDimensions: false,
resolvedSourceRowKeys: {
[`${input.ticker}-p1`]: 'total_assets',
[`${input.ticker}-p2`]: 'total_assets'
}
},
{
key: 'free_cash_flow',
label: 'Free Cash Flow',
category: 'cash_flow',
order: 30,
unit: 'currency',
values: {
[`${input.ticker}-p1`]: input.ticker === 'AAPL' ? 95 : 80,
[`${input.ticker}-p2`]: input.ticker === 'AAPL' ? 105 : 92
},
sourceConcepts: ['free_cash_flow'],
sourceRowKeys: ['free_cash_flow'],
sourceFactIds: [3],
formulaKey: null,
hasDimensions: false,
resolvedSourceRowKeys: {
[`${input.ticker}-p1`]: 'free_cash_flow',
[`${input.ticker}-p2`]: 'free_cash_flow'
}
}
]
},
ratioRows: [
{
key: 'gross_margin',
label: 'Gross Margin',
category: 'margins',
order: 10,
unit: 'percent',
values: {
[`${input.ticker}-p1`]: input.ticker === 'AAPL' ? 0.43 : 0.39,
[`${input.ticker}-p2`]: input.ticker === 'AAPL' ? 0.45 : 0.41
},
sourceConcepts: ['gross_margin'],
sourceRowKeys: ['gross_margin'],
sourceFactIds: [4],
formulaKey: 'gross_margin',
hasDimensions: false,
resolvedSourceRowKeys: {
[`${input.ticker}-p1`]: null,
[`${input.ticker}-p2`]: null
},
denominatorKey: 'revenue'
}
],
kpiRows: null,
trendSeries: [],
categories: [],
availability: {
adjusted: false,
customMetrics: false
},
nextCursor: null,
facts: null,
coverage: {
filings: 2,
rows: 3,
dimensions: 0,
facts: 0
},
dataSourceStatus: {
enabled: true,
hydratedFilings: 2,
partialFilings: 0,
failedFilings: 0,
pendingFilings: 0,
queuedSync: false
},
metrics: {
taxonomy: null,
validation: null
},
dimensionBreakdown: null
}
};
}
async function mockGraphingFinancials(page: Page) {
await page.route('**/api/financials/company**', async (route) => {
const url = new URL(route.request().url());
const ticker = url.searchParams.get('ticker') ?? 'MSFT';
const cadence = (url.searchParams.get('cadence') ?? 'annual') as 'annual' | 'quarterly' | 'ltm';
const surface = url.searchParams.get('surface') ?? 'income_statement';
if (ticker === 'BAD') {
await route.fulfill({
status: 404,
contentType: 'application/json',
body: JSON.stringify({ error: 'Ticker not found' })
});
return;
}
const companyName = ticker === 'AAPL'
? 'Apple Inc.'
: ticker === 'NVDA'
? 'NVIDIA Corporation'
: ticker === 'AMD'
? 'Advanced Micro Devices, Inc.'
: 'Microsoft Corporation';
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createFinancialsPayload({
ticker,
companyName,
cadence,
surface
}))
});
});
}
test('supports graphing compare controls and partial failures', async ({ page }, testInfo) => {
await signUp(page, testInfo);
await mockGraphingFinancials(page);
await page.goto('/graphing');
await expect(page).toHaveURL(/tickers=MSFT%2CAAPL%2CNVDA/);
await expect(page.getByRole('heading', { name: 'Graphing' })).toBeVisible();
await expect(page.getByText('Microsoft Corporation')).toBeVisible();
await page.getByRole('button', { name: 'Graph surface Balance Sheet' }).click();
await expect(page).toHaveURL(/surface=balance_sheet/);
await expect(page).toHaveURL(/metric=total_assets/);
await page.getByRole('button', { name: 'Graph cadence Quarterly' }).click();
await expect(page).toHaveURL(/cadence=quarterly/);
await page.getByRole('button', { name: 'Chart type Bar' }).click();
await expect(page).toHaveURL(/chart=bar/);
await page.getByRole('button', { name: 'Remove AAPL' }).click();
await expect(page).not.toHaveURL(/AAPL/);
await page.getByLabel('Compare tickers').fill('MSFT, NVDA, AMD');
await page.getByRole('button', { name: 'Update Compare Set' }).click();
await expect(page).toHaveURL(/tickers=MSFT%2CNVDA%2CAMD/);
await expect(page.getByText('Advanced Micro Devices, Inc.')).toBeVisible();
await page.goto('/graphing?tickers=MSFT,BAD&surface=income_statement&metric=revenue&cadence=annual&chart=line&scale=millions');
await expect(page.getByText('Partial coverage detected.')).toBeVisible();
await expect(page.getByRole('cell', { name: /BAD/ })).toBeVisible();
await expect(page.getByText('Ticker not found')).toBeVisible();
await expect(page.getByText('Microsoft Corporation')).toBeVisible();
});