Electron Test
在此测试框架中,主要测试重点是:测试套件公开自定义方法。这有如下几种情况:
- 主进程中的功能函数
- 渲染进程中的功能函数
- Webview 中的功能函数
- 不同进程之间的功能函数协作流程
- 测试多应用同时启动的场景
测试用例编写基本流程
测试用例组织
我们会把所有的测试用例,统一写在:electron/tests
中。
我们会新建一个 electron/tests/index.test.ts
:
/**
* @jest-environment @modern-js/electron-test/dist/js/node/testEnvironment.js
*/
jest.setTimeout(100000);
我们可以通过这样注释的形式添加一个默认的测试环境,则会为我们启动一个 Electron 实例来进行测试。
警告
一般,我们在electron/tests
目录下,仅会有一个 *.test.ts
的文件,并在其中加入注释,注入环境。这样,跑我们所有测试用例的时候,只会开启一个 Electron 实例。若要开启多个实例,可看下方相关示例。
测试主进程功能函数
注册需要测试的主进程函数。
首先,我们先注册需要测试的主进程函数
testMainFunc
:electron/main.tsimport { testServices } from '@modern-js/electron-test/main';
const services = {
testMainFunc: (data: string) => data
}
testServices({
...services,
winService,
updateService,
lifecycleService
});
const runtime = new Runtime({
windowsConfig,
mainServices: services,
});补充信息
我们除了将用户自定义的服务,通过
testServices
注册以外,我们还注册了框架的一些服务,比如:winService
。这样在我们的测试用例里也可以使用这些基础服务。测试主进程
testMainFunc
函数。electron/tests/main-process/index.ts// test main services
import TestDriver from '@modern-js/electron-test';
let testDriver: TestDriver | null = null;
jest.setTimeout(100000);
beforeAll(async () => {
testDriver = (global as any).testDriver;
// 当主进程 Ready 之后。
await testDriver?.whenReady();
});
describe('test main process services', () => {
it('test testMainFunc', async () => {
const data = 'test data';
// 访问主进程服务函数:testMainFunc
const result = await testDriver?.call({
funcName: 'testMainFunc',
args: [data]
})
expect(result).toEqual(data);
})
})
测试渲染进程功能函数
注册渲染进程所需函数
testRenderFunc
。electron/preload/browserWindow/index.tsimport {
exposeInMainWorld,
browserWindowPreloadApis,
} from '@modern-js/runtime/electron-render';
import { testServices } from '@modern-js/electron-test/render';
const { callMain } = browserWindowPreloadApis;
export const apis = testServices({
...browserWindowPreloadApis,
testRenderFunc: (data: string) => data,
openWindow: (winName: string) => {
return callMain('openWindow', winName);
}
});
exposeInMainWorld(apis);测试窗口中注册的函数
testRenderFunc
。electron/tests/render-process/index.ts// test render services
import TestDriver from '@modern-js/electron-test';
let testDriver: TestDriver | null = null;
jest.setTimeout(100000);
beforeAll(async () => {
testDriver = (global as any).testDriver;
// 当窗口 main 加载完毕
await testDriver?.whenReady('main');
});
describe('test render process services', () => {
it('test render func', async () => {
const data = 'test render func';
// 测试窗口中注册的函数 testRenderFunc
const result = await testDriver?.call({
funcName: 'testRenderFunc',
winName: 'main',
args: [data]
})
expect(result).toEqual(data);
})
})
测试 webview 功能函数
新建 webview 的预加载脚本,注册
testWebviewFunc
函数。electron/preload/webview/index.tsimport {
webviewPreloadApis,
exposeInMainWorld,
} from '@modern-js/runtime/electron-webview';
import { testServices } from '@modern-js/electron-test/webview';
export const apis = testServices({
...webviewPreloadApis,
testWebviewFunc: (data: string) => data
});
exposeInMainWorld(apis);electron/preload/webview/index.dev.jsconst { join } = require('path');
const babel = require('@babel/register');
const { babelConfig } = require('@modern-js/plugin-electron/tools');
babel(
Object.assign(babelConfig, {
extensions: ['.ts', '.js'],
}),
);
require(join(__dirname, 'index.ts'));在页面中,增加
webview
,并设置预加载脚本路径。首先,渲染进程预加载脚本中注册一个获得路径的方法。
electron/browserWindow/index.ts...
export const apis = testServices({
...
getWebviewPreloadJs: () =>
join(
'file://',
__dirname,
'..',
'webview',
process.env.NODE_ENV === 'development' ? 'index.dev.js' : 'index.js',
),
});其次,添加
webview
组件,加上预加载脚本路径。xx/xx.tsx(渲染进程)const App: React.FC = () => (
<div>
...
<webview
src={'https://www.baidu.com'}
id="webview1"
// @ts-expect-error
nodeintegration="true"
preload={bridge.getWebviewPreloadJs()}
style={{
width: '100%',
height: 300,
}}></webview>
</div>
);编写测试用例。
import TestDriver from '@modern-js/electron-test';
let testDriver: TestDriver | null = null;
const webviewId1 = 'webview1';
beforeAll(() => {
testDriver = new TestDriver({
cwd: process.cwd(),
envs: {
TEST_FOLDER: 'webviewBridge',
},
});
return Promise.all(
[webviewId1].map(async x => {
await testDriver?.whenReady('main', {
webviewId: x,
});
await testDriver?.call({
funcName: 'webviewService.addWebview',
winName: 'main',
args: [x, true],
});
}),
);
});
describe('test webview bridge', () => {
it('test webview registerServices again', async () => {
const msg1 = 'this is msg from webview';
const result = await testDriver?.call({
funcName: 'webviewService.callWebview',
winName: 'main',
args: [webviewId1, 'testWebviewFunc', msg1],
});
expect(result).toBe(msg1);
});
});补充信息
对于 webview 中的服务函数测试较为复杂,一般流程如下:
在
beforeAll
中- 等待
webview
加载完毕。
await testDriver?.whenReady('main', {
webviewId: x,
});- 将
webview
加入webviewService
的管理。(webviewService.addWebview
)。
await testDriver?.call({
funcName: 'webviewService.addWebview',
winName: 'main',
args: [x, true],
});- 等待
在测试用例中,我们通过渲染进程中的
webviewService.callWebview
来调用webview
中注册的服务。const result = await testDriver?.call({
funcName: 'webviewService.callWebview',
winName: 'main',
args: [webviewId1, 'testWebviewFunc', msg1],
});
测试多个实例
我们可以这样,新建一个实例:
const winName = 'main';
let myDriver: TestDriver | null;
beforeAll(async () => {
myDriver = new TestDriver({
cwd: process.cwd(),
envs: {
TEST_FOLDER: 2,
},
});
await myDriver?.whenReady(winName);
});
接着,我们来测试此实例关闭的表现:
it(`will quit if set forceQuit=true`, async () => {
await myDriver?.call({
funcName: 'lifecycleService.quit',
args: [
{
forceQuit: true,
},
],
});
return sleep(1).then(() => {
expect(myDriver?.isRunning()).toBe(false);
});
});
补充信息
运行测试
我们将所有测试用例从 index.test.ts
中引入:
/**
* @jest-environment @modern-js/electron-test/dist/js/node/testEnvironment.js
*/
import './main-process';
import './render-process';
import './webview-process';
jest.setTimeout(100000);
接着,我们直接跑:pnpm run test
即可运行测试用例。