Files
Neon-Desk/e2e/research-mvp.spec.ts

290 lines
11 KiB
TypeScript

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();
const coverageTable = page.locator('.data-table-wrap:visible');
const nvdaRow = coverageTable.locator('tbody tr').filter({
has: page.getByText('NVIDIA Corporation')
});
await expect(coverageTable).toHaveCount(1);
await expect(nvdaRow).toHaveCount(1);
await page.waitForFunction(() => window.innerWidth >= 1024);
await nvdaRow.getByLabel('NVDA status').selectOption('active');
await expect(nvdaRow.getByLabel('NVDA status')).toHaveValue('active');
await nvdaRow.getByLabel('NVDA priority').selectOption('high');
await expect(nvdaRow.getByLabel('NVDA priority')).toHaveValue('high');
await nvdaRow.locator('a[href="/analysis?ticker=NVDA"]').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.getByRole('heading', { name: 'Recent Developments' })).toBeVisible();
await page.locator('a[href="/research?ticker=NVDA"]').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('button', { name: 'Workspace' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Copilot Focus' })).toBeVisible();
await expect(page.getByText('Research Copilot')).toBeVisible();
await expect(page.getByLabel('Research copilot prompt')).toBeVisible();
await page.getByRole('button', { name: 'Copilot Focus' }).click();
await expect(page.getByText('Copilot Focus')).toBeVisible();
await expect(page.getByRole('button', { name: 'Library' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Memo' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Packet' })).toBeVisible();
await page.getByRole('button', { name: 'Workspace' }).click();
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();
});