Skip to content

Conversation

@google-labs-jules
Copy link
Contributor

@google-labs-jules google-labs-jules bot commented Oct 22, 2025

User description

This change introduces a comprehensive end-to-end test suite to the project. It adds data-testid attributes to all interactive UI elements and implements a wide range of tests for the header, chat, map, sidebar, and mobile UI. This will help to prevent regressions and ensure the stability of the application. It also includes a fix for a build error that was discovered during the verification process.


PR created automatically by Jules for task 12672328258465556184


PR Type

Tests, Enhancement, Bug fix


Description

  • Add comprehensive Playwright E2E test suite with tests for chat, header, map, sidebar, and mobile UI

  • Add data-testid attributes to all interactive UI elements for reliable test targeting

  • Fix incorrect import path in mapbox_mcp/hooks.ts from QCX/lib/utils to @/lib/utils

  • Configure Playwright with multi-browser support (Chromium, Firefox, WebKit) and mobile device testing


Diagram Walkthrough

flowchart LR
  A["UI Components"] -->|"Add data-testid"| B["Enhanced Components"]
  C["Playwright Config"] -->|"Configure testing"| D["Test Infrastructure"]
  E["Test Files"] -->|"Chat, Header, Map, Sidebar, Mobile"| F["E2E Test Coverage"]
  B --> F
  D --> F
  G["Import Fix"] -->|"QCX → @/"| H["Fixed Build"]
Loading

File Walkthrough

Relevant files
Bug fix
1 files
hooks.ts
Fix incorrect import path in hooks                                             
+1/-1     
Tests
6 files
playwright.config.ts
Add Playwright configuration for E2E testing                         
+42/-0   
chat.spec.ts
Add comprehensive chat functionality tests                             
+47/-0   
header.spec.ts
Add header and navigation tests                                                   
+30/-0   
map.spec.ts
Add map functionality and controls tests                                 
+35/-0   
mobile.spec.ts
Add mobile UI interaction tests                                                   
+27/-0   
sidebar.spec.ts
Add sidebar and chat history tests                                             
+30/-0   
Enhancement
9 files
chat-panel.tsx
Add data-testid attributes to chat elements                           
+5/-1     
header.tsx
Add data-testid to header buttons                                               
+2/-2     
history-item.tsx
Add data-testid to history item link                                         
+1/-0     
history.tsx
Add data-testid to history panel elements                               
+2/-1     
map-toggle.tsx
Add data-testid to map toggle menu items                                 
+4/-4     
mobile-icons-bar.tsx
Add data-testid to mobile icon buttons                                     
+7/-7     
mode-toggle.tsx
Add data-testid to theme toggle elements                                 
+5/-5     
profile-toggle.tsx
Add data-testid to profile menu items                                       
+5/-5     
chat-history-client.tsx
Add data-testid to history clear dialog buttons                   
+3/-2     
Dependencies
1 files
package.json
Add Playwright test dependency                                                     
+1/-0     

This commit adds a comprehensive end-to-end test suite using Playwright to verify the functionality of all major UI components and user flows.

It includes:
- `data-testid` attributes added to all interactive UI elements for reliable testing.
- New test files for the header, map, sidebar, and mobile-specific UI.
- Expanded tests for the chat functionality, including file attachments and new chat creation.
- Fixes for placeholder code and removal of unnecessary test files.
- A fix for a build error caused by an incorrect import path in `mapbox_mcp/hooks.ts`.
@google-labs-jules
Copy link
Contributor Author

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link

vercel bot commented Oct 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
qcx Ready Ready Preview Comment Oct 24, 2025 0:29am

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-merge-pro
Copy link
Contributor

