Spectron test leaves window open - mocha.js

I am using just the basic Spectron test file (in Typescript) to open my app, get the window count, and presumably exit. However, Spectron's app.stop() only seems to close the dev tools window and leaves the main window running. I've searched around and come across a few GitHub issues with people having this problem. The best people seem to offer is to use pkill. I don't want to do that as it could potentially kill more than it should (on a CI server, for example).
Before I show all the code, my question is, what do I need to do to make Spectron's session actually exit after a test?
Here's my spec.ts containing my tests:
import { Application } from "spectron";
import * as assert from "assert";
import * as electronPath from "electron";
import * as path from "path";
describe('Application launch', function () {
this.timeout(10000);
beforeEach(function () {
this.app = new Application({
path: electronPath,
args: [path.join(__dirname, '..')]
} as any);
return this.app.start();
})
afterEach(function () {
if (this.app && this.app.isRunning()) {
// TODO: figure out way to close all windows
return this.app.electron.app.quit();
}
});
it('shows an initial window', function () {
return this.app.client.getWindowCount().then(function (count: any) {
//assert.equal(count, 1)
// Please note that getWindowCount() will return 2 if `dev tools` are opened.
assert.equal(count, 2);
});
});
});
Here's my package.json:
{
"main": "dist/js/entry/main.js",
"scripts": {
"build": "node_modules/.bin/tsc -p tsconfig.json && mkdir -p dist/static && rsync -ar --delete static/ dist/static/",
"lint": "node_modules/.bin/tslint -c tslint.json -p tsconfig.json",
"start": "node_modules/.bin/electron .",
"build_start": "npm run build && npm start",
"package": "node_modules/.bin/electron-builder",
"package-test": "node_modules/.bin/electron-builder --dir",
"test": "node_modules/.bin/mocha -r ts-node/register -r ignore-styles -r jsdom-global/register test/*.ts"
},
"devDependencies": {
"#types/chai": "^4.1.4",
"#types/mocha": "^5.2.4",
"ajv": "^6.5.1",
"asar": "^0.14.3",
"chai": "^4.1.2",
"electron": "^2.0.3",
"electron-builder": "^20.16.0",
"ignore-styles": "^5.0.1",
"jasmine": "^3.1.0",
"jsdom": "^11.11.0",
"jsdom-global": "^3.0.2",
"mocha": "^5.2.0",
"spectron": "^3.8.0",
"ts-node": "^7.0.0",
"tslint": "^5.10.0",
"typescript": "^2.9.2"
},
"build": {
"appId": "your.id",
"files": [
"dist/**/*"
],
"directories": {
"output": "build"
},
"linux": {
"category": "Video",
"target": [
"deb",
"snap"
]
}
},
"dependencies": {
"#types/core-js": "^2.5.0",
"moment": "^2.22.2",
"winston": "^3.0.0"
}
}
Here's my main.ts:
import { app, BrowserWindow, ipcMain, crashReporter } from "electron";
import * as path from "path";
process.env.ELECTRON_PROCESS_NAME = "main";
import { initLogger } from "../common/logging";
let log = initLogger();
log.info("=== Starting up ===");
let mainWindow: Electron.BrowserWindow = null;
function createMainWindow() {
if (mainWindow === null) {
mainWindow = new BrowserWindow({
height: 600,
width: 800,
});
mainWindow.loadFile(path.join(__dirname, "../../static/ui.html"));
mainWindow.webContents.openDevTools();
}
}
app.on("ready", createMainWindow);
app.on("window-all-closed", () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
log.info("Exiting...");
app.quit();
}
});
app.on("activate", () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
createMainWindow();
});

