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`; const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://127.0.0.1:3400'; const output = execFileSync('bun', [ '-e', ` const [email, password] = process.argv.slice(1); const { auth } = await import('./lib/auth'); const response = await auth.api.signUpEmail({ body: { name: 'Playwright Research User', email, password, callbackURL: '/' }, asResponse: true }); console.log(JSON.stringify({ status: response.status, sessionCookie: response.headers.get('set-cookie') })); `, email, PASSWORD ], { cwd: process.cwd(), env: { ...process.env, DATABASE_URL: `file:${E2E_DATABASE_PATH}`, BETTER_AUTH_BASE_URL: baseURL, BETTER_AUTH_SECRET: 'playwright-e2e-secret-playwright-e2e-secret', BETTER_AUTH_TRUSTED_ORIGINS: baseURL }, encoding: 'utf8' }); const { status, sessionCookie } = JSON.parse(output) as { status: number; sessionCookie: string | null }; expect(status).toBe(200); expect(sessionCookie).toBeTruthy(); const [cookieNameValue] = sessionCookie!.split(';'); const separatorIndex = cookieNameValue!.indexOf('='); const cookieName = cookieNameValue!.slice(0, separatorIndex); const cookieValue = cookieNameValue!.slice(separatorIndex + 1); await page.context().addCookies([{ name: cookieName, value: cookieValue, url: baseURL, httpOnly: true, sameSite: 'Lax' }]); await page.goto('/'); 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) => { test.slow(); const accessionNumber = `0001045810-26-${String(Date.now()).slice(-6)}`; const uploadFixture = join(process.cwd(), 'e2e', 'fixtures', 'sample-research.txt'); 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: /^Open overview/ }).first().click(); await expect(page).toHaveURL(/\/analysis\?ticker=NVDA/); await expect(page.getByText('Bull vs Bear')).toBeVisible(); await expect(page.getByText('Past 7 Days')).toBeVisible(); await expect(page.getByText('Recent Developments')).toBeVisible(); await page.getByRole('link', { name: 'Research' }).first().click(); await expect(page).toHaveURL(/\/research\?ticker=NVDA/); await page.getByLabel('Research note title').fill('Own-the-stack moat check'); await page.getByLabel('Research note summary').fill('Initial moat checkpoint'); await page.getByLabel('Research note body').fill('Monitor hyperscaler concentration, gross margin durability, and Blackwell shipment cadence.'); await page.getByLabel('Research note tags').fill('moat, thesis'); await page.getByRole('button', { name: 'Save note' }).click(); await expect(page.getByText('Saved note to the research library.')).toBeVisible(); await page.getByLabel('Upload title').fill('Supply-chain diligence'); await page.getByLabel('Upload summary').fill('Vendor and channel-check notes'); await page.getByLabel('Upload tags').fill('diligence, channel-check'); await page.getByLabel('Upload file').setInputFiles(uploadFixture); await page.locator('button', { hasText: 'Upload file' }).click(); await expect(page.getByText('Uploaded research file.')).toBeVisible(); await page.goto(`/filings?ticker=NVDA`); await page.getByRole('link', { name: 'Summary' }).first().click(); await expect(page).toHaveURL(/\/analysis\/reports\/NVDA\//); await page.getByRole('button', { name: 'Save to library' }).click(); await expect(page.getByText('Saved to the company research library.')).toBeVisible(); await page.goto('/research?ticker=NVDA'); await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/\/research\?ticker=NVDA/); await expect(page.getByRole('heading', { name: '10-K AI memo' }).first()).toBeVisible(); await page.getByLabel('Memo rating').selectOption('buy'); await page.getByLabel('Memo conviction').selectOption('high'); await page.getByLabel('Memo time horizon').fill('24'); await page.getByLabel('Packet title').fill('NVIDIA buy-side packet'); await page.getByLabel('Packet subtitle').fill('AI infrastructure compounder'); await page.getByLabel('Memo Thesis').fill('Maintain a constructive stance as datacenter demand and platform depth widen the moat.'); await page.getByLabel('Memo Catalysts').fill('Blackwell ramp, enterprise inference demand, and sustained operating leverage.'); await page.getByLabel('Memo Risks').fill('Customer concentration, competition, and execution on supply.'); await page.getByRole('button', { name: 'Save memo' }).click(); await expect(page.getByText('Saved investment memo.')).toBeVisible(); await page.getByRole('button', { name: 'Attach' }).first().click(); await expect(page.getByText('Attached evidence to Thesis.')).toBeVisible(); await page.goto('/analysis?ticker=NVDA'); await expect(page).toHaveURL(/\/analysis\?ticker=NVDA/); await expect(page.getByText('Bull vs Bear')).toBeVisible(); await expect(page.getByText('Past 7 Days')).toBeVisible(); await expect(page.getByText('Recent Developments')).toBeVisible(); await page.goto('/financials?ticker=NVDA'); await expect(page).toHaveURL(/\/financials\?ticker=NVDA/); await page.goto('/filings?ticker=NVDA'); await expect(page).toHaveURL(/\/filings\?ticker=NVDA/); await expect(page.getByRole('cell', { name: 'NVIDIA Corporation' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Summary' }).first()).toBeVisible(); }); test('supports add, edit, and delete holding flows with summary refresh', async ({ page }, testInfo) => { test.slow(); 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(); });