qodo-merge-pro bot commented Oct 23, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
- [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true -->

</details></td></tr>
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@ngoiyaeric
Copy link
Collaborator

@coderabbitai can you figure out how to achieve a comprehensive test suite of the whole applications current states and functionality and review the existing code.

@qodo-merge-pro
Copy link
Contributor

qodo-merge-pro bot commented Oct 23, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Make E2E tests more robust

Refactor the E2E tests to be more robust by removing dependencies on
implementation details. Specifically, avoid accessing the global window.map
object and creating files during test execution.

Examples:

tests/map.spec.ts [22]
    const getZoom = () => page.evaluate(() => (window as any).map.getZoom());
tests/chat.spec.ts [24]
    require('fs').writeFileSync(filePath, 'This is a test file.');

Solution Walkthrough:

Before:

// tests/map.spec.ts
test('should zoom in and out', async ({ page }) => {
  const getZoom = () => page.evaluate(() => (window as any).map.getZoom());
  const initialZoom = await getZoom();
  await page.click('.mapboxgl-ctrl-zoom-in');
  const zoomedInZoom = await getZoom();
  expect(zoomedInZoom).toBeGreaterThan(initialZoom);
});

// tests/chat.spec.ts
test('should allow a user to attach a file', async ({ page }) => {
  const filePath = path.join(__dirname, 'test-file.txt');
  require('fs').writeFileSync(filePath, 'This is a test file.');
  await fileChooser.setFiles(filePath);
  // ...
});

After:

// tests/map.spec.ts
test('should zoom in and out', async ({ page }) => {
  // Use visual regression or another method that doesn't rely on internal state
  const initialMapImage = await page.locator('.mapboxgl-canvas').screenshot();
  await page.click('.mapboxgl-ctrl-zoom-in');
  await page.waitForTimeout(500); // Wait for animation
  await expect(page.locator('.mapboxgl-canvas')).not.toHaveScreenshot(initialMapImage);
});

// tests/chat.spec.ts
test('should allow a user to attach a file', async ({ page }) => {
  // Use a pre-existing file from a test assets directory
  const filePath = path.join(__dirname, '../test-assets/test-file.txt');
  await fileChooser.setFiles(filePath);
  // ...
});
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies brittle patterns in the new E2E tests, such as relying on the global window.map object and creating files during test execution, which undermines the goal of a stable test suite.

Medium
Possible issue
Replace fixed timeout with polling
Suggestion Impact:The commit removed fixed timeouts and replaced them with a dynamic wait using page.waitForFunction to wait for zoom changes, aligning with the intent to avoid flaky fixed delays.

code diff:

-    await page.waitForTimeout(500); // Wait for the zoom animation
+    await page.waitForFunction(() => (window as any).map.getZoom() > initialZoom);
     const zoomedInZoom = await getZoom();
     expect(zoomedInZoom).toBeGreaterThan(initialZoom);
 
     await page.click('.mapboxgl-ctrl-zoom-out');
-    await page.waitForTimeout(500); // Wait for the zoom animation
+    await page.waitForFunction(() => (window as any).map.getZoom() < zoomedInZoom);
     const zoomedOutZoom = await getZoom();
     expect(zoomedOutZoom).toBeLessThan(zoomedInZoom);

Replace page.waitForTimeout() with expect.poll to reliably wait for the map zoom
level to change, making the test more robust.

tests/map.spec.ts [24-33]

 const initialZoom = await getZoom();
 await page.click('.mapboxgl-ctrl-zoom-in');
-await page.waitForTimeout(500); // Wait for the zoom animation
+
+await expect.poll(async () => getZoom(), { timeout: 5000 }).toBeGreaterThan(initialZoom);
 const zoomedInZoom = await getZoom();
-expect(zoomedInZoom).toBeGreaterThan(initialZoom);
 
 await page.click('.mapboxgl-ctrl-zoom-out');
-await page.waitForTimeout(500); // Wait for the zoom animation
-const zoomedOutZoom = await getZoom();
-expect(zoomedOutZoom).toBeLessThan(zoomedInZoom);
+await expect.poll(async () => getZoom(), { timeout: 5000 }).toBeLessThan(zoomedInZoom);

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies the use of a fixed timeout, which can cause flaky tests, and proposes using expect.poll for a more robust and reliable check.

Medium
Clean up created test files

Use a try...finally block to ensure the temporary file created for the
attachment test is deleted after the test runs, preventing side effects.

tests/chat.spec.ts [22-29]

+const fs = require('fs').promises;
 const filePath = path.join(__dirname, 'test-file.txt');
-// Create a dummy file for the test
-require('fs').writeFileSync(filePath, 'This is a test file.');
+try {
+  // Create a dummy file for the test
+  await fs.writeFile(filePath, 'This is a test file.');
 
-const fileChooserPromise = page.waitForEvent('filechooser');
-await page.click('[data-testid="attachment-button"]');
-const fileChooser = await fileChooserPromise;
-await fileChooser.setFiles(filePath);
+  const fileChooserPromise = page.waitForEvent('filechooser');
+  await page.click('[data-testid="attachment-button"]');
+  const fileChooser = await fileChooserPromise;
+  await fileChooser.setFiles(filePath);
+} finally {
+  // Clean up the dummy file
+  await fs.unlink(filePath);
+}
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a test hygiene issue where a created file is not cleaned up, and proposes a robust solution using try...finally to prevent side effects.

Low
General
Use robust class attribute assertions
Suggestion Impact:The commit changed expectations from toHaveAttribute('class', 'dark') to toHaveClass with a regex matching the 'dark' class, implementing the suggested approach.

code diff:

-    await expect(html).toHaveAttribute('class', 'dark');
+    await expect(html).toHaveClass(/(^|\s)dark(\s|$)/);
 
     await page.click('[data-testid="theme-toggle"]');
     await page.click('[data-testid="theme-light"]');
-    await expect(html).not.toHaveAttribute('class', 'dark');
+    await expect(html).not.toHaveClass(/(^|\s)dark(\s|$)/);

Use the toHaveClass assertion instead of toHaveAttribute to check for the
theme's class on the html element, making the test more robust.

tests/header.spec.ts [9-16]

 await page.click('[data-testid="theme-toggle"]');
 await page.click('[data-testid="theme-dark"]');
 const html = page.locator('html');
-await expect(html).toHaveAttribute('class', 'dark');
+await expect(html).toHaveClass(/dark/);
 
 await page.click('[data-testid="theme-toggle"]');
 await page.click('[data-testid="theme-light"]');
-await expect(html).not.toHaveAttribute('class', 'dark');
+await expect(html).not.toHaveClass(/dark/);

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that toHaveAttribute is a brittle way to check for a class and recommends the more robust toHaveClass assertion, improving test reliability.

Medium
  • Update

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get an overview of the application structure
echo "=== Application Structure ==="
fd -t f -e tsx -e ts -e jsx -e js | grep -E "(app|components|lib|pages)" | head -50

echo -e "\n=== Component Files ==="
fd -t f -e tsx --full-path components | head -30

echo -e "\n=== App Routes/Pages ==="
fd -t f -e tsx -e ts --full-path app | grep -v node_modules | head -30

echo -e "\n=== Test Files ==="
fd -t f -e ts --full-path e2e

echo -e "\n=== API Routes ==="
fd -t f -e ts --full-path "api" | grep -v node_modules | head -20

echo -e "\n=== Key Configuration Files ==="
ls -la | grep -E "(package.json|tsconfig|next.config|tailwind|playwright)"

Length of output: 3069


🏁 Script executed:

#!/bin/bash

# Examine the existing test files in detail
echo "=== Chat Test Coverage ==="
cat e2e/chat.spec.ts

echo -e "\n=== Header Test Coverage ==="
cat e2e/header.spec.ts

echo -e "\n=== Map Test Coverage ==="
cat e2e/map.spec.ts

echo -e "\n=== Sidebar Test Coverage ==="
cat e2e/sidebar.spec.ts

echo -e "\n=== Mobile Test Coverage ==="
cat e2e/mobile.spec.ts

Length of output: 395


🏁 Script executed:

#!/bin/bash

# Get key component files to understand application features
echo "=== Main Components Overview ==="
fd -t f "chat" --extension tsx | head -10

echo -e "\n=== Chat Panel Component ==="
cat components/chat-panel.tsx

echo -e "\n=== Header Component ==="
cat components/header.tsx

Length of output: 10393


🏁 Script executed:

#!/bin/bash

# Check for authentication, forms, and other interactive features
echo "=== Authentication Components ==="
fd -t f --full-path components | xargs grep -l "auth\|login\|sign" | head -10

echo -e "\n=== Form Components ==="
fd -t f --full-path components | xargs grep -l "form\|input\|textarea" 2>/dev/null | head -10

echo -e "\n=== API Endpoints ==="
fd -t f -e ts --full-path "app.*api" | head -20

Length of output: 684


🏁 Script executed:

#!/bin/bash

# Look at the app directory structure for Next.js app router
echo "=== App Directory Structure ==="
find app -type f -name "*.tsx" -o -name "*.ts" 2>/dev/null | head -30

echo -e "\n=== Main Layout ==="
cat app/layout.tsx 2>/dev/null || echo "Layout not found in standard location"

echo -e "\n=== Main Page ==="
cat app/page.tsx 2>/dev/null || echo "Page not found in standard location"

Length of output: 3191


🏁 Script executed:

#!/bin/bash

# Get the test files from the PR diff
echo "=== Get list of all changed files in PR ==="
gh pr view 350 --json files --jq '.files[].path'

echo -e "\n=== Get test files content from PR ==="
gh pr diff 350 | head -1000

Length of output: 31645


🏁 Script executed:

#!/bin/bash

# Get more specific information about components and their interactions
echo "=== Chat Component ==="
cat components/chat.tsx

echo -e "\n=== Main Actions ==="
head -100 app/actions.tsx

echo -e "\n=== API Chat Route ==="
cat app/api/chat/route.ts

Length of output: 11962


🏁 Script executed:

#!/bin/bash

# Examine more components for comprehensive coverage analysis
echo "=== Search Components ==="
cat components/search-section.tsx 2>/dev/null || echo "Not found"

echo -e "\n=== Calendar Component ==="
cat components/calendar-notepad.tsx

echo -e "\n=== Clear History ==="
cat components/clear-history.tsx

Length of output: 9510


🏁 Script executed:

#!/bin/bash

# Check for error handling, loading states, and edge cases
echo "=== Empty Screen Component ==="
cat components/empty-screen.tsx

echo -e "\n=== Message Component ==="
cat components/message.tsx

echo -e "\n=== Follow-up Panel ==="
cat components/followup-panel.tsx

Length of output: 4862


🏁 Script executed:

#!/bin/bash

# Get the actual test files from the PR diff - focusing on tests directory
echo "=== Getting complete test files from PR diff ==="
gh pr diff 350 --patch | grep -A 500 "diff --git a/e2e"

Length of output: 48


@ngoiyaeric, I'll provide a comprehensive analysis of the test suite and recommendations for achieving complete coverage of your application.

Current Test Coverage Analysis

The PR adds a solid foundation with 5 E2E test files covering:

  • ✅ Chat input and message submission
  • ✅ File attachments
  • ✅ New chat creation
  • ✅ Theme switching
  • ✅ Profile menu
  • ✅ Calendar toggle
  • ✅ Map mode switching
  • ✅ Mobile UI interactions
  • ✅ Chat history

Critical Gaps in Test Coverage

1. API Endpoints (Not Tested)

// tests/api/chat.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Chat API', () => {
  test('POST /api/chat creates a new chat', async ({ request }) => {
    const response = await request.post('/api/chat', {
      data: {
        title: 'Test Chat',
        initialMessageContent: 'Hello API',
        role: 'user'
      }
    });
    expect(response.status()).toBe(201);
    const data = await response.json();
    expect(data.chatId).toBeDefined();
  });

  test('GET /api/chats/all returns user chats', async ({ request }) => {
    const response = await request.get('/api/chats/all');
    expect(response.ok()).toBeTruthy();
    const chats = await response.json();
    expect(Array.isArray(chats)).toBeTruthy();
  });
});