A simple solution is to disable devtools because they wont be in your final version anyway. You could add a "--debug" argument (parsed in main.js) and add it to the "start" script in package.json. Then, the devtools wont open in your test if you don't add the argument.
If you want to keep devtools or just be sure to exit your app, you can stop your main process with the Node.js exit() function which is a bit brutal:
afterEach(function () {
if (this.app && this.app.isRunning()) {
this.app.mainProcess.exit(0);
}
});
I choose to have a mix of both disabling devtools and be sure to exit (to prevent any bug in my app that could prevent to exit). I'm not familiar with Mocha beceause I use AVA instead and I work in JS instead of TS. So I tried to adapt this code but there may be some mistakes:
beforeEach(function () {
this.app = new Application({
path: electronPath,
// you could add --debug in the following array
// don't do it, to keep the devtools closed
args: [path.join(__dirname, '..')]
} as any);
return this.app.start();
})
// use ES8 'async' to be able to wait
afterEach(async function () {
if (this.app && this.app.isRunning()) {
// get the main process PID
let pid = this.app.mainProcess.pid;
// close the renderer window using its own js context
// to get closer to the user action of closing the app
// you could also use .stop() here
await this.app.client.execute(() => {
window.close();
});
// here, the app should be close
try {
// check if PID is running using '0' signal (throw error if not)
process.kill(pid, 0);
}
catch(e) {
// error catched : the process was not running
// do someting to end the test with success !
return;
}
// no error, process is still running, stop it
this.app.mainProcess.exit(1);
// do someting to end the test with error
return
}
});

Try this
app.on('window-all-closed', () => {
// prevent quit on MacOS. But also quit if we are in test.
if (process.platform !== 'darwin' || isTest) {
app.quit();
}
});
And isTest is const isTest = process.env.NODE_ENV === 'test';
Then the app will properly quit!
To get process.env.NODE_ENV, you may need to update your webpack to use DefinePlugin:
new webpack.DefinePlugin({
'process.env.NODE_ENV': `"${process.env.NODE_ENV ?? 'production'}"`,
// global: {},
}),

With latest Electron, you have to open your Window with contextIsolation set to false.
const win = new BrowserWindow({
...,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
}
})

Related

Is it possible to run Angular Cypress Test with K6?

We have a lot of cypress tests in our Angular Project. But we want to use k6 as our new main load testing tool. But also we want to keep our already written cypress tests.
Is it possible to execute cypress tests within k6? e.g run k6 with 1000 VUs but instead of k6 test script, use cypress test scrpts.
There's an article here: Using Cypress to automate k6 scripts recording
Lots of steps, but to update for Cypress v10 the syntax for the before:browser:launch hook:
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:browser:launch', (browser, launchOptions) => {
if (browser.isHeaded) {
try {
launchOptions.extensions.push(...);
} catch (error) {
console.log("extension not available"); //when in headless mode
}
return launchOptions;
}
})
},
// other config
}
Answer above was a solid suggestion. I used that article indicated there, only slight modifications (i mentioned it on the medium post).
for cypress 10+, under cypress.config.js
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:browser:launch', (browser = {}, launchOptions) => {
console.log(launchOptions.args) // print all current args
//Mac and Linux path
//const k6path = "k6Files/k6-Browser-Recorder"
//Windows path
const k6path = "C:\\Users\\..."
if (browser.isHeaded) {
try {
launchOptions.extensions.push(k6path);
} catch (error) {
console.log("extension not available"); //when in headless mode
}
return launchOptions
}
})
},
},
});
if your using env config files like cypress.dev-config.js then above goes in that e2e section
Rest of instruction on the post should work just fine.

Restoring Main Window on Electron JS

I created an app (done for OSX) that will send a message even when the main window is hidden, currently my main.js is like this:
const { app, shell, BrowserWindow, dialog } = require('electron')
const path = require('path')
const electron = require('electron')
// Enable live reload for all the files inside your project directory
require('electron-reload')(__dirname);
let win = null
function createWindow () {
win = new BrowserWindow({
width: 420,
height: 420,
resizable: false,
fullscreenable: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true
}
})
win.on('close', (event) => {
if (app.quitting) {
win = null
} else {
event.preventDefault()
win.hide()
}
})
win.loadFile('index.html')
win.webContents.on('new-window', function(e, url) {
// make sure local urls stay in electron perimeter
if('file://' === url.substr(0, 'file://'.length)) {
return;
}
// and open every other protocols on the browser
e.preventDefault();
shell.openExternal(url);
});
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => { win.show() })
app.on('before-quit', () => app.quitting = true)
The idea is that when the user try to close the app it will just hide it. I'll need to add something that at certain hours of the day the app will launch the main window again.
Is there a way to make the main window be reopened or to run a code in the background so at certain time of the day the window will be unhidden?
Sure....this will reopen your minimized window
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
}

Discord Music Bot joins voice channel, light up green but didnt has any audio. Worked well for 2 weeks before. No errors in console

