We're developing a VSCode extension that works with Jupyter notebooks and need to run integration tests in CI/CD. The main issue is that when running integration tests, VSCode prompts the user to manually select a Jupyter kernel, which breaks automation.
The current setup is a custom extension that integrates with Jupyter notebooks and uses 'vscode/test-electron' for integration testing.
When running the current integration tests:
- VSCode opens a notebook file
- Try to execute a cell
- Test hangs waiting for manual kernel selection in the UI. In CI/CD, this causes tests to timeout. If the user does click on the test kernel the test succeed.
Here is the current setup to try to programmatically select a default kernel to execute a jupyter notebook cell:
Notebook Metadata Injection
We inject the kernel specification directly into the notebook file:
{
"metadata": {
"kernelspec": {
"name": "test-kernel",
"display_name": "Python (Test Environment)",
"language": "python"
}
}
}
VSCode Settings Configuration
We create workspace-specific settings to configure the Jupyter extension:
{
"python.defaultInterpreterPath": "/path/to/test-venv/bin/python",
"jupyter.jupyterServerType": "local",
"jupyter.kernels.default": "test-kernel",
"jupyter.alwaysTrustNotebooks": true
}
Current Test Code
test('Execute notebook cell', async function () {
this.timeout(10000);
const nbWsPath = vscode.workspace.workspaceFolders![0].uri.fsPath;
const nbPath = path.join(nbWsPath, 'simple_notebook.ipynb');
// Open notebook
const nbDoc = await vscode.workspace.openNotebookDocument(vscode.Uri.file(nbPath));
const editor = await vscode.window.showNotebookDocument(nbDoc);
// Wait for notebook to initialize
await new Promise(resolve => setTimeout(resolve, 3000));
// Execute our extension functionality that triggers kernel selection prompt
await executeCell(editor, 0);
// Wait for execution to complete
await waitForCellExecution(editor, 0);
// Verify execution
const cell = editor.notebook.cellAt(0);
assert.ok(cell.executionSummary, 'Cell should have execution summary');
assert.ok(cell.executionSummary.executionOrder, 'Cell should have execution order');
});
Test Setup Code
Here's a simplified version of how we set up the test environment:
// src/test/runTest.ts (simplified)
async function main(): Promise<void> {
const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
const fixturesPath = path.resolve(extensionDevelopmentPath, 'src/test/fixtures/workspaces');
const userDataDir = path.resolve(fixturesPath, 'userdata');
// Download and setup VSCode
const vscodeExecutablePath = await downloadAndUnzipVSCode('stable');
const cli = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
// Install required extensions
spawnSync(cli[0], [...cli.slice(1), '--install-extension', 'ms-toolsai.jupyter'], { stdio: 'inherit' });
spawnSync(cli[0], [...cli.slice(1), '--install-extension', 'ms-python.python'], { stdio: 'inherit' });
// Create test virtual environment and register kernel
const venvDir = path.join(os.tmpdir(), 'test-venv');
const systemPython = 'python3';
if (!fs.existsSync(venvDir)) {
spawnSync(systemPython, ['-m', 'venv', venvDir], { stdio: 'inherit' });
}
const venvPython = path.join(venvDir, 'bin', 'python');
spawnSync(venvPython, ['-m', 'pip', 'install', 'ipykernel'], { stdio: 'inherit' });
spawnSync(venvPython, ['-m', 'ipykernel', 'install', '--sys-prefix', '--name', 'test-kernel', '--display-name', 'Python (Test Environment)'], { stdio: 'inherit' });
// Setup workspace with settings
const workspaceFolder = path.join(fixturesPath, 'simple_cell_execution');
const wsVscodeDir = path.join(workspaceFolder, '.vscode');
const wsSettingsPath = path.join(wsVscodeDir, 'settings.json');
if (!fs.existsSync(wsVscodeDir)) fs.mkdirSync(wsVscodeDir, { recursive: true });
const settings = {
"python.defaultInterpreterPath": venvPython,
"jupyter.jupyterServerType": "local",
"jupyter.kernels.default": "test-kernel",
"jupyter.alwaysTrustNotebooks": true
};
fs.writeFileSync(wsSettingsPath, JSON.stringify(settings, null, 2));
// Run tests
await runTests({
vscodeExecutablePath,
extensionDevelopmentPath,
extensionTestsPath: path.resolve(__dirname, './suite/index'),
launchArgs: [
`--user-data-dir=${userDataDir}`,
`--enable-proposed-api=ms-toolsai.jupyter`,
`--enable-proposed-api=ms-python.python`,
workspaceFolder
],
});
}
Key Files
- Test Runner:
src/test/runTest.ts - Integration Test:
src/test/integration/simple_cell_execution.integration.test.ts - Notebook:
src/test/fixtures/workspaces/simple_cell_execution/simple_notebook.ipynb - Settings:
src/test/fixtures/workspaces/simple_cell_execution/.vscode/settings.json
So my questions are:
- why isn't the jupyter notebook directly using the default kernel like it does in my personnal instance of vscode (compare to the integration test context)
- Is there another way to select the jupyter notebook kernel programmatically ?