import { expect, test, type Page, type TestInfo } from '@playwright/test'; import { execFileSync } from 'node:child_process'; import { join } from 'node:path'; const PASSWORD = 'Sup3rSecure!123'; const E2E_DATABASE_PATH = join(process.cwd(), 'data', 'e2e.sqlite'); test.describe.configure({ mode: 'serial' }); 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-research-${testInfo.workerIndex}-${toSlug(testInfo.title)}-${Date.now()}@example.com`; await page.goto('/auth/signup'); await page.locator('input[autocomplete="name"]').fill('Playwright Research 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).toHaveURL(/\/$/); return email; } function seedFiling(input: { ticker: string; companyName: string; accessionNumber: string; filingType: '10-K' | '10-Q'; filingDate: string; summary: string; }) { const now = new Date().toISOString(); execFileSync('python3', [ '-c', ` import json import sqlite3 import sys db_path, ticker, filing_type, filing_date, accession, company_name, now, summary = sys.argv[1:] connection = sqlite3.connect(db_path) try: connection.execute( """ INSERT INTO filing ( ticker, filing_type, filing_date, accession_number, cik, company_name, filing_url, submission_url, primary_document, metrics, analysis, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( ticker, filing_type, filing_date, accession, "0001045810", company_name, f"https://www.sec.gov/Archives/{accession}.htm", f"https://www.sec.gov/submissions/{accession}.json", f"{accession}.htm", json.dumps({ "revenue": 61000000000, "netIncome": 29000000000, "totalAssets": 98000000000, "cash": 27000000000, "debt": 11000000000, }), json.dumps({ "provider": "playwright", "model": "fixture", "text": summary, }), now, now, ), ) connection.commit() finally: connection.close() `, E2E_DATABASE_PATH, input.ticker, input.filingType, input.filingDate, input.accessionNumber, input.companyName, now, input.summary ]); } test('supports the core coverage-to-research workflow', async ({ page }, testInfo) => { const accessionNumber = `0001045810-26-${String(Date.now()).slice(-6)}`; await signUp(page, testInfo); seedFiling({ ticker: 'NVDA', companyName: 'NVIDIA Corporation', accessionNumber, filingType: '10-K', filingDate: '2026-02-18', summary: 'AI datacenter demand remained the central upside driver with expanding operating leverage.' }); await page.goto('/watchlist'); await page.getByLabel('Coverage ticker').fill('NVDA'); await page.getByLabel('Coverage company name').fill('NVIDIA Corporation'); await page.getByLabel('Coverage sector').fill('Technology'); await page.getByLabel('Coverage category').fill('Core'); await page.getByLabel('Coverage tags').fill('AI, semis'); await page.getByRole('button', { name: 'Save coverage' }).click(); await expect(page.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible(); await page.getByLabel('NVDA status').selectOption('active'); await expect(page.getByLabel('NVDA status')).toHaveValue('active'); await page.getByLabel('NVDA priority').selectOption('high'); await expect(page.getByLabel('NVDA priority')).toHaveValue('high'); await page.getByRole('link', { name: /^Analyze/ }).first().click(); await expect(page).toHaveURL(/\/analysis\?ticker=NVDA/); await expect(page.getByText('Coverage Workflow')).toBeVisible(); await page.getByLabel('Journal title').fill('Own-the-stack moat check'); await page.getByLabel('Journal body').fill('Monitor hyperscaler concentration, gross margin durability, and Blackwell shipment cadence.'); await page.getByRole('button', { name: 'Save note' }).click(); await expect(page.getByText('Own-the-stack moat check')).toBeVisible(); await page.getByRole('link', { name: 'Open summary' }).first().click(); await expect(page).toHaveURL(/\/analysis\/reports\/NVDA\//); await page.getByRole('button', { name: 'Add to journal' }).click(); await expect(page.getByText('Saved to the company research journal.')).toBeVisible(); await page.getByRole('link', { name: 'Back to analysis' }).click(); await expect(page).toHaveURL(/\/analysis\?ticker=NVDA/); await expect(page.getByText('10-K AI memo')).toBeVisible(); await page.getByRole('link', { name: 'Open financials' }).click(); await expect(page).toHaveURL(/\/financials\?ticker=NVDA/); await page.getByRole('link', { name: 'Filings' }).first().click(); await expect(page).toHaveURL(/\/filings\?ticker=NVDA/); await expect(page.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible(); await expect(page.getByRole('button', { name: /journal/i }).first()).toBeVisible(); }); test('supports add, edit, and delete holding flows with summary refresh', async ({ page }, testInfo) => { await signUp(page, testInfo); await page.goto('/portfolio'); await page.getByLabel('Holding ticker').fill('MSFT'); await page.getByLabel('Holding company name').fill('Microsoft Corporation'); await page.getByLabel('Holding shares').fill('10'); await page.getByLabel('Holding average cost').fill('100'); await page.getByLabel('Holding current price').fill('110'); await page.getByRole('button', { name: 'Save holding' }).click(); await expect(page.getByText('Microsoft Corporation')).toBeVisible(); await expect(page.getByRole('cell', { name: '$1,100.00' })).toBeVisible(); await page.getByRole('button', { name: /^Edit$/ }).first().click(); await page.getByLabel('Holding company name').fill('Microsoft Corp.'); await page.getByLabel('Holding current price').fill('120'); await page.getByRole('button', { name: 'Update holding' }).click(); await expect(page.getByText('Microsoft Corp.')).toBeVisible(); await expect(page.getByRole('cell', { name: '$1,200.00' })).toBeVisible(); await page.getByRole('button', { name: /^Delete$/ }).first().click(); await expect(page.getByText('No holdings added yet.')).toBeVisible(); });