I coded a bot with node.js. I used the example by Crawl for his music bot. I did everything similar to him. After I finished my build everything worked. Every other command and the play command. But now after 2 weeks the bot joins the voice channel, light up green but has no sound. I updated ffmpeg, #discordjs/opus, ffmpeg-static and downloaded the completed version from ffmpeg but the bot still has no audio. The queue, volume, nowplaying, skip, shuffle, loop everything works. But after I got the video or playlist with the play command the bot only joins light up green but has no audio. So the bot definitly get the url, get the video, get everything he needs to play. But after joining he doesnt use the informations to play. Also he doesnt leave the voicechannel after the song should end.
function play(guild, song) {
try {
const ServerMusicQueue = queue.get(guild.id);
if (!song) {
ServerMusicQueue.textchannel.send(`🎶 Queue ended and left the Voicechannel!`).then(message => message.delete(6000));
ServerMusicQueue.voiceChannel.leave()
queue.delete(guild.id)
return;
}
const dispatcher = ServerMusicQueue.connection.playStream(ytdl(song.url, { filter: 'audioonly', quality: 'highestaudio', highWaterMark: 1 << 25 }))
.on('end', () => {
var loopset = JSON.parse(fs.readFileSync("./rqs/loopset.json", "utf8"))
if (!loopset[message.guild.id]) {
loopset[message.guild.id] = {
loopset: config.loopset
}
}
var loop2 = loopset[message.guild.id].loopset;
if (loop2 === "true") {
play(guild, ServerMusicQueue.songs[0])
return;
}
ServerMusicQueue.songs.shift()
play(guild, ServerMusicQueue.songs[0])
})
.on('error', error => {
console.error(error)
});
dispatcher.setVolumeLogarithmic(ServerMusicQueue.volume / 5);
ServerMusicQueue.textchannel.send(`🎶 Start playing: **${song.title}**`).then(message => message.delete(8000));
} catch (error2) {
console.log(error2)
}
}
async function handleVideo(video, message, voiceChannel, playlist = false) {
const ServerMusicQueue = queue.get(message.guild.id)
const song = {
id: video.id,
title: Util.escapeMarkdown(video.title),
url: `https://www.youtube.com/watch?v=${video.id}`,
duration: video.duration,
requested: message.author.username
};
if(!ServerMusicQueue) {
const queueConstruct = {
textchannel: message.channel,
voiceChannel: voiceChannel,
connection: null,
songs: [],
volume: 5,
playing: true,
};
queue.set(message.guild.id, queueConstruct);
queueConstruct.songs.push(song)
try {
var connection = await voiceChannel.join()
queueConstruct.connection = connection;
play(message.guild, queueConstruct.songs[0])
var loopset = JSON.parse(fs.readFileSync("./rqs/loopset.json", "utf8"))
if(!loopset[message.guild.id]){
loopset[message.guild.id] = {
loopset: config.loopset
}
}
var loop2 = loopset[message.guild.id].loopset;
if(loop2 === "true") {
loopset[message.guild.id] = {
loopset: "false"
}
fs.writeFile("./rqs/loopset.json", JSON.stringify(loopset), (err) => {
if (err) console.log(err)
});
}
} catch (error) {
console.error(`Voicechannel join: ${error}`)
queue.delete(message.guild.id);
message.channel.send("Error with joining the Voicechannel!").then(message => message.delete(5000));
message.delete(4000).catch(console.error);
return;
}
} else {
ServerMusicQueue.songs.push(song);
if(playlist) return undefined;
else return message.channel.send(`🎶 **${song.title}** has been added to the queue!`).then(message => message.delete(5000));
}
return;
}
package.json
"dependencies": {
"#discordjs/opus": "^0.3.2",
"bufferutil": "^4.0.1",
"colors": "^1.4.0",
"discord.js": "^11.6.4",
"discord.js-commando": "^0.10.0",
"discord.js-musicbot-addon": "^13.9.1",
"discordjs-prompter": "^1.3.1",
"ffmpeg-static": "^4.2.2",
"file-system": "^2.2.2",
"html-entities": "^1.3.1",
"m3u8stream": "^0.7.0",
"miniget": "^1.7.0",
"ms": "^2.1.2",
"node-opus": "^0.3.3",
"npm": "^6.14.5",
"simple-youtube-api": "^5.2.1",
"sqlite": "^3.0.3",
"sqlite3": "^4.1.0",
"superagent": "^5.2.2",
"yt-search": "^1.1.2",
"ytdl-core": "^2.1.3"
}
Try getting ytdl-core, it may fix the issues.
npm i ytdl-core
If that does not fix the issue, try getting Discord-YTDL-Core (make sure you have ytdl-core kept installed)
npm i discord-ytdl-core

