Open dlrandy opened 6 years ago
在electron里测试Rerender Process,可以在Electron之外测试 也就是测试web 项目那样
测试Main Process则需要electron里测试 使用spectron它是test Runner 还需要Mocha(测试框架)和chai(验证库)
spectron 的describe不要使用箭头函数,若使用了,this.timeout(10000)这句会有问题的
测试的时候 不要打开devTools https://github.com/electron/spectron/issues/174
const electron = require('electron');
const Application = require('spectron').Application;
const expect = require('chai').expect;
describe('SPECTRON Example', function(){
this.timeout(10000)
global.app = null;
before(() => {
app = new Application({
path: electron,
args: ['.'],
})
return app.start()
.then(() =>{
app.client.waitUntilWindowLoaded()
app.browserWindow.show()
return app;
})
});
after(() => {
if (app && app.isRunning()) {
return app.stop()
}
});
it('should open a window', () => {
return app.client.waitUntilWindowLoaded()
.getWindowCount()
.then(count => {
expect(count).to.equal(1)
})
});
it('should open a working window', () => {
return app.client.waitUntilWindowLoaded()
.browserWindow.isVisible()
.then(res => {
expect(res).to.be.true
})
.browserWindow.isFocused()
.then(res => {
expect(res).to.be.true
})
.browserWindow.isMinimized()
.then(res => {
expect(res).to.be.false
})
.browserWindow.isDevToolsOpened()
.then(res => {
expect(res).to.be.false
})
});
it('should open a window to correct size', () => {
return app.client.waitUntilWindowLoaded()
.browserWindow.getBounds()
.then(res => {
expect(res.width).to.equal(800)
expect(res.height).to.equal(600)
})
});
it('should call foobar', () => {
return app.client.waitUntilWindowLoaded()
.then(() => {
return app.client.getText('#foobarBtn')
})
.then(text => {
expect(text).to.equal('get foo bar')
return app.client.click('#foobarBtn')
})
.then(() => {
return app.client.getText('#foobarBtn')
})
.then(text => {
expect(text).to.equal('hello!')
})
});
});
build 或者发布electron app要使用Electron-Builder 它的作用: 1构建打包app(mac Windows linux) 2、支持app的code sign 3、可以生成自动更新的app
npm install electron-builder --save-dev 按步骤顺序: 他对目录有严格要求: build是用来查找build资源的; dist目标App目录; app【optioal】存放在的app文件,main.js, renderer.js index.html
修改package.JSON scripts: "dist": "build -mwl --x64 --ia32"
MAC build windows: brew install wine --without-x11 //linux上的win实现 brew install mono //.NET frameWork 的实现
Mac build Linux brew install gnu-tar graphicsmagick xz brew install rpm
//如果上面都安装成功使用下面的还行,否则就在特定的平台上执行build吧 "build" : { "appId": "cc.gochen.desktop", "copyright": "Copyright @ 2017 ", "productName": "My Electron App", "electronVersion": "1.7.8", "mac": { "category": "public.app-category.developer-tools" }, "win": { "target": [ "nsis" ] }, "linux": { "target": [ "AppImage", "deb" ] } },
上面的配置会有icon警告,electron-builder在build的时候回去build目录找资源的 Mac:icon.icns; Window: icon.ico; Linux: icon.icns; 2的(4--9) 可以使用 electron-icon-maker ,源图片可以放在新建的目录build-assets里 然后build到build目录 electron-icon-maker -i build-assets/icon.png -o build/ 这里要把生成的ico文件放到build下
配置MacOS DMG Electron-builder可以使用background.png/tif "dmg": { "title": "ConfiGAP Installer", "background": "./build/background.png", "icon": "./build/installer.icns", "iconSize": 128, "contents": [ { "x": 388, "y": 160, "type": "link", "path": "/Applications" }, { "x": 128, "y": 160, "type": "file", "path": "" } ] } "nsis" : { "oneClick": true, "artifactName": "Electron Builder Setup.exe", "installerIcon": "./build/installerIcon.ico", "installerHeaderIcon": "./build/installerHeaderIcon.ico" }
electron forge
自动更新包括三个任务: app的base code;build过程的修改;app的正常发布。
Electron的auto-updater module是Squirrel(github)的接口,Squirrel可以检查新版本,下载并执行实际的升级。不能用在Linux上
MacOS 上的自动更新 首先要在Main Process autoUpdater =electron.autoUpdater This module broadcasts four events: • checking-for-update • update-available • update-not-available • error 这个是自动更新的生命周期
autoUpdater.checkForUpdates()如果接受的response是204就认为没有更新,若是200,并带有一个url属性的JSON,那么就知道是需要更新的了
可以在生命周期的函数里给出反馈
可用的Auto Update Server: nuts;electron-release-server;Squirrel-updates/repease-server 当然可以自己设置server
'use strict'
const fs = require('fs')
const express = require('express')
const path = require('path')
const app = express()
app.get('/updates/latest', function (req, res) {
const latest = getLatestRelease()
const clientVersion = req.query.v
if (clientVersion === latest) {
res.status(204).end()
} else {
let baseURL = getBaseUrl()
let updateURL = baseURL + '/releases/darwin/' + latest + '/electron.zip'
res.json({
url: updateURL,
name: "My Release Name",
notes: "These are some release notes",
pub_date: "2017-04-18T12:29:53+01:00"
}) }
})
let getLatestRelease = () => {
const dir = __dirname + '/releases/darwin'
const versionsDesc = fs.readdirSync(dir).filter((file) => {
const filePath = path.join(dir, file)
return fs.statSync(filePath).isDirectory()
}).reverse()
return versionsDesc[0];
}
let getBaseUrl = () => {
if (process.env.NODE_ENV === 'development') {
return 'http://localhost:3000'
} else {
return 'http://your-company.com'
}
}
app.listen(process.env.PORT || 3000, () => {
console.log(`Express server listening on port ${process.env.PORT || 3000}`)
});
签名App 要想AutoUpdate正常工作,需要sign app 会用Keychain Access工具生成证书: Keychain Access ➤ Certificate Assistant ➤ Create a Certificate... We need to now set the CSC_NAME environment variable, so the signing can occur when we build our application. In your terminal and in our application’s active directory, run export CSC_NAME="Certificate Name" https://github.com/electron/electron/blob/master/docs/tutorial/mac-app-store-submission-guide.md
"dist": "build -m"
生成一个更新需要三步: 修改package.json的version,并rebuild app; 新的app上传到server的某个位置; 更新server的url;
Windows自动更新依赖delta package和特殊的RELEASEs 文件
npm install electron-builder-squirrel-windows --save-dev 如果目标是32位的PC,继续使用nsis,否则使用squirrel dist": "build -w --x64"
• Electron Demo Setup 1.0.1.exe、发布的安装包 • autoupdatedemo-1.0.1-full.nupkg app的源码 • RELEASES 文本文件有一些app version, checksums
"build": {
"appId": "com.ajsoftware.electronapp",
"copyright": "Copyright © 2017 Chris Griffith",
"productName": "Electron Demo",
"electronVersion": "1.4.1",
"win": {
"target": [
"squirrel"
] ,
"certificateFile": "./certificate.pfx",
"certificatePassword": "your-password",
"icon": "./build/icon.ico",
"squirrelWindows": {
"iconUrl": "http://your-company.com/releases/win/icon.ico",
"loadingGif": "./build/loader.gif",
"useAppIdAsId": false
Squirrel安装app时候会把安装包放在AppData目录,会基于App ID或者name属性创建一个新目录,AppID会被截取(com.company-name.app-name --》 name),所以使用name
} } 签名Window app openssl genrsa -aes128 -out privateKey.key 2048 openssl req -new -x509 -days 365 -key privateKey.key -out certificate.crt
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
if (process.platform === 'darwin') { autoUpdater.setFeedURL(updateUrl) autoUpdater.checkForUpdates() } else { updateUrl = "http://your-company.com/releases/win/" autoUpdater.setFeedURL(updateUrl) autoUpdater.checkForUpdates() }
const electron = require("electron");
// Module to control application life.
const app = electron.app;
const dialog = electron.dialog;
const autoUpdater = electron.autoUpdater;
autoUpdater.addListener("update-available", function(event) {
console.log("UPdate Available");
dialog.showMessageBox(
{
type: "info",
title: "Update Available",
message: "There is an updated version available",
buttons: ["Update", "Skip"]
},
function(index) {
console.log("index ", index);
}
);
});
autoUpdater.addListener("update-downloaded", function(
event,
releaseNotes,
releaseName,
releaseDate,
updateURL
) {
console.log("Update Downloaded");
console.log("releaseNotes", releaseNotes);
console.log("releaseNotes", releaseName);
console.log("releaseNotes", releaseDate);
console.log("releaseNotes", updateURL);
dialog.showMessageBox(
{
type: "info",
title: "Update Downloaded",
message: "Update has downloaded",
detail: releaseNotes,
buttons: ["Install", "Skip"]
},
function(index) {
if (index === 0) {
autoUpdater.quitAndInstall();
} else {
autoUpdater.quit();
}
}
);
});
autoUpdater.addListener("error", function(error) {
console.log("Error");
console.log(error);
dialog.showMessageBox({
type: "warning",
title: "Update Error",
message: "An error occurred. " + error,
buttons: ["OK"]
});
});
autoUpdater.addListener("checking-for-update", function(event) {
console.log("releaseNotes", "Checking for Update");
});
autoUpdater.addListener("update-not-available", function(event) {
console.log("releaseNotes", "Update Not Available");
dialog.showMessageBox({
type: "warning",
title: "No Updates",
message: "No update available at this time.",
buttons: ["OK"]
});
});
let appVersion = app.getVersion();
let updateUrl =
"https://apress-electron-manager.herokuapp.com/updates/latest?v=" +
appVersion;
if (process.platform === "darwin") {
autoUpdater.setFeedURL(updateUrl);
autoUpdater.checkForUpdates();
}
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;
const path = require("path");
const url = require("url");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({ width: 800, height: 600 });
// and load the index.html of the app.
mainWindow.loadURL(
url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true
})
);
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on("closed", function() {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
// Quit when all windows are closed.
app.on("window-all-closed", function() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", function() {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
if (handleSquirrelEvent()) {
// squirrel event handled and app will exit in 1000ms, so don't do anything else
return;
}
function handleSquirrelEvent() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require("child_process");
const path = require("path");
const appFolder = path.resolve(process.execPath, "..");
const rootAtomFolder = path.resolve(appFolder, "..");
const updateDotExe = path.resolve(path.join(rootAtomFolder, "Update.exe"));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, { detached: true });
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case "--squirrel-install":
case "--squirrel-updated":
// Optionally do things such as:
// - Add your .exe to the PATH
// - Write to the registry for things like file associations and
// explorer context menus
// Install desktop and start menu shortcuts
spawnUpdate(["--createShortcut", exeName]);
setTimeout(app.quit, 1000);
return true;
case "--squirrel-uninstall":
// Undo anything you did in the --squirrel-install and
// --squirrel-updated handlers
// Remove desktop and start menu shortcuts
spawnUpdate(["--removeShortcut", exeName]);
setTimeout(app.quit, 1000);
return true;
case "--squirrel-obsolete":
// This is called on the outgoing version of your app before
// we update to the new version - it's the opposite of
// --squirrel-updated
app.quit();
return true;
}
}
electron-builder并不是唯一的build工具: Windows installer module(https://github.com/electron/windows-installer); Electron Forge (https://beta.electronforge.io/);
debug electron app Chromium dev tool for Renderer Process; MenuItem 的click函数的参数(item, focusedWindow) 在VSCode里调试Main Process; 根目录下创建目录.vscode 加入launch.json,内容是 { "version": "0.2.0", "configurations": [ { "name": "Debug Electron Main Process", "type": "node", "request": "launch", "cwd": "${workspaceRoot}", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", "program": "${workspaceRoot}/main.js" } ] } debugmenu里add configuration Menu要在app ready的时候加入
也可以是用node-inspector调试mainProcess npm install node-inspector --save-dev npm install node-pre-gyp electron --debug=5858 ELECTRON_RUN_AS_NODE=true path/to/electron.app|exe node_modules/node-inspector/bin/ inspector.js
http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 还可以使用Devtron npm install devtron --save-dev