2. Search Functionality (Not Tested)

// tests/search.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Search Functionality', () => {
  test('should display search results for a query', async ({ page }) => {
    await page.goto('/');
    await page.fill('[data-testid="chat-input"]', 'climate change effects');
    await page.click('[data-testid="chat-submit"]');
    
    // Wait for search results to appear
    await expect(page.locator('.search-results')).toBeVisible({ timeout: 20000 });
    await expect(page.locator('.search-results .result-item')).toHaveCount.greaterThan(0);
  });

  test('should display image results', async ({ page }) => {
    await page.goto('/');
    await page.fill('[data-testid="chat-input"]', 'show me images of forests');
    await page.click('[data-testid="chat-submit"]');
    
    await expect(page.locator('[data-testid="search-images"]')).toBeVisible({ timeout: 20000 });
  });
});

3. Calendar/Notepad Features (Not Tested)

// tests/calendar.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Calendar and Notes', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="calendar-toggle"]');
    await expect(page.locator('[data-testid="calendar-notepad"]')).toBeVisible();
  });

  test('should add a note to current date', async ({ page }) => {
    const noteContent = 'Test note content';
    await page.fill('textarea[placeholder*="Add note"]', noteContent);
    await page.keyboard.press('Meta+Enter');
    
    await expect(page.locator(`text=${noteContent}`)).toBeVisible();
  });

  test('should tag location in note', async ({ page }) => {
    await page.fill('textarea[placeholder*="Add note"]', 'Location test');
    await page.click('button:has-text("MapPin")');
    
    await expect(page.locator('textarea')).toContainText('#location');
  });

  test('should navigate between dates', async ({ page }) => {
    const nextWeekButton = page.locator('button:has(svg.lucide-chevron-right)');
    await nextWeekButton.click();
    
    // Verify date changed (check for different date numbers)
    await page.waitForTimeout(500);
  });
});

