Skip to content

Commit 59c29ba

Browse files
committed
chore(vscode): add integration test
1 parent 8ea6ac9 commit 59c29ba

9 files changed

Lines changed: 209 additions & 25 deletions

File tree

.circleci/continue_config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ jobs:
4747
name: Install Dependencies
4848
command: |
4949
pnpm install
50+
- run:
51+
name: Fetch VSCode
52+
command: |
53+
cd vscode/extension
54+
pnpm run fetch-vscode
5055
- run:
5156
name: Run VSCode extension CI
5257
command: |

pnpm-lock.yaml

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode/extension/.vscode-test.mjs

Lines changed: 0 additions & 5 deletions
This file was deleted.

vscode/extension/E2E_TESTING.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# E2E Testing with Playwright
2+
3+
This directory contains end-to-end tests for the SQLMesh VS Code extension using Playwright.
4+
5+
## Setup
6+
7+
1. **Install dependencies:**
8+
```bash
9+
pnpm install
10+
```
11+
12+
2. **Download VS Code executable (one-time setup):**
13+
```bash
14+
pnpm run fetch-vscode
15+
```
16+
17+
This downloads VS Code and caches it in `.vscode-test/` directory. The paths are saved to `.vscode-test/paths.json` for Playwright to use.
18+
19+
3. **Install Playwright browsers:**
20+
```bash
21+
npx playwright install
22+
```
23+
24+
## Running Tests
25+
26+
- **Run all E2E tests:**
27+
```bash
28+
pnpm run test:e2e
29+
```
30+
31+
- **Run tests with UI (interactive):**
32+
```bash
33+
pnpm run test:e2e:ui
34+
```
35+
36+
- **Run tests in headed mode (visible browser):**
37+
```bash
38+
pnpm run test:e2e:headed
39+
```
40+
41+
## Test Structure
42+
43+
- `scripts/fetch-vscode.ts` - Downloads and caches VS Code executable
44+
- `playwright.config.ts` - Playwright configuration for Electron testing
45+
- `tests/webview.e2e.spec.ts` - E2E tests for webview panels
46+
47+
## How It Works
48+
49+
1. **VS Code as Electron app:** Playwright launches VS Code as an Electron application with the extension loaded in development mode
50+
2. **Extension isolation:** Each test runs with a fresh user data directory (`/tmp/vscode-test`)
51+
3. **Webview testing:** Tests can interact with webview content using frame locators
52+
4. **Visual regression:** Screenshots are captured and compared for pixel-perfect testing
53+
54+
## CI/CD
55+
56+
- The `.vscode-test` directory should be cached in CI to avoid re-downloading VS Code
57+
- Tests run in headless mode by default in CI environments
58+
- Screenshots are stored as test artifacts for comparison
59+
60+
## Adding New Tests
61+
62+
Create new test files in the `tests/` directory following the pattern:
63+
64+
```typescript
65+
import { test, expect, _electron as electron } from '@playwright/test';
66+
import path from 'path';
67+
import fs from 'fs-extra';
68+
69+
const VS_CODE_EXE = fs.readJsonSync('.vscode-test/paths.json').executablePath;
70+
const EXT_PATH = path.join(__dirname, '..');
71+
72+
test('my new test', async () => {
73+
const electronApp = await electron.launch({
74+
executablePath: VS_CODE_EXE,
75+
args: [
76+
`--extensionDevelopmentPath=${EXT_PATH}`,
77+
'--disable-workspace-trust',
78+
'--disable-telemetry',
79+
'--user-data-dir=/tmp/vscode-test',
80+
],
81+
});
82+
83+
const window = await electronApp.firstWindow();
84+
85+
// Your test logic here...
86+
87+
await electronApp.close();
88+
});
89+
```

