Skip to content

Commit 73086f1

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

8 files changed

Lines changed: 206 additions & 27 deletions

File tree

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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,15 @@
8080
]
8181
},
8282
"scripts": {
83-
"ci": "pnpm run lint && pnpm run compile && pnpm run test",
83+
"ci": "pnpm run lint && pnpm run compile",
8484
"compile-tests": "tsc -p . --outDir out",
8585
"watch-tests": "tsc -p . -w --outDir out",
86-
"pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint",
8786
"lint": "eslint src",
8887
"lint:fix": "eslint src --fix",
89-
"test": "vscode-test",
88+
"test:e2e": "playwright test",
89+
"test:e2e:ui": "playwright test --ui",
90+
"test:e2e:headed": "playwright test --headed",
91+
"fetch-vscode": "tsx scripts/fetch-vscode.ts",
9092
"compile": "pnpm run check-types && node esbuild.js",
9193
"check-types": "tsc --noEmit",
9294
"watch": "node esbuild.js --watch",
@@ -105,6 +107,7 @@
105107
},
106108
"devDependencies": {
107109
"@eslint/js": "^9.25.1",
110+
"@playwright/test": "^1.48.2",
108111
"@types/mocha": "^10.0.10",
109112
"@types/node": "20.11.25",
110113
"@types/vscode": "1.96.0",
@@ -114,6 +117,7 @@
114117
"esbuild": "^0.25.2",
115118
"eslint": "^9.23.0",
116119
"ts-loader": "^9.5.2",
120+
"tsx": "^4.19.2",
117121
"typescript": "^5.8.2",
118122
"typescript-eslint": "^8.31.1"
119123
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { defineConfig } from '@playwright/test'
2+
3+
export default defineConfig({
4+
testDir: 'tests',
5+
timeout: 60_000,
6+
retries: process.env.CI ? 1 : 0,
7+
8+
projects: [
9+
{
10+
name: 'electron-vscode',
11+
use: {
12+
// ⭢ we'll launch Electron ourselves – no browser needed
13+
browserName: 'chromium',
14+
headless: false, // headed makes screenshots deterministic
15+
launchOptions: {
16+
slowMo: process.env.CI ? 0 : 100,
17+
},
18+
},
19+
},
20+
],
21+
})
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
;(async () => {
8+
const vscPath = await downloadAndUnzipVSCode('stable') // one-time-only
9+
console.log('VS Code downloaded to:', vscPath)
10+
11+
const cliPath = resolveCliPathFromVSCodeExecutablePath(vscPath) // optional
12+
console.log('CLI path:', cliPath)
13+
14+
// Save paths to a JSON file for Playwright to use
15+
const pathsFile = path.join('.vscode-test', 'paths.json')
16+
await fs.ensureDir(path.dirname(pathsFile))
17+
await fs.writeJson(
18+
pathsFile,
19+
{
20+
executablePath: vscPath,
21+
cliPath: cliPath,
22+
},
23+
{ spaces: 2 },
24+
)
25+
26+
console.log('Paths saved to:', pathsFile)
27+
})()

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

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { test, _electron as electron, expect } 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+
const ciArgs = process.env.CI ? [
14+
'--disable-gpu',
15+
'--headless',
16+
'--no-sandbox',
17+
'--disable-dev-shm-usage', // Prevents memory issues in Docker/CI
18+
'--window-position=-10000,0', // Ensures window is off-screen
19+
] : [];
20+
const args = [
21+
...ciArgs,
22+
`--extensionDevelopmentPath=${EXT_PATH}`,
23+
'--disable-workspace-trust', // no modal prompt
24+
'--disable-telemetry',
25+
'--user-data-dir=/tmp/vscode-test', // throwaway profile
26+
`${PROJECT_PATH}`,
27+
];
28+
const electronApp = await electron.launch({
29+
executablePath: VS_CODE_EXE,
30+
args,
31+
});
32+
33+
// ➋ Grab the first window that appears (the Workbench)
34+
const window = await electronApp.firstWindow();
35+
36+
// Wait for VS Code to be ready
37+
await window.waitForLoadState('domcontentloaded');
38+
await window.waitForLoadState('networkidle');
39+
40+
await expect(window.locator("text=lineage").first()).toBeVisible();
41+
42+
// ➌ Trigger our command exactly like a user would
43+
await window.keyboard.press(process.platform === 'darwin' ? 'Meta+Shift+P' : 'Control+Shift+P');
44+
await window.keyboard.type('Lineage: Focus On View');
45+
await window.keyboard.press('Enter');
46+
47+
// Wait for "Loaded SQLmesh Context" text to appear
48+
const loadedContextText = window.locator('text=Loaded SQLmesh Context');
49+
await expect(loadedContextText).toBeVisible({ timeout: 10000 });
50+
51+
await electronApp.close();
52+
});

0 commit comments

Comments
 (0)