4. Authentication & Authorization (Not Tested)

// tests/auth.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Authentication', () => {
  test('should redirect unauthenticated users', async ({ page }) => {
    // Clear cookies/storage
    await page.context().clearCookies();
    await page.goto('/');
    
    // Verify redirect or login prompt
    await expect(page).toHaveURL(/login|auth/);
  });

  test('should access protected API routes when authenticated', async ({ request }) => {
    const response = await request.get('/api/chats/all');
    expect(response.status()).not.toBe(401);
  });
});

5. Error Handling & Edge Cases (Not Tested)

// tests/error-handling.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Error Handling', () => {
  test('should handle network errors gracefully', async ({ page, context }) => {
    await page.goto('/');
    
    // Simulate offline
    await context.setOffline(true);
    await page.fill('[data-testid="chat-input"]', 'Test message');
    await page.click('[data-testid="chat-submit"]');
    
    // Verify error message or retry behavior
    await expect(page.locator('text=/error|failed|retry/i')).toBeVisible({ timeout: 10000 });
  });

  test('should handle file upload size limit', async ({ page }) => {
    await page.goto('/');
    
    // Try to upload file > 10MB (would need actual large file)
    const fileInput = page.locator('input[type="file"]');
    // This would trigger the alert in chat-panel.tsx
  });

  test('should prevent empty message submission', async ({ page }) => {
    await page.goto('/');
    const submitButton = page.locator('[data-testid="chat-submit"]');
    
    await expect(submitButton).toBeDisabled();
    
    await page.fill('[data-testid="chat-input"]', '   '); // Only spaces
    await expect(submitButton).toBeDisabled();
  });
});