vscode/extension/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@
8686
"pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint",
8787
"lint": "eslint src",
8888
"lint:fix": "eslint src --fix",
89-
"test": "vscode-test",
89+
"test": "pnpm run test:e2e",
90+
"test:e2e": "playwright test",
91+
"test:e2e:ui": "playwright test --ui",
92+
"test:e2e:headed": "playwright test --headed",
93+
"fetch-vscode": "tsx scripts/fetch-vscode.ts",
9094
"compile": "pnpm run check-types && node esbuild.js",
9195
"check-types": "tsc --noEmit",
9296
"watch": "node esbuild.js --watch",
@@ -105,6 +109,7 @@
105109
},
106110
"devDependencies": {
107111
"@eslint/js": "^9.25.1",
112+
"@playwright/test": "^1.48.2",
108113
"@types/mocha": "^10.0.10",
109114
"@types/node": "20.11.25",
110115
"@types/vscode": "1.96.0",
@@ -114,6 +119,7 @@
114119
"esbuild": "^0.25.2",
115120
"eslint": "^9.23.0",
116121
"ts-loader": "^9.5.2",
122+
"tsx": "^4.19.2",
117123
"typescript": "^5.8.2",
118124
"typescript-eslint": "^8.31.1"
119125
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { defineConfig } from '@playwright/test'
2+
import * as path from 'path'
3+
4+
export default defineConfig({
5+
testDir: 'tests',
6+
timeout: 60_000,
7+
retries: process.env.CI ? 1 : 0,
8+
9+
projects: [
10+
{
11+
name: 'electron-vscode',
12+
use: {
13+
// ⭢ we'll launch Electron ourselves – no browser needed
14+
browserName: 'chromium',
15+
headless: false, // headed makes screenshots deterministic
16+
launchOptions: {
17+
slowMo: process.env.CI ? 0 : 100,
18+
},
19+
},
20+
},
21+
],
22+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
downloadAndUnzipVSCode,
3+
resolveCliPathFromVSCodeExecutablePath,
4+
} from '@vscode/test-electron'
5+
import * as fs from 'fs-extra'
6+
import * as path from 'path'
7+
8+
;(async () => {
9+
const vscPath = await downloadAndUnzipVSCode('stable') // one-time-only
10+
console.log('VS Code downloaded to:', vscPath)
11+
12+
const cliPath = resolveCliPathFromVSCodeExecutablePath(vscPath) // optional
13+
console.log('CLI path:', cliPath)
14+
15+
// Save paths to a JSON file for Playwright to use
16+
const pathsFile = path.join('.vscode-test', 'paths.json')
17+
await fs.ensureDir(path.dirname(pathsFile))
18+
await fs.writeJson(
19+
pathsFile,
20+
{
21+
executablePath: vscPath,
22+
cliPath: cliPath,
23+
},
24+
{ spaces: 2 },
25+
)
26+
27+
console.log('Paths saved to:', pathsFile)
28+
})()

vscode/extension/src/test/extension.test.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { test, _electron as electron } from '@playwright/test';
2+
import path from 'path';
3+
import fs from 'fs-extra';
4+
5+
// Absolute path to the VS Code executable you downloaded in step 1.
6+
const VS_CODE_EXE = fs.readJsonSync('.vscode-test/paths.json').executablePath;
7+
// Where your extension lives on disk
8+
const EXT_PATH = path.join(__dirname, '..');
9+
// Where the sushi project lives which we open in the webview
10+
const PROJECT_PATH = path.join(__dirname, '..', '..', '..', 'examples', 'sushi');
11+
12+
test('Lineage panel renders correctly', async () => {
13+
// ➊ Launch VS Code as an Electron app with our extension loaded
14+
const electronApp = await electron.launch({
15+
executablePath: VS_CODE_EXE,
16+
args: [
17+
`--extensionDevelopmentPath=${EXT_PATH}`,
18+
'--disable-workspace-trust', // no modal prompt
19+
'--disable-telemetry',
20+
'--user-data-dir=/tmp/vscode-test', // throwaway profile
21+
`${PROJECT_PATH}`,
22+
],
23+
});
24+
25+
// ➋ Grab the first window that appears (the Workbench)
26+
const window = await electronApp.firstWindow();
27+
28+
// Wait for VS Code to be ready
29+
await window.waitForLoadState('domcontentloaded');
30+
await window.waitForLoadState('networkidle');
31+
32+
// ➌ Trigger our command exactly like a user would
33+
await window.keyboard.press(process.platform === 'darwin' ? 'Meta+Shift+P' : 'Control+Shift+P');
34+
await window.keyboard.type('Lineage: Focus On View');
35+
await window.keyboard.press('Enter');
36+
37+
await new Promise(resolve => setTimeout(resolve, 5000));
38+
39+
// Wait for "Loaded SQLmesh Context" text to appear
40+
const loadedContextText = window.locator('text=Loaded SQLmesh Context');
41+
try {
42+
await loadedContextText.waitFor({ timeout: 10000 });
43+
} catch (error) {
44+
throw new Error('Failed to find "Loaded SQLmesh Context" text within 5 seconds');
45+
}
46+
47+
await electronApp.close();
48+
});

0 commit comments

Comments
 (0)