Fix filings ticker scope consistency
This commit is contained in:
202
e2e/filings.spec.ts
Normal file
202
e2e/filings.spec.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
const PASSWORD = 'Sup3rSecure!123';
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
type FilingFixture = {
|
||||
id: number;
|
||||
ticker: string;
|
||||
filing_type: '10-K' | '10-Q' | '8-K';
|
||||
filing_date: string;
|
||||
accession_number: string;
|
||||
cik: string;
|
||||
company_name: string;
|
||||
filing_url: string | null;
|
||||
submission_url: string | null;
|
||||
primary_document: string | null;
|
||||
metrics: {
|
||||
revenue: number | null;
|
||||
netIncome: number | null;
|
||||
totalAssets: number | null;
|
||||
cash: number | null;
|
||||
debt: number | null;
|
||||
} | null;
|
||||
analysis: null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
function uniqueEmail(prefix: string) {
|
||||
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@example.com`;
|
||||
}
|
||||
|
||||
function createFiling(input: {
|
||||
id: number;
|
||||
ticker: string;
|
||||
accessionNumber: string;
|
||||
filingType: '10-K' | '10-Q' | '8-K';
|
||||
filingDate: string;
|
||||
companyName: string;
|
||||
revenue?: number | null;
|
||||
}): FilingFixture {
|
||||
return {
|
||||
id: input.id,
|
||||
ticker: input.ticker,
|
||||
filing_type: input.filingType,
|
||||
filing_date: input.filingDate,
|
||||
accession_number: input.accessionNumber,
|
||||
cik: '0001045810',
|
||||
company_name: input.companyName,
|
||||
filing_url: `https://www.sec.gov/Archives/${input.accessionNumber}.htm`,
|
||||
submission_url: `https://www.sec.gov/submissions/${input.accessionNumber}.json`,
|
||||
primary_document: `${input.accessionNumber}.htm`,
|
||||
metrics: input.revenue === undefined
|
||||
? null
|
||||
: {
|
||||
revenue: input.revenue,
|
||||
netIncome: null,
|
||||
totalAssets: null,
|
||||
cash: null,
|
||||
debt: null
|
||||
},
|
||||
analysis: null,
|
||||
created_at: '2026-03-14T12:00:00.000Z',
|
||||
updated_at: '2026-03-14T12:00:00.000Z'
|
||||
};
|
||||
}
|
||||
|
||||
async function signUp(page: Page, email: string) {
|
||||
await page.goto('/auth/signup');
|
||||
await page.locator('input[autocomplete="name"]').fill('Playwright Filings 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 });
|
||||
}
|
||||
|
||||
async function installFilingsRouteStub(
|
||||
page: Page,
|
||||
options?: {
|
||||
unscopedDelayMs?: number;
|
||||
scopedDelayMs?: number;
|
||||
}
|
||||
) {
|
||||
const nvdaFilings = [
|
||||
createFiling({
|
||||
id: 1,
|
||||
ticker: 'NVDA',
|
||||
accessionNumber: '0001045810-26-000001',
|
||||
filingType: '10-K',
|
||||
filingDate: '2026-03-13',
|
||||
companyName: 'NVIDIA Corporation',
|
||||
revenue: 130_500_000_000
|
||||
})
|
||||
];
|
||||
const msftFilings = [
|
||||
createFiling({
|
||||
id: 2,
|
||||
ticker: 'MSFT',
|
||||
accessionNumber: '0000789019-26-000002',
|
||||
filingType: '10-Q',
|
||||
filingDate: '2026-03-12',
|
||||
companyName: 'Microsoft Corporation',
|
||||
revenue: 71_000_000_000
|
||||
})
|
||||
];
|
||||
const mixedFilings = [...nvdaFilings, ...msftFilings];
|
||||
|
||||
await page.route(/\/api\/filings(\?.*)?$/, async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
const ticker = url.searchParams.get('ticker')?.trim().toUpperCase() ?? null;
|
||||
const delay = ticker === 'NVDA'
|
||||
? options?.scopedDelayMs ?? 0
|
||||
: options?.unscopedDelayMs ?? 0;
|
||||
|
||||
if (delay > 0) {
|
||||
await page.waitForTimeout(delay);
|
||||
}
|
||||
|
||||
const filings = ticker === 'NVDA' ? nvdaFilings : mixedFilings;
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ filings })
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function filingsLedger(page: Page) {
|
||||
return page.locator('section').filter({
|
||||
has: page.getByRole('heading', { name: 'Filing Ledger' })
|
||||
}).first();
|
||||
}
|
||||
|
||||
test('direct URL entry keeps the filings ledger scoped to the URL ticker', async ({ page }) => {
|
||||
await signUp(page, uniqueEmail('playwright-filings-direct'));
|
||||
await installFilingsRouteStub(page);
|
||||
|
||||
await page.goto('/filings?ticker=NVDA', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const ledger = await filingsLedger(page);
|
||||
|
||||
await expect(page).toHaveURL(/\/filings\?ticker=NVDA$/);
|
||||
await expect(ledger.getByText('1 records loaded for NVDA. Values shown in Millions (M).')).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'Microsoft Corporation' })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('apply and clear keep the URL and visible filings rows aligned', async ({ page }) => {
|
||||
await signUp(page, uniqueEmail('playwright-filings-apply-clear'));
|
||||
await installFilingsRouteStub(page);
|
||||
|
||||
await page.goto('/filings', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const ledger = await filingsLedger(page);
|
||||
|
||||
await expect(page).toHaveURL(/\/filings$/);
|
||||
await expect(ledger.getByText('2 records loaded. Values shown in Millions (M).')).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'Microsoft Corporation' })).toBeVisible();
|
||||
|
||||
await page.getByPlaceholder('Ticker filter').fill('nvda');
|
||||
await page.getByRole('button', { name: 'Apply' }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/filings\?ticker=NVDA$/);
|
||||
await expect(ledger.getByText('1 records loaded for NVDA. Values shown in Millions (M).')).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'Microsoft Corporation' })).toHaveCount(0);
|
||||
|
||||
await page.getByRole('button', { name: 'Clear' }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/filings$/);
|
||||
await expect(ledger.getByText('2 records loaded. Values shown in Millions (M).')).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'Microsoft Corporation' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('a stale global filings response cannot overwrite a newer scoped ledger', async ({ page }) => {
|
||||
await signUp(page, uniqueEmail('playwright-filings-stale'));
|
||||
await installFilingsRouteStub(page, {
|
||||
unscopedDelayMs: 900,
|
||||
scopedDelayMs: 50
|
||||
});
|
||||
|
||||
await page.goto('/filings', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const ledger = await filingsLedger(page);
|
||||
|
||||
await page.getByPlaceholder('Ticker filter').fill('NVDA');
|
||||
await page.getByRole('button', { name: 'Apply' }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/filings\?ticker=NVDA$/);
|
||||
await expect(ledger.getByText('1 records loaded for NVDA. Values shown in Millions (M).')).toBeVisible();
|
||||
|
||||
await page.waitForTimeout(1_100);
|
||||
|
||||
await expect(ledger.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible();
|
||||
await expect(ledger.getByRole('cell', { name: 'Microsoft Corporation' })).toHaveCount(0);
|
||||
});
|
||||
Reference in New Issue
Block a user