6. Map Drawing & GeoJSON Features (Not Tested)

// tests/map-advanced.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Advanced Map Features', () => {
  test('should save drawn features', async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="map-toggle"]');
    await page.click('[data-testid="map-mode-draw"]');
    
    // Wait for draw controls
    await expect(page.locator('.mapboxgl-ctrl-draw-btn')).toBeVisible();
    
    // Simulate drawing (complex - may need custom helpers)
    // Verify features are saved to context
  });

  test('should load GeoJSON layers', async ({ page }) => {
    await page.goto('/');
    // Send a message that triggers GeoJSON display
    await page.fill('[data-testid="chat-input"]', 'Show me earthquake data');
    await page.click('[data-testid="chat-submit"]');
    
    // Verify GeoJSON layer is rendered on map
    await page.waitForTimeout(3000);
    const canvas = page.locator('.mapboxgl-canvas');
    await expect(canvas).toBeVisible();
  });
});

7. Settings & Profile Management (Incomplete)

// tests/settings.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';

test.describe('Settings', () => {
  test('should navigate through all settings sections', async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="profile-toggle"]');
    
    // Test each section
    await page.click('[data-testid="profile-settings"]');
    await expect(page.locator('text=/model|settings/i')).toBeVisible();
    
    await page.click('[data-testid="profile-toggle"]');
    await page.click('[data-testid="profile-appearance"]');
    await expect(page.locator('text=/appearance|theme/i')).toBeVisible();
    
    await page.click('[data-testid="profile-toggle"]');
    await page.click('[data-testid="profile-security"]');
    await expect(page.locator('text=/security|password/i')).toBeVisible();
  });
});

