Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ dist
# web-ext build
/web-ext-artifacts/

# Test artifacts
test-results/
playwright-report/
.vitest/
.test-user-data/
.test-user-data-firefox/
tests/e2e/.extension-id

# Ignore other package managers
package-lock.json
yarn.lock
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.3/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
"files": { "ignoreUnknown": false, "includes": ["**"], "maxSize": 8388608 },
"formatter": {
Expand Down
11 changes: 0 additions & 11 deletions docs/TESTING.md

This file was deleted.

15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,21 @@
"dev:firefox": "cross-env BROWSER=firefox bun tools/bundler.ts && concurrently --kill-others \"cross-env BROWSER=firefox bun tools/watcher.ts\" \"web-ext run --source-dir=build\"",
"dev:chromium": "cross-env BROWSER=chromium bun tools/bundler.ts && concurrently --kill-others \"cross-env BROWSER=chromium bun tools/watcher.ts\" \"web-ext run -t chromium --source-dir=build\"",
"dev:firefox-android": "cross-env BROWSER=firefox bun tools/bundler.ts && concurrently --kill-others \"cross-env BROWSER=firefox bun tools/watcher.ts\" \"web-ext run -t firefox-android --source-dir=build\"",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:all": "npm run test && npm run test:e2e",
"format": "biome format --write && prettier --write '**/*.{json,md,html}' '!build/**' '!node_modules/**' '!web-ext-artifacts/**'",
"format:check": "biome format && prettier --check '**/*.{json,md,html}' '!build/**' '!node_modules/**' '!web-ext-artifacts/**'",
"webext:lint": "web-ext lint --source-dir=build",
"lint": "biome lint",
"lint:fix": "biome lint --write",
"typecheck": "tsc --noEmit",
"spell": "cspell \"**/*.{ts,js,md,json,txt,html,css}\" \"Makefile\" --gitignore --cache",
"precommit": "bun run lint && bun run format:check && bun run typecheck && bun run spell && bun run build:firefox && web-ext lint --source-dir=build && bun run build:chromium",
"precommit": "npm run test && bun run lint && bun run format:check && bun run typecheck && bun run spell && bun run build:firefox && web-ext lint --source-dir=build && bun run build:chromium",
"prepare": "husky"
},
"type": "module",
Expand All @@ -61,19 +68,25 @@
},
"devDependencies": {
"@biomejs/biome": "^2.3.0",
"@playwright/test": "^1.56.1",
"@types/chrome": "^0.1.24",
"@types/firefox-webext-browser": "^143.0.0",
"@types/fs-extra": "^11.0.4",
"@types/node": "^24.9.1",
"@types/webextension-polyfill": "^0.12.4",
"@vitest/coverage-v8": "^4.0.8",
"@vitest/ui": "^4.0.8",
"chokidar": "^4.0.3",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"cspell": "^9.2.2",
"esbuild": "^0.25.11",
"globals": "^16.4.0",
"happy-dom": "^20.0.10",
"husky": "^9.1.7",
"jsdom": "^27.2.0",
"prettier": "^3.6.2",
"vitest": "^4.0.8",
"web-ext": "^9.1.0",
"webextension-polyfill": "^0.12.0"
}
Expand Down
39 changes: 39 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests/e2e',
fullyParallel: false, // Run tests serially for extension tests
forbidOnly: !!process.env['CI'],
retries: process.env['CI'] ? 2 : 0,
workers: 1, // Use single worker for extension tests
reporter: 'html',
timeout: 120000, // 120 seconds per test (form filling with real API takes time)

use: {
headless: false, // Extensions require headed mode
viewport: { width: 1280, height: 720 },
actionTimeout: 30000, // 30 seconds for actions (API calls take time)
navigationTimeout: 30000, // 30 seconds for navigation
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure', // Record video of failed tests
},

projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// channel is set in extension-fixture.ts for launchPersistentContext
},
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
},
],

// No web server needed for extension tests
});
21 changes: 21 additions & 0 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,31 @@ interface MagicPromptMessage {
model: LLMEngineType;
}

// Helper function to store extension ID for testing
async function storeExtensionIdForTesting() {
try {
await browser.storage.local.set({
__test_extension_id: browser.runtime.id,
});
} catch (e) {
// Silently fail if storage isn't available
}
}

browser.runtime.onInstalled.addListener(async () => {
await MetricsManager.getInstance().getMetrics();
await storeExtensionIdForTesting();
});

// Also store ID when service worker starts (not just on install)
// This ensures tests can always find the ID
browser.runtime.onStartup.addListener(async () => {
await storeExtensionIdForTesting();
});

// Store immediately on load for first-time testing
storeExtensionIdForTesting();

browser.runtime.onMessage.addListener(
async (message: unknown, _sender: browser.Runtime.MessageSender) => {
const typedMessage = message as ChromeResponseMessage | MagicPromptMessage;
Expand Down
30 changes: 21 additions & 9 deletions src/docFillerCore/engines/gptEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ export class LLMEngine {

private metricsManager = MetricsManager.getInstance();

constructor(engine: LLMEngineType) {
constructor(
engine: LLMEngineType,
providedApiKeys?: Partial<Record<string, string>>,
) {
this.engine = engine;

this.instances = {
Expand All @@ -61,10 +64,10 @@ export class LLMEngine {
};

this.apiKeys = {
chatGptApiKey: undefined,
geminiApiKey: undefined,
mistralApiKey: undefined,
anthropicApiKey: undefined,
chatGptApiKey: providedApiKeys?.['chatGptApiKey'],
geminiApiKey: providedApiKeys?.['geminiApiKey'],
mistralApiKey: providedApiKeys?.['mistralApiKey'],
anthropicApiKey: providedApiKeys?.['anthropicApiKey'],
};

this.fetchApiKeys()
Expand All @@ -83,10 +86,19 @@ export class LLMEngine {
}

private async fetchApiKeys(): Promise<void> {
this.apiKeys['chatGptApiKey'] = await getChatGptApiKey();
this.apiKeys['geminiApiKey'] = await getGeminiApiKey();
this.apiKeys['mistralApiKey'] = await getMistralApiKey();
this.apiKeys['anthropicApiKey'] = await getAnthropicApiKey();
// Only fetch from storage if not provided via constructor
if (!this.apiKeys['chatGptApiKey']) {
this.apiKeys['chatGptApiKey'] = await getChatGptApiKey();
}
if (!this.apiKeys['geminiApiKey']) {
this.apiKeys['geminiApiKey'] = await getGeminiApiKey();
}
if (!this.apiKeys['mistralApiKey']) {
this.apiKeys['mistralApiKey'] = await getMistralApiKey();
}
if (!this.apiKeys['anthropicApiKey']) {
this.apiKeys['anthropicApiKey'] = await getAnthropicApiKey();
}
}
public instantiateEngine(engine: LLMEngineType): LLMInstance {
switch (engine) {
Expand Down
Loading
Loading