|
1 | | -import { describe, it, expect, beforeAll, afterAll } from 'vitest' |
| 1 | +import { existsSync, rmSync } from 'node:fs' |
| 2 | +import { join } from 'node:path' |
| 3 | +import { fileURLToPath } from 'node:url' |
2 | 4 | import { build } from 'vite' |
3 | 5 | import { crx } from '@crxjs/vite-plugin' |
4 | | -import { resolve, dirname } from 'path' |
5 | | -import { fileURLToPath } from 'url' |
6 | | -import { existsSync, rmSync } from 'fs' |
7 | 6 | import { chromium, type BrowserContext } from 'playwright-chromium' |
| 7 | +import { describe, test, expect, beforeEach, afterEach } from 'vitest' |
8 | 8 |
|
9 | | -const __dirname = dirname(fileURLToPath(import.meta.url)) |
10 | | -const outDir = resolve(__dirname, 'dist-e2e-test') |
| 9 | +const __dirname = fileURLToPath(new URL('.', import.meta.url)) |
| 10 | +const distDir = join(__dirname, 'dist') |
| 11 | +const cacheDir = join(__dirname, '.chromium') |
11 | 12 |
|
12 | 13 | const manifest = { |
13 | | - manifest_version: 3, |
14 | | - name: 'CRXJS E2E Test', |
| 14 | + manifest_version: 3 as const, |
| 15 | + name: 'Vite7 E2E Test Extension', |
15 | 16 | version: '1.0.0', |
16 | 17 | content_scripts: [ |
17 | 18 | { |
18 | | - matches: ['<all_urls>'], |
19 | 19 | js: ['src/content.js'], |
| 20 | + matches: ['https://example.com/*'], |
20 | 21 | }, |
21 | 22 | ], |
22 | 23 | } |
23 | 24 |
|
24 | | -describe('E2E Browser Test', () => { |
25 | | - let context: BrowserContext |
| 25 | +// Simple HTML page for testing |
| 26 | +const exampleHtml = ` |
| 27 | +<!DOCTYPE html> |
| 28 | +<html> |
| 29 | +<head><title>Example</title></head> |
| 30 | +<body> |
| 31 | + <h1>Example Page</h1> |
| 32 | + <div id="test-target">Original content</div> |
| 33 | +</body> |
| 34 | +</html> |
| 35 | +` |
26 | 36 |
|
27 | | - beforeAll(async () => { |
28 | | - // Clean up before test |
29 | | - if (existsSync(outDir)) { |
30 | | - rmSync(outDir, { recursive: true }) |
| 37 | +describe('E2E Browser Tests', () => { |
| 38 | + let browser: BrowserContext | undefined |
| 39 | + |
| 40 | + beforeEach(async () => { |
| 41 | + // Clean up directories |
| 42 | + if (existsSync(distDir)) { |
| 43 | + rmSync(distDir, { recursive: true, force: true }) |
| 44 | + } |
| 45 | + if (existsSync(cacheDir)) { |
| 46 | + rmSync(cacheDir, { recursive: true, force: true }) |
31 | 47 | } |
| 48 | + }) |
32 | 49 |
|
| 50 | + afterEach(async () => { |
| 51 | + if (browser) { |
| 52 | + await browser.close() |
| 53 | + browser = undefined |
| 54 | + } |
| 55 | + }) |
| 56 | + |
| 57 | + test('extension content script runs in browser after build', async () => { |
33 | 58 | // Build the extension |
34 | 59 | await build({ |
35 | 60 | root: __dirname, |
36 | 61 | logLevel: 'silent', |
37 | 62 | build: { |
38 | | - outDir, |
39 | | - emptyOutDir: true, |
| 63 | + outDir: 'dist', |
40 | 64 | minify: false, |
41 | 65 | }, |
42 | 66 | plugins: [crx({ manifest })], |
43 | 67 | }) |
44 | 68 |
|
45 | | - // Launch browser with extension |
46 | | - context = await chromium.launchPersistentContext('', { |
| 69 | + // Verify build output |
| 70 | + expect(existsSync(join(distDir, 'manifest.json'))).toBe(true) |
| 71 | + |
| 72 | + // Launch browser with extension loaded |
| 73 | + browser = await chromium.launchPersistentContext(cacheDir, { |
47 | 74 | headless: false, |
48 | 75 | args: [ |
49 | | - `--disable-extensions-except=${outDir}`, |
50 | | - `--load-extension=${outDir}`, |
51 | | - '--no-first-run', |
52 | | - '--disable-gpu', |
| 76 | + `--disable-extensions-except=${distDir}`, |
| 77 | + `--load-extension=${distDir}`, |
| 78 | + '--headless=new', |
53 | 79 | ], |
54 | 80 | }) |
55 | | - }) |
56 | 81 |
|
57 | | - afterAll(async () => { |
58 | | - if (context) { |
59 | | - await context.close() |
60 | | - } |
61 | | - if (existsSync(outDir)) { |
62 | | - rmSync(outDir, { recursive: true }) |
63 | | - } |
64 | | - }) |
| 82 | + // Mock example.com |
| 83 | + await browser.route('https://example.com/**', async (route) => { |
| 84 | + await route.fulfill({ |
| 85 | + status: 200, |
| 86 | + contentType: 'text/html', |
| 87 | + body: exampleHtml, |
| 88 | + }) |
| 89 | + }) |
65 | 90 |
|
66 | | - it('should load extension in browser', async () => { |
67 | | - const page = await context.newPage() |
| 91 | + // Navigate to the page |
| 92 | + const page = await browser.newPage() |
68 | 93 | await page.goto('https://example.com') |
69 | 94 |
|
70 | | - // Wait for content script to inject the div |
71 | | - const testDiv = await page.waitForSelector('#crxjs-test', { |
72 | | - timeout: 10000, |
| 95 | + // Wait for our content script element to appear |
| 96 | + const crxElement = page.locator('#crxjs-vite7-test') |
| 97 | + await crxElement.waitFor({ timeout: 10000 }) |
| 98 | + |
| 99 | + // Verify the content script ran |
| 100 | + const text = await crxElement.textContent() |
| 101 | + expect(text).toContain('CRXJS Vite7 E2E Test') |
| 102 | + |
| 103 | + // Verify element has correct styling (proves CSS was injected) |
| 104 | + const bgColor = await crxElement.evaluate( |
| 105 | + (el) => window.getComputedStyle(el).backgroundColor, |
| 106 | + ) |
| 107 | + expect(bgColor).toBe('rgb(76, 175, 80)') // #4CAF50 |
| 108 | + }, 60000) |
| 109 | + |
| 110 | + test('content script DOM manipulation works', async () => { |
| 111 | + // Build the extension |
| 112 | + await build({ |
| 113 | + root: __dirname, |
| 114 | + logLevel: 'silent', |
| 115 | + build: { |
| 116 | + outDir: 'dist', |
| 117 | + minify: false, |
| 118 | + }, |
| 119 | + plugins: [crx({ manifest })], |
73 | 120 | }) |
74 | 121 |
|
75 | | - expect(testDiv).toBeTruthy() |
76 | | - const text = await testDiv.textContent() |
77 | | - expect(text).toBe('CRXJS Extension Active') |
| 122 | + // Launch browser with extension loaded |
| 123 | + browser = await chromium.launchPersistentContext(cacheDir, { |
| 124 | + headless: false, |
| 125 | + args: [ |
| 126 | + `--disable-extensions-except=${distDir}`, |
| 127 | + `--load-extension=${distDir}`, |
| 128 | + '--headless=new', |
| 129 | + ], |
| 130 | + }) |
78 | 131 |
|
79 | | - await page.close() |
80 | | - }) |
| 132 | + await browser.route('https://example.com/**', async (route) => { |
| 133 | + await route.fulfill({ |
| 134 | + status: 200, |
| 135 | + contentType: 'text/html', |
| 136 | + body: exampleHtml, |
| 137 | + }) |
| 138 | + }) |
| 139 | + |
| 140 | + const page = await browser.newPage() |
| 141 | + await page.goto('https://example.com') |
| 142 | + |
| 143 | + // Original page content should still be there |
| 144 | + const heading = page.locator('h1') |
| 145 | + await heading.waitFor({ timeout: 5000 }) |
| 146 | + expect(await heading.textContent()).toBe('Example Page') |
| 147 | + |
| 148 | + // Content script element should be added |
| 149 | + const crxElement = page.locator('#crxjs-vite7-test') |
| 150 | + await crxElement.waitFor({ timeout: 10000 }) |
| 151 | + expect(await crxElement.isVisible()).toBe(true) |
| 152 | + }, 60000) |
| 153 | + |
| 154 | + test('multiple page navigations work with content script', async () => { |
| 155 | + // Build the extension |
| 156 | + await build({ |
| 157 | + root: __dirname, |
| 158 | + logLevel: 'silent', |
| 159 | + build: { |
| 160 | + outDir: 'dist', |
| 161 | + minify: false, |
| 162 | + }, |
| 163 | + plugins: [crx({ manifest })], |
| 164 | + }) |
| 165 | + |
| 166 | + browser = await chromium.launchPersistentContext(cacheDir, { |
| 167 | + headless: false, |
| 168 | + args: [ |
| 169 | + `--disable-extensions-except=${distDir}`, |
| 170 | + `--load-extension=${distDir}`, |
| 171 | + '--headless=new', |
| 172 | + ], |
| 173 | + }) |
| 174 | + |
| 175 | + await browser.route('https://example.com/**', async (route) => { |
| 176 | + await route.fulfill({ |
| 177 | + status: 200, |
| 178 | + contentType: 'text/html', |
| 179 | + body: exampleHtml, |
| 180 | + }) |
| 181 | + }) |
| 182 | + |
| 183 | + const page = await browser.newPage() |
| 184 | + |
| 185 | + // First navigation |
| 186 | + await page.goto('https://example.com/page1') |
| 187 | + let crxElement = page.locator('#crxjs-vite7-test') |
| 188 | + await crxElement.waitFor({ timeout: 10000 }) |
| 189 | + expect(await crxElement.isVisible()).toBe(true) |
| 190 | + |
| 191 | + // Second navigation - content script should run again |
| 192 | + await page.goto('https://example.com/page2') |
| 193 | + crxElement = page.locator('#crxjs-vite7-test') |
| 194 | + await crxElement.waitFor({ timeout: 10000 }) |
| 195 | + expect(await crxElement.isVisible()).toBe(true) |
| 196 | + }, 60000) |
81 | 197 | }) |
0 commit comments