8. Accessibility Tests (Missing)

// tests/accessibility.spec.ts - NEW FILE NEEDED
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility', () => {
  test('should have no accessibility violations on home page', async ({ page }) => {
    await page.goto('/');
    
    const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('should be keyboard navigable', async ({ page }) => {
    await page.goto('/');
    
    // Tab through interactive elements
    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="profile-toggle"]')).toBeFocused();
    
    await page.keyboard.press('Tab');
    // Continue testing keyboard navigation
  });

  test('should have proper ARIA labels', async ({ page }) => {
    await page.goto('/');
    
    const submitButton = page.locator('[data-testid="chat-submit"]');
    await expect(submitButton).toHaveAttribute('aria-label', 'Send message');
  });
});

Test Quality Improvements

Issues with Current Tests:

  1. Brittle Timeouts (map.spec.ts):
// ❌ BAD: Arbitrary timeout
await page.waitForTimeout(500);

// ✅ GOOD: Wait for specific condition
await page.waitForFunction(() => (window as any).map.getZoom() > initialZoom);
  1. Direct Window Access (map.spec.ts):
// ❌ BAD: Assumes global window.map exists
const getZoom = () => page.evaluate(() => (window as any).map.getZoom());

// ✅ GOOD: Check if map exists first, or use data attributes
await page.waitForSelector('.mapboxgl-canvas');
const zoomLevel = await page.getAttribute('[data-map-zoom]', 'value');
  1. Test File Cleanup (chat.spec.ts):
// ❌ BAD: Creates file but doesn't clean up
require('fs').writeFileSync(filePath, 'This is a test file.');

// ✅ GOOD: Use fixtures and cleanup
import { test as base } from '@playwright/test';
import fs from 'fs/promises';

const test = base.extend({
  testFile: async ({}, use) => {
    const filePath = path.join(__dirname, 'test-file.txt');
    await fs.writeFile(filePath, 'This is a test file.');
    await use(filePath);
    await fs.unlink(filePath).catch(() => {}); // Cleanup
  }
});

Recommended Testing Strategy

1. Add Test Fixtures

// tests/fixtures/test-data.ts
export const testMessages = [
  { role: 'user', content: 'Hello' },
  { role: 'assistant', content: 'Hi there!' }
];

export const testChat = {
  id: 'test-chat-id',
  title: 'Test Chat',
  messages: testMessages
};

2. Mock API Responses

// tests/chat.spec.ts
test('should display mocked chat response', async ({ page }) => {
  await page.route('**/api/chat', route => {
    route.fulfill({
      status: 200,
      body: JSON.stringify({ message: 'Mocked response' })
    });
  });
  
  await page.goto('/');
  await page.fill('[data-testid="chat-input"]', 'Test');
  await page.click('[data-testid="chat-submit"]');
  
  await expect(page.locator('text=Mocked response')).toBeVisible();
});

3. Add Visual Regression Tests

// playwright.config.ts - add to config
expect: {
  toHaveScreenshot: { maxDiffPixels: 100 }
}

// tests/visual.spec.ts
test('homepage matches screenshot', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png');
});

4. Add Performance Tests

// tests/performance.spec.ts
import { test, expect } from '@playwright/test';

test('page should load within 3 seconds', async ({ page }) => {
  const startTime = Date.now();
  await page.goto('/');
  await page.waitForLoadState('networkidle');
  const loadTime = Date.now() - startTime;
  
  expect(loadTime).toBeLessThan(3000);
});

5. Integration Test Example

// tests/integration/chat-to-map.spec.ts
test('should update map when sending location query', async ({ page }) => {
  await page.goto('/');
  
  // Send message with location
  await page.fill('[data-testid="chat-input"]', 'Show me New York');
  await page.click('[data-testid="chat-submit"]');
  
  // Wait for bot response
  await page.waitForSelector('div.items-start', { timeout: 15000 });
  
  // Verify map updated (check for map markers or center change)
  await expect(page.locator('.mapboxgl-marker')).toBeVisible();
});

