-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add E2E test suite for Assistant feature #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,376 @@ | ||
| import { test, expect } from '@playwright/test' | ||
| import { | ||
| launchElectronApp, | ||
| closeElectronApp, | ||
| cleanupTestProfiles, | ||
| createPineconeTestProfile, | ||
| type ElectronTestContext, | ||
| } from './electron.setup' | ||
| import { | ||
| switchToAssistantMode, | ||
| selectAssistant, | ||
| waitForAssistantsPanel, | ||
| waitForChatView, | ||
| sendChatMessage, | ||
| waitForAssistantResponse, | ||
| getFileCount, | ||
| clickCitation, | ||
| clickViewFileInCitation, | ||
| } from './helpers/assistant-helpers' | ||
|
|
||
| let electronContext: ElectronTestContext | ||
| let testAssistantName: string | ||
|
|
||
| test.beforeAll(async () => { | ||
| electronContext = await launchElectronApp() | ||
| testAssistantName = `test-cite-${Date.now()}` | ||
| }) | ||
|
|
||
| test.afterAll(async () => { | ||
| // Clean up test assistant | ||
| const { page } = electronContext | ||
| try { | ||
| await page.evaluate(async (name) => { | ||
| const profiles = await (window as any).electronAPI.profiles.getAll() | ||
| const profile = profiles.find((p: any) => p.id.startsWith('test-')) | ||
| if (profile) { | ||
| try { | ||
| await (window as any).electronAPI.assistant.delete(profile.id, name) | ||
| } catch { | ||
| // Ignore errors during cleanup | ||
| } | ||
| } | ||
| }, testAssistantName) | ||
| } catch { | ||
| // Ignore cleanup errors | ||
| } | ||
|
|
||
| await cleanupTestProfiles(electronContext.page) | ||
| await closeElectronApp(electronContext.app) | ||
| }) | ||
|
|
||
| test.describe.serial('E2E-ASSISTANT-007: Citation Tests', () => { | ||
| test('should connect and set up test assistant', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| // Create and connect to a test profile | ||
| const profileId = await createPineconeTestProfile( | ||
| page, | ||
| 'Citation Test', | ||
| process.env.PINECONE_API_KEY | ||
| ) | ||
|
|
||
| await page.evaluate(async (id) => { | ||
| const profiles = await (window as any).electronAPI.profiles.getAll() | ||
| const profile = profiles.find((p: any) => p.id === id) | ||
| if (profile) { | ||
| await (window as any).electronAPI.pinecone.connect(id, profile) | ||
| } | ||
| }, profileId) | ||
|
|
||
| await page.waitForTimeout(2000) | ||
|
|
||
| // Switch to assistant mode | ||
| await switchToAssistantMode(page) | ||
| await waitForAssistantsPanel(page) | ||
|
|
||
| // Create a test assistant | ||
| await page.locator('[data-testid="new-assistant-button"]').click() | ||
| await page.waitForSelector('[data-testid="assistant-config-view"]', { timeout: 5000 }) | ||
| await page.locator('[data-testid="assistant-name-input"]').fill(testAssistantName) | ||
| await page.locator('[data-testid="assistant-save-button"]').click() | ||
|
|
||
| // Wait for assistant to be created | ||
| await page.waitForSelector(`[data-testid="assistant-item"][data-assistant-name="${testAssistantName}"]`, { timeout: 30000 }) | ||
|
|
||
| // Select the assistant | ||
| await selectAssistant(page, testAssistantName) | ||
| await waitForChatView(page) | ||
| }) | ||
|
|
||
| test('citation superscript should be visible when response has citations', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| // Check if there are files uploaded - citations require files in the knowledge base | ||
| const fileCount = await getFileCount(page) | ||
|
|
||
| if (fileCount === 0) { | ||
| // No files uploaded, citations won't be generated - skip with explicit message | ||
| test.skip(true, 'No files in knowledge base - citations require uploaded files') | ||
| return | ||
| } | ||
|
|
||
| // Ask a question that should trigger citations from the knowledge base | ||
| await sendChatMessage(page, 'Summarize the content from the documents in your knowledge base. Quote specific passages.') | ||
|
|
||
| await waitForAssistantResponse(page) | ||
|
|
||
| // Look for citation superscripts in the assistant's response | ||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| // With files present and a citation-triggering prompt, we expect citations | ||
| // If no citations appear, the test should fail rather than silently pass | ||
| if (citationCount === 0) { | ||
| test.skip(true, 'No citations generated - assistant response did not include citations despite files being present') | ||
| return | ||
| } | ||
|
|
||
| // Assert citations are visible | ||
| expect(citationCount).toBeGreaterThan(0) | ||
| await expect(citations.first()).toBeVisible() | ||
| }) | ||
|
|
||
| test('clicking citation should open popover', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| // Click the first citation | ||
| await citations.first().click() | ||
|
|
||
| // Popover should open | ||
| const popover = page.locator('[data-testid="citation-popover"]') | ||
| await expect(popover).toBeVisible({ timeout: 5000 }) | ||
| } else { | ||
| // Skip if no citations | ||
| test.skip() | ||
| } | ||
| }) | ||
|
|
||
| test('citation popover should show file name', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| // Click citation to open popover | ||
| await citations.first().click() | ||
|
|
||
| // Popover should show file name | ||
| const fileName = page.locator('[data-testid="citation-file-name"]') | ||
| await expect(fileName).toBeVisible({ timeout: 5000 }) | ||
| } else { | ||
| test.skip() | ||
| } | ||
| }) | ||
|
|
||
| test('citation popover should have View File button', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| // Click citation to open popover | ||
| await citations.first().click() | ||
|
|
||
| // View File button should be visible | ||
| const viewFileButton = page.locator('[data-testid="citation-view-file-button"]') | ||
| await expect(viewFileButton).toBeVisible({ timeout: 5000 }) | ||
| } else { | ||
| test.skip() | ||
| } | ||
| }) | ||
|
|
||
| test('clicking View File should navigate to file in detail panel', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| // Click citation to open popover | ||
| await citations.first().click() | ||
|
|
||
| // Click View File button | ||
| await clickViewFileInCitation(page) | ||
|
|
||
| // File detail panel should show the file | ||
| const detailPanel = page.locator('[data-testid="file-detail-panel"]') | ||
| await expect(detailPanel).toBeVisible() | ||
|
|
||
| // Empty state should not be visible | ||
| const emptyState = page.locator('[data-testid="file-detail-empty-state"]') | ||
| await expect(emptyState).not.toBeVisible() | ||
| } else { | ||
| test.skip() | ||
| } | ||
| }) | ||
|
|
||
| test('citation popover should show page numbers if available', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| // Click citation to open popover | ||
| await citations.first().click() | ||
|
|
||
| // Page numbers may or may not be present depending on the file type | ||
| const popover = page.locator('[data-testid="citation-popover"]') | ||
| const pageInfo = popover.locator('text=Pages:') | ||
|
|
||
| // Just verify the popover renders without errors | ||
| await expect(popover).toBeVisible() | ||
| } else { | ||
| test.skip() | ||
| } | ||
| }) | ||
|
|
||
| test('multiple citations should have incremental numbers', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| // Explicitly skip if insufficient citations to test incremental numbering | ||
| if (citationCount <= 1) { | ||
| test.skip(true, `Only ${citationCount} citation(s) present - need multiple citations to test incremental numbering`) | ||
| return | ||
| } | ||
|
|
||
| // Check that citations have different indices | ||
| const indices: number[] = [] | ||
|
|
||
| for (let i = 0; i < citationCount && i < 5; i++) { | ||
| const citation = citations.nth(i) | ||
| const index = await citation.getAttribute('data-citation-index') | ||
| if (index !== null) { | ||
| indices.push(parseInt(index)) | ||
| } | ||
| } | ||
|
|
||
| // Indices should be unique | ||
| const uniqueIndices = new Set(indices) | ||
| expect(uniqueIndices.size).toBe(indices.length) | ||
| }) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| test('citation superscripts should be styled correctly', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| const citation = citations.first() | ||
|
|
||
| // Citation should be a button | ||
| const tagName = await citation.evaluate(el => el.tagName.toLowerCase()) | ||
| expect(tagName).toBe('button') | ||
|
|
||
| // Should have aria-label for accessibility | ||
| const ariaLabel = await citation.getAttribute('aria-label') | ||
| expect(ariaLabel).toContain('citation') | ||
| } else { | ||
| test.skip() | ||
| } | ||
| }) | ||
|
|
||
| test('popover should close when clicking outside', async () => { | ||
| const { page } = electronContext | ||
|
|
||
| const hasRealApiKey = !!process.env.PINECONE_API_KEY && | ||
| process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' | ||
|
|
||
| if (!hasRealApiKey) { | ||
| test.skip() | ||
| return | ||
| } | ||
|
|
||
| const citations = page.locator('[data-testid="citation-superscript"]') | ||
| const citationCount = await citations.count() | ||
|
|
||
| if (citationCount > 0) { | ||
| // Click citation to open popover | ||
| await citations.first().click() | ||
|
|
||
| const popover = page.locator('[data-testid="citation-popover"]') | ||
| await expect(popover).toBeVisible() | ||
|
|
||
| // Click outside the popover | ||
| await page.locator('[data-testid="chat-view"]').click({ position: { x: 10, y: 10 } }) | ||
|
|
||
| // Popover should close | ||
| await expect(popover).not.toBeVisible({ timeout: 3000 }) | ||
| } else { | ||
| test.skip() | ||
| } | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.