How to add antd to Nextjs

I create a project base on with-ant-design-less and then try to add sass to project. I change the following files:
next.config.js:
/* eslint-disable */
const withSass = require("#zeit/next-sass");
const withLess = require("#zeit/next-less");
const lessToJS = require("less-vars-to-js");
const fs = require("fs");
const path = require("path");
// Where your antd-custom.less file lives
const themeVariables = lessToJS(
fs.readFileSync(path.resolve(__dirname, "./assets/antd-custom.less"), "utf8")
);
module.exports = withSass({
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: "[folder]_[local]___[hash:base64:5]",
},
...withLess({
lessLoaderOptions: {
javascriptEnabled: true,
modifyVars: themeVariables, // make your antd custom effective
},
webpack: (config, { isServer }) => {
if (isServer) {
const antStyles = /antd\/.*?\/style.*?/;
const origExternals = [...config.externals];
config.externals = [
(context, request, callback) => {
if (request.match(antStyles)) return callback();
if (typeof origExternals[0] === "function") {
origExternals[0](context, request, callback);
} else {
callback();
}
},
...(typeof origExternals[0] === "function" ? [] : origExternals),
];
config.module.rules.unshift({
test: antStyles,
use: "null-loader",
});
}
return config;
},
}),
});
package.json
{
"name": "with-ant-design-less",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"#zeit/next-less": "^1.0.1",
"#zeit/next-sass": "^1.0.1",
"antd": "^3.5.4",
"babel-plugin-import": "^1.7.0",
"less": "3.0.4",
"less-vars-to-js": "1.3.0",
"next": "latest",
"null-loader": "2.0.0",
"react": "^16.7.0",
"sass": "^1.26.3",
"react-dom": "^16.7.0"
},
"license": "ISC",
"devDependencies": {
"#types/node": "^13.13.1",
"typescript": "^3.8.3"
}
}
but when I run the project I get the following error:
[ error ] ./pages/index.module.scss
To use Next.js' built-in Sass support, you first need to install `sass`.
Run `npm i sass` or `yarn add sass` inside your workspace.
Although I'm looking for better solution to setup the project because in this way all the style will be in one big chunk that cause performance issue.
Any idea? Thanks
next.config.js:
const withPlugins = require('next-compose-plugins');
const withCss = require('#zeit/next-css');
const withSass = require('#zeit/next-sass');
const withLess = require('#zeit/next-less');
const lessToJS = require('less-vars-to-js');
const fs = require('fs');
const path = require('path');
const lessThemeVariablesFilePath = './static/ant-theme-variables.less';
const themeVariables = lessToJS(
fs.readFileSync(path.resolve(__dirname, lessThemeVariablesFilePath), 'utf8'),
);
const lessNextConfig = {
lessLoaderOptions: {
javascriptEnabled: true,
modifyVars: themeVariables,
},
webpack: (config, { isServer }) => {
if (isServer) {
const antStyles = /antd\/.*?\/style.*?/;
const origExternals = [...config.externals];
config.externals = [
(context, request, callback) => {
if (request.match(antStyles)) return callback();
if (typeof origExternals[0] === 'function') {
origExternals[0](context, request, callback);
} else {
callback();
}
},
...(typeof origExternals[0] === 'function' ? [] : origExternals),
];
config.module.rules.unshift({
test: antStyles,
use: 'null-loader',
});
}
return config;
},
};
const sassNextConfig = {
cssModules: true,
cssLoaderOptions: {
localIdentName: '[path]___[local]___[hash:base64:5]',
},
};
module.exports = withPlugins([
[withLess, lessNextConfig],
[withSass, sassNextConfig],
]);
babel config:
module.exports = {
presets: ['next/babel'],
plugins: [
['import', { libraryName: 'antd', style: true }],
],
};
I use sass, less and css. it depends on your requirement. and you can add your custom variables in an static file as I did.
hope be helpful.
So, for people who came here just for the basic addition, you can add antd to your nextjs app by installing antd
npm i antd
and then you can add the antd styles to your
_app.js file
after your global styles:
import 'antd/dist/antd.css'

How to get nightwatch test_workers work with browserstack-local