Recommended Test Organization

tests/
├── unit/                  # Component unit tests (consider Vitest)
├── integration/           # Feature integration tests
│   ├── chat-flow.spec.ts
│   ├── map-interaction.spec.ts
│   └── user-journey.spec.ts
├── e2e/                   # End-to-end tests
│   ├── chat.spec.ts      ✅ (exists)
│   ├── header.spec.ts    ✅ (exists)
│   ├── map.spec.ts       ✅ (exists)
│   ├── mobile.spec.ts    ✅ (exists)
│   ├── sidebar.spec.ts   ✅ (exists)
│   ├── search.spec.ts    ❌ (add)
│   ├── calendar.spec.ts  ❌ (add)
│   └── settings.spec.ts  ❌ (add)
├── api/                   # API endpoint tests
│   └── chat-api.spec.ts  ❌ (add)
├── accessibility/         # A11y tests
│   └── a11y.spec.ts      ❌ (add)
├── performance/           # Performance tests
│   └── perf.spec.ts      ❌ (add)
├── visual/                # Visual regression
│   └── visual.spec.ts    ❌ (add)
└── fixtures/              # Test data
    ├── test-data.ts
    └── test-helpers.ts

Immediate Action Items

  1. Fix existing test issues (timeouts, cleanup)
  2. Add API tests for all endpoints
  3. Add search functionality tests
  4. Add calendar/notepad tests
  5. Implement error handling tests
  6. Add accessibility tests (install @axe-core/playwright)
  7. Create test fixtures for reusable data
  8. Add CI/CD configuration for running tests

Would you like me to generate any of these specific test files, or would you prefer I create an issue to track this comprehensive testing roadmap?

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several E2E tests are brittle or incorrect: the mobile UI test queries a non-existent .mobile-icons-bar selector, and the mobile profile button is inert while the test expects a dropdown to open. Theme assertions rely on exact class equality, which is fragile, and the map zoom test relies on a global window.map that may not exist. The chat attachment test writes a file into the repo without cleanup; use the test output directory instead.

Additional notes (1)
  • Readability | tests/sidebar.spec.ts:26-28
    This final assertion is prone to race conditions when clearing history asynchronously and only checks visibility. Verifying that the list has zero items is more robust and self-documenting.
Summary of changes
  • Added data-testid attributes across multiple UI components (chat panel, header, history, map toggle, mobile icons bar, mode/profile toggles) to support E2E testing.
  • Introduced Playwright test infrastructure (playwright.config.ts) with multi-browser/device projects and a dev web server.
  • Added comprehensive E2E tests for chat, header, map, mobile, and sidebar flows.
  • Fixed an import path in mapbox_mcp/hooks.ts from QCX/lib/utils to @/lib/utils.
  • Updated .gitignore to exclude Playwright artifacts and logs; added @playwright/test to devDependencies.

<Plus className="h-[1.2rem] w-[1.2rem]" />
</Button>
<Button variant="ghost" size="icon">
<Button variant="ghost" size="icon" data-testid="mobile-profile-button">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mobile profile button does not open any menu; it’s just an inert icon button. Your mobile.spec.ts expects a profile menu to open and check [data-testid="profile-account"], which will fail. Reuse the existing ProfileToggle (as in the header) to provide the dropdown on mobile as well.

Suggestion

Replace the standalone profile button with the shared ProfileToggle component:

// at top of the file
import { ProfileToggle } from './profile-toggle'

// in the JSX, replace the button with:
<ProfileToggle />

Update the mobile test to click [data-testid="profile-toggle"] if needed. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change

<Paperclip className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<Button variant="ghost" size="icon">
<Button variant="ghost" size="icon" data-testid="mobile-submit-button">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This submit button on mobile has no onClick and thus does nothing; adding a data-testid to it may encourage tests to click it expecting to send messages. Either wire it to the actual submit logic or hide/disable it until implemented to avoid misleading tests and UX.

Suggestion

If the submit action is not implemented yet, disable the button to avoid confusion:

<Button variant="ghost" size="icon" data-testid="mobile-submit-button" disabled aria-disabled="true">
  <ArrowRight className="h-[1.2rem] w-[1.2rem]" />
