JupyterLab集成测试指南:前端与后端交互场景模拟方法
测试框架核心组件与架构
JupyterLab的集成测试基于Galata框架构建,该框架提供了完整的前端与后端交互模拟能力。核心测试工具位于galata/src/helpers/目录下,主要包含以下关键类:
- JupyterLabPage:提供页面级操作封装,定义在galata/src/jupyterlabpage.ts中,负责测试环境的初始化与页面生命周期管理
- NotebookHelper:处理 notebook 相关操作,实现在galata/src/helpers/notebook.ts,支持单元格操作、运行控制和结果验证
- KernelHelper:管理内核生命周期,用于模拟后端计算环境
- FileBrowserHelper:文件系统交互工具,支持测试文件的创建、读取和删除
测试架构采用分层设计,通过Playwright实现浏览器自动化,结合JupyterLab内部API实现状态管理与交互模拟。测试配置定义在galata/playwright.config.js,可通过环境变量TARGET_URL指定测试目标地址,默认使用http://localhost:8888。
测试环境搭建与初始化
基础环境配置
- 安装依赖:
git clone https://gitcode.com/gh_mirrors/ju/jupyterlab
cd jupyterlab
pip install -e .[test]
jlpm install
- 启动测试服务器:
jlpm run start:test
测试用例编写规范
测试文件应放置在examples/目录下,推荐使用test_*.ipynb命名格式。以下是一个基础测试用例结构:
import { test, expect } from '@jupyterlab/galata';
test.describe('Notebook Execution Test', () => {
test.beforeEach(async ({ page }) => {
// 初始化测试环境
await page.goto('/lab');
await page.waitForSelector('#jupyterlab-splash', { state: 'detached' });
});
test('Execute sample notebook', async ({ page, notebook }) => {
// 创建新notebook
await notebook.createNew('test.ipynb');
// 添加并运行代码单元格
await notebook.setCell(0, 'code', 'print("Hello, World!")');
await notebook.runCell(0);
// 验证执行结果
expect(await notebook.getCellOutputText(0)).toContain('Hello, World!');
});
});
前端与后端交互模拟关键技术
内核状态管理
内核是前后端交互的核心枢纽,Galata通过galata/src/helpers/kernel.ts提供内核生命周期管理:
// 启动内核并等待就绪
await kernel.startKernel('python3');
await kernel.waitForReady();
// 模拟内核崩溃并恢复
await kernel.simulateCrash();
expect(await kernel.isRestarted()).toBe(true);
// 关闭内核
await kernel.shutdown();
异步操作处理
测试框架提供多种等待机制处理前后端异步交互:
- 状态等待:
// 等待内核空闲
await page.waitForSelector('[data-status="idle"]');
// 自定义条件等待
await notebook.waitForCondition(async () => {
return (await notebook.getCellCount()) > 0;
});
- 执行等待:
// 运行所有单元格并等待完成
await notebook.run();
await notebook.waitForRun();
配置与状态模拟
通过galata/src/fixtures.ts中定义的测试夹具,可以灵活配置测试环境:
test.use({
// 模拟配置
mockConfig: {
'notebook': {
'recordTiming': true
}
},
// 模拟用户状态
mockState: {
'ui': {
'theme': 'JupyterLab Dark'
}
},
// 自动清理测试文件
serverFiles: 'off'
});
典型交互场景测试实现
场景一:Notebook单元格执行流程
以下测试用例验证代码单元格从输入到输出的完整流程:
test('Cell execution workflow', async ({ page, notebook, contents }) => {
// 创建测试notebook
await contents.createFile('test.ipynb', {
cells: [
{ cell_type: 'code', source: '1 + 1', execution_count: null }
],
metadata: { kernelspec: { name: 'python3' } },
nbformat: 4,
nbformat_minor: 5
});
// 打开notebook
await notebook.open('test.ipynb');
expect(await notebook.isActive('test.ipynb')).toBe(true);
// 运行单元格并验证结果
await notebook.runCell(0);
const output = await notebook.getCellTextOutput(0);
expect(output).toContain('2');
// 验证执行计数
const count = await notebook.getCellExecutionCount(0);
expect(count).toBe(1);
});
场景二:文件系统与内核交互
该场景测试文件保存与内核状态的一致性:
test('File save and kernel state', async ({ page, notebook, kernel, contents }) => {
// 创建带状态的notebook
await notebook.openByPath('examples/notebook/test.ipynb');
// 定义变量并保存
await notebook.setCell(0, 'code', 'x = 100');
await notebook.runCell(0);
await notebook.save();
// 关闭并重新打开
await notebook.close();
await notebook.open('test.ipynb');
// 验证变量状态
await notebook.setCell(1, 'code', 'print(x)');
await notebook.runCell(1);
expect(await notebook.getCellOutputText(1)).toContain('100');
});
场景三:并发编辑冲突处理
测试多用户并发编辑场景下的冲突解决机制:
test('Concurrent edit conflict resolution', async ({ page, context }) => {
// 创建两个测试页面模拟不同用户
const page2 = await context.newPage();
await page2.goto('/lab');
// 用户1打开notebook
const notebook1 = new NotebookHelper(page);
await notebook1.open('shared.ipynb');
// 用户2打开同一notebook
const notebook2 = new NotebookHelper(page2);
await notebook2.open('shared.ipynb');
// 用户1编辑单元格
await notebook1.setCellText(0, 'user1 = "alice"');
await notebook1.runCell(0);
// 用户2编辑同一单元格
await notebook2.setCellText(0, 'user2 = "bob"');
await notebook2.runCell(0);
// 验证冲突解决
expect(await notebook1.getCellTextInput(0)).toContain('user2 = "bob"');
expect(await notebook2.getCellTextInput(0)).toContain('user2 = "bob"');
});
测试结果验证与报告生成
断言方法
框架提供丰富的验证手段:
- 视觉断言:
// 单元格输出截图验证
await expect(await notebook.getCellOutputLocator(0)).toHaveScreenshot('cell-output.png');
- 性能断言:
// 执行时间验证
const metrics = await notebook.getCellPerformanceMetrics(0);
expect(metrics.executionTime).toBeLessThan(100); // 单位:毫秒
测试报告
执行测试后生成详细报告:
jlpm run test:galata --reporter=html
报告文件生成在galata/test-results/目录,包含:
- 测试执行摘要
- 失败用例截图
- 性能指标图表
- 交互时序记录
最佳实践与常见问题
测试效率优化
- 测试隔离:每个测试用例应使用独立的临时目录,通过
tmpPath配置实现:
test.use({
tmpPath: 'test-specific-folder'
});
- 并行执行:在galata/playwright.config.js中配置:
module.exports = {
workers: process.env.CI ? 1 : '50%',
fullyParallel: true
};
常见问题解决方案
- 内核启动超时:
test.use({
waitForApplication: async (page, helpers) => {
await page.waitForTimeout(5000); // 延长超时时间
await helpers.waitForCondition(() => helpers.kernel.isReady());
}
});
- 元素定位不稳定:
// 使用稳定选择器
const runButton = page.locator('[data-command="notebook:run-all"]');
await runButton.click({ timeout: 10000 });
- 资源竞争冲突:
test.beforeEach(async ({ contents }) => {
// 确保测试环境干净
if (await contents.fileExists('conflict.ipynb')) {
await contents.deleteFile('conflict.ipynb');
}
});
扩展测试能力
自定义测试辅助类
可以通过继承基础辅助类扩展测试能力:
class AdvancedNotebookHelper extends NotebookHelper {
async runWithTiming(cellIndex: number): Promise<{result: string, time: number}> {
const start = Date.now();
await this.runCell(cellIndex);
const time = Date.now() - start;
const result = await this.getCellTextOutput(cellIndex);
return { result, time };
}
}
集成CI/CD流程
在.github/workflows/目录添加测试工作流配置,实现自动化测试:
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup environment
run: |
pip install -e .[test]
jlpm install
- name: Run tests
run: jlpm run test:galata
通过本文档介绍的方法,开发人员可以构建全面的集成测试套件,有效验证JupyterLab前端与后端的交互场景,确保系统在各种使用条件下的稳定性和可靠性。更多示例可参考examples/目录下的测试用例,包括examples/notebook/test.ipynb等实际测试文件。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