I am trying to get Nightwatch's inbuilt parallel test_workers working with browserstack-local for local url testing.
When running tests without browserstack local, Nightwatch test_workers seem to work just fine. The same is true for running local tests without test_workers enabled.
I have tried the examples found here https://github.com/browserstack/nightwatch-browserstack but none of these uses browserstack-local in combination with Nightwatch test_workers.
When running locally with test_workers I get the following output.
Connecting local
Connected. Now testing...
Process terminated with code 0.
Has anyone else encountered similar issues?
EDIT: I have since resolved this issue and have posted an answer below
I have dumped the relevant configuration files below.
My local.conf.js
nightwatch_config = {
globals_path: 'globals.js',
output_folder: false,
src_folders: ['tests'],
selenium: {
'start_process': false,
'host': 'hub-cloud.browserstack.com',
'port': 80
},
test_workers: {
"enabled": true,
"workers":2
},
test_settings: {
default: {
desiredCapabilities: {
'browserstack.user': process.env.BROWSERSTACK_USER,
'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY,
'browserstack.local': true,
'browserstack.debug': false,
}
}
}
};
// Code to copy seleniumhost/port into test settings
for (let i in nightwatch_config.test_settings) {
if (nightwatch_config.test_settings.hasOwnProperty(i)) {
let config = nightwatch_config.test_settings[i];
config['selenium_host'] = nightwatch_config.selenium.host;
config['selenium_port'] = nightwatch_config.selenium.port;
}
}
module.exports = nightwatch_config;
local.runner.js
#!/usr/bin/env node
var Nightwatch = require('nightwatch');
var browserstack = require('browserstack-local');
var bs_local;
process.env.BROWSERSTACK_ID = new Date().getTime();
try {
process.mainModule.filename = "./node_modules/.bin/nightwatch";
// Code to start browserstack local before start of test
console.log("Connecting local");
Nightwatch.bs_local = bs_local = new browserstack.Local();
bs_local.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY }, function (error) {
if (error) throw error;
console.log('Connected. Now testing...');
Nightwatch.cli(function (argv) {
Nightwatch.CliRunner(argv)
.setup(null, function () {
// Code to stop browserstack local after end of parallel test
bs_local.stop(function () { });
})
.runTests(function () {
// Code to stop browserstack local after end of single test
bs_local.stop(function () { });
});
});
});
} catch (ex) {
console.log('There was an error while starting the test runner:\n\n');
process.stderr.write(ex.stack + '\n');
process.exit(2);
}
and my package.json script
node ./local.runner.js -c ./local.conf.js
The issue here was due to the module filename being incorrectly defined in the local.runner.js
process.mainModule.filename = "./node_modules/.bin/nightwatch";
should be pointed directly at the Nightwatch file in its directory.
process.mainModule.filename = "./node_modules/nightwatch/bin/nightwatch";
The difference in these files and the exact reason for this solution working are beyond me.
The answer was derived from the "suite" runner in https://github.com/browserstack/nightwatch-browserstack
It seems you are not specifying browsers. Modify your configuration file to be inline with the following configuration file:
var browserstack = require('browserstack-local');
nightwatch_config = {
src_folders : [ "local" ],
selenium : {
"start_process" : false,
"host" : "hub-cloud.browserstack.com",
"port" : 80
},
test_workers: {
"enabled": true,
"workers":2
},
common_capabilities: {
'browserstack.user': process.env.BROWSERSTACK_USERNAME || 'BROWSERSTACK_USERNAME',
'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY || 'BROWSERSTACK_ACCESS_KEY',
'browserstack.debug': true,
'browserstack.local': true
},
test_settings: {
default: {},
chrome: {
desiredCapabilities: {
browser: "chrome"
}
},
firefox: {
desiredCapabilities: {
browser: "firefox"
}
},
safari: {
desiredCapabilities: {
browser: "safari"
}
}
}
};
// Code to support common capabilites
for(var i in nightwatch_config.test_settings){
var config = nightwatch_config.test_settings[i];
config['selenium_host'] = nightwatch_config.selenium.host;
config['selenium_port'] = nightwatch_config.selenium.port;
config['desiredCapabilities'] = config['desiredCapabilities'] || {};
for(var j in nightwatch_config.common_capabilities){
config['desiredCapabilities'][j] = config['desiredCapabilities'][j] || nightwatch_config.common_capabilities[j];
}
}
module.exports = nightwatch_config;

Resources