</Button>

Or, preferably, wire it to the message submit handler (e.g., by passing an onSubmit prop). Reply with "@CharlieHelps yes please" if you'd like me to add a commit with the disabling change

Comment on lines 11 to 16
const html = page.locator('html');
await expect(html).toHaveAttribute('class', 'dark');

await page.click('[data-testid="theme-toggle"]');
await page.click('[data-testid="theme-light"]');
await expect(html).not.toHaveAttribute('class', 'dark');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using exact toHaveAttribute('class', 'dark') is brittle, since the <html> element may have multiple classes (e.g., Tailwind injects others). This can make the test flaky or falsely failing. Prefer checking classList contains dark.

Suggestion

Update the assertions to check for the presence/absence of the dark class rather than exact equality:

await expect(html).toHaveClass(/(^|\s)dark(\s|$)/);

// ... later
await expect(html).not.toHaveClass(/(^|\s)dark(\s|$)/);

Alternatively:

await expect(await html.evaluate(el => el.classList.contains('dark'))).toBe(true);
await expect(await html.evaluate(el => el.classList.contains('dark'))).toBe(false);

Reply with "@CharlieHelps yes please" if you'd like me to add a commit updating the tests

Comment on lines 22 to 25
const filePath = path.join(__dirname, 'test-file.txt');
// Create a dummy file for the test
require('fs').writeFileSync(filePath, 'This is a test file.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test writes a file into the source tree via require('fs').writeFileSync(...) and never cleans it up. It mixes require with ES imports and leaves local artifacts behind, which can pollute the working tree and cause flaky runs. Use Playwright’s test output folder and async FS APIs.

Suggestion

Use the test output directory and async FS:

import { promises as fs } from 'fs';

const filePath = test.info().outputPath('test-file.txt');
await fs.writeFile(filePath, 'This is a test file.');

This keeps artifacts out of the repo and ensures per-test isolation. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change

Comment on lines +22 to +23
const getZoom = () => page.evaluate(() => (window as any).map.getZoom());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test assumes a global window.map to read zoom, which is fragile and will throw if the app doesn’t expose the Mapbox instance globally. Tests should not depend on hidden globals; either instrument the app in test mode to expose the map or guard/skip when unavailable.

Suggestion

Guard against missing globals or expose the map in test mode. For a minimal test-side fix:

const hasMap = await page.evaluate(() => Boolean((window as any).map));
if (!hasMap) test.skip(true, 'Map instance not available on window for E2E');

const getZoom = () => page.evaluate(() => (window as any).map.getZoom());

Alternatively, instrument the app (in dev/test only) to assign the map instance to window.map after initialization. Reply with "@CharlieHelps yes please" if you'd like me to add a commit to apply the test-side guard

Comment on lines 36 to 41
webServer: {
command: 'bun run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 600000, // 10 minutes
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running E2E against the dev server (bun run dev) in CI tends to be slower and less deterministic (HMR, additional logging, non-optimized builds). Using a production build for CI improves reliability and performance.

Suggestion

Switch to a production server in CI while keeping dev locally:

webServer: {
  command: process.env.CI ? 'bun run build && bun run start' : 'bun run dev',
  url: 'http://localhost:3000',
  reuseExistingServer: !process.env.CI,
  timeout: 600000,
},

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 23, 2025 04:57
@ngoiyaeric ngoiyaeric added the 1.0.0 Standard release label Oct 24, 2025
This commit adds a comprehensive and robust end-to-end test suite using Playwright to verify the functionality of all major UI components and user flows.

It includes:
- `data-testid` attributes added to all interactive UI elements for reliable testing.
- New test files for the header, map, sidebar, and mobile-specific UI.
- Expanded tests for the chat functionality, including file attachments and new chat creation.
- Fixes for placeholder code and removal of unnecessary test files.
- A fix for a build error caused by an incorrect import path in `mapbox_mcp/hooks.ts`.
- Significant improvements to the quality and reliability of the tests based on detailed code review feedback. This includes:
  - Using more robust assertions.
  - Handling temporary files correctly.
  - Adding guards for fragile tests.
  - Improving the Playwright configuration for CI environments.
  - Fixing bugs in the UI that were discovered during testing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants