I'm struggling to understand how to correctly import ipcRenderer in a .vue file.
I put in /src/background.js file :
webPreferences: {
nodeIntegration:false,
contextIsolation: true, // protects against prototype pollution
preload: path.join(__dirname, "../dist_electron/preload.js"),
}
And, based on https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration I put in preload.js :
window.ipcRenderer = ipcRenderer
webpack.config.js :
module.exports = {
entry: './src/background.js',
target: 'node',
output: {
path: path.join(__dirname, 'build'),
filename: 'background.js'
}
}
In order to facilitate the debugging, I created a github repo.
You can git clone the repo from here: https://github.com/raphael10-collab/ElectronVueTypeScriptScaffolding.git
After executing yarn -> yarn electron:serve
you will get the correct page.
But when activating in /src/views/Home.vue this line:
//import { ipcRenderer } from 'electron'
you will get this error:
__dirname is not defined
Environment Info:
System:
OS: Linux 5.4 Ubuntu 18.04.5 LTS (Bionic Beaver)
CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU # 4.00GHz
Binaries:
Node: 14.5.0 - ~/.nvm/versions/node/v14.5.0/bin/node
Yarn: 1.22.4 - /usr/bin/yarn
npm: 6.14.5 - ~/.nvm/versions/node/v14.5.0/bin/npm
Browsers:
Chrome: 85.0.4183.83
Firefox: 79.0
npmPackages:
#vue/babel-helper-vue-jsx-merge-props: 1.0.0
#vue/babel-plugin-transform-vue-jsx: 1.1.2
#vue/babel-preset-app: 4.4.6
#vue/babel-preset-jsx: 1.1.2
#vue/babel-sugar-functional-vue: 1.1.2
#vue/babel-sugar-inject-h: 1.1.2
#vue/babel-sugar-v-model: 1.1.2
#vue/babel-sugar-v-on: 1.1.2
#vue/cli-overlay: 4.4.6
#vue/cli-plugin-babel: ~4.4.0 => 4.4.6
#vue/cli-plugin-e2e-cypress: ~4.4.0 => 4.4.6
#vue/cli-plugin-router: ~4.4.0 => 4.4.6
#vue/cli-plugin-typescript: ~4.4.0 => 4.4.6
#vue/cli-plugin-unit-mocha: ~4.4.0 => 4.4.6
#vue/cli-plugin-vuex: ~4.4.0 => 4.4.6
#vue/cli-service: ~4.4.0 => 4.4.6
#vue/cli-shared-utils: 4.4.6
#vue/component-compiler-utils: 3.2.0
#vue/preload-webpack-plugin: 1.1.2
#vue/test-utils: ^1.0.3 => 1.0.3
#vue/web-component-wrapper: 1.2.0
babel-helper-vue-jsx-merge-props: 2.0.3
typescript: ^3.9.7 => 3.9.7
vue: ^2.6.11 => 2.6.11
vue-class-component: ^7.2.5 => 7.2.5
vue-cli-plugin-electron-builder: ~2.0.0-rc.4 => 2.0.0-rc.4
vue-hot-reload-api: 2.3.4
vue-i18n: ^8.20.0 => 8.20.0
vue-loader: 15.9.3
vue-property-decorator: ^9.0.0 => 9.0.0
vue-router: ^3.2.0 => 3.3.4
vue-style-loader: 4.1.2
vue-template-compiler: ^2.6.11 => 2.6.11
vue-template-es2015-compiler: 1.9.1
vuex: ^3.5.1 => 3.5.1
vuex-class: ^0.3.2 => 0.3.2
npmGlobalPackages:
#vue/cli: 4.4.6
node version: v14.5.0
Update 1)
I tried to set webPreferences as follows (with nodeIntegration: true) :
webPreferences: {
nodeIntegration: true,
//contextIsolation: true, // protects against prototype pollution
//preload: path.join(__dirname, "../dist_electron/preload.js"),
},
and got this error:
fs.existsSync is not a function
Searching around for info about this kind of problem, I found this post:
How to resolve fs.existsSync is not a function
With this link: https://webpack.js.org/concepts/targets/
But I already specified in webpack.config.js the target ‘node’:
in webpack.config.js :
module.exports = {
entry: './src/background.js',
target: 'node',
output: {
path: path.join(__dirname, 'build'),
filename: 'background.js'
}
}
So... how to solve this new problem?
By the way,
Why must I put
webPreferences: {
nodeIntegration: true,
}
if, for security reasons, it is more secure to have:
webPreferences: {
nodeIntegration:false,
contextIsolation: true, // protects against prototype pollution
preload: path.join(__dirname, "../dist_electron/preload.js"),
}
dist_electron/preload.js :
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) =>
func(...args));
}
}
}
);
window.ipcRenderer = ipcRenderer
https://www.electronjs.org/docs/tutorial/security#electron-security-warnings
Update 2)
in vue.config.js I've put:
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'dist_electron/preload.js',
// Or, for multiple preload files:
//preload: { preload: 'src/preload.js', otherPreload:
//'src/preload2.js' }
}
}
}
But I get the same error when I do
yarn electron:serve
UncaughtReferenceError: __dirname is not defined
When setting nodeIntegration: true (but I would prefer to set it to false, and use preload.js file), I get this other error (as above):
Uncaught TypeError: fs.existsSync is not a function
Uncaught TypeError: fs.existsSync is not a function
How to solve the problem?
Looking forward to your kind help
Updated Answer - Nodeintegration disabled and contextIsolation enabled
In order to use the ipcRenderer with Vue CLI plugin Electron Builder you need to first setup electron to utilize a preload.js file.
Inside your vue.config.js file you need to add the preload.js path like this:
// vue.config.js - project root
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
// Or, for multiple preload files:
preload: { preload: 'src/preload.js', otherPreload: 'src/preload2.js' }
}
}
}
Next you need to updated your background.js file to use preload.js in the web preferences like this:
// src/background.js
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
enableRemoteModule: true,
preload: path.join(__dirname, 'preload.js'),
},
})
Note: nodeIntegration is disabled and contextIsolation is enabled by default
Once you have that complete you can create the preload.js file in your src directory.
With contextIsolation enabled you need to import the contextBridge along with ipcRenderer. Then you can can expose the ipcRenderer to your client.
Then add this to the file:
// src/preload.js
import { contextBridge, ipcRenderer } from 'electron'
// Expose ipcRenderer to the client
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
let validChannels = ['nameOfClientChannel'] // <-- Array of all ipcRenderer Channels used in the client
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['nameOfElectronChannel'] // <-- Array of all ipcMain Channels used in the electron
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
Note: You need to make sure your preload.js file is in the src folder and not dist_electron
To test and make sure the preload file is working you can also create an alert in the preload.js file
// src/preload.js
import { contextBridge, ipcRenderer } from 'electron'
// Expose ipcRenderer to the client
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
let validChannels = ['nameOfClientChannel'] // <-- Array of all ipcRenderer Channels used in the client
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['nameOfElectronChannel'] // <-- Array of all ipcMain Channels used in the electron
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
alert("It Worked!") // Remove this line once you confirm it worked
When you have verified that your preload script is working correctly, you can access the ipcRenderer from your vue app.
like this:
// src/App.vue
<template>
\\ Some html
</template>
<script>
export default {
name: "App",
methods: {
test(){
window.ipcRenderer.send(channel, args...) // or any other ipcRenderer method you want to invoke
}
};
</script>
In electron you can listen for those events
// background.js
ipcMain.on(channel, (event, args) => {
// Do stuff
});
Sources:
Preload Files
Node Integration
With contextIsolation = true, is it possible to use ipcRenderer?
Original Answer
In order to use the ipcRenderer with Vue CLI plugin Electron Builder you need to first setup electron to utilize a preload.js file.
Inside your vue.config.js file you need to add the preload.js path like this:
// vue.config.js - project root
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
// Or, for multiple preload files:
preload: { preload: 'src/preload.js', otherPreload: 'src/preload2.js' }
}
}
}
Next you need to updated your background.js file to use preload.js in the web preferences like this:
// src/background.js
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/configuration.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
+ preload: path.join(__dirname, 'preload.js')
}
})
Once you have that complete you can create the preload.js file in your src directory
Then add this to the file:
// src/preload.js
import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
Note: You need to make sure your preload.js file is in the src folder and not dist_electron
To test and make sure the preload file is working you can also create an alert in the preload.js file
// src/preload.js
import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
alert("It Worked!") // Remove this line once you confirm it worked
When you have verified that your preload script is working correctly, you can access the ipcRenderer from your vue app.
like this:
// src/App.vue
<template>
\\ Some html
</template>
<script>
export default {
name: "App",
methods: {
test(){
window.ipcRenderer.send(channel, args...) // or any other ipcRenderer method you want to invoke
}
};
</script>
Sources:
Preload Files
Node Integration
What worked for me was setting the electron window's contextIsolation to false.
So in your main.js wherever you create a BrowserWindow it would look like this:
const win = new BrowserWindow({
webPreferences: {
contextIsolation: false,
preload: path.join(__dirname, 'preload.js'),
},
})
And then in preload.js you can simply do
const { ipcRenderer } = require('electron')
window.ipcRenderer = ipcRenderer
And then you'll have access to ipcRenderer anywhere in your vue code.
It seems that in the current version of electron contextIsolation defaults to true, which makes the window that preload.js sees different from the one your vue app sees.
You need to set nodeIntegration to true.
This enables NodeJs in the renderer process (i.e the front-end) so you can use stuff like fs (FileSystem) and other NodeJs-only features in your Vue code.
As ipcRenderer requires NodeJs's environnement too (__dirname is a global variable for NodeJs only), it needs to be activated.
Related
I am trying to bundle my Node.js server-side code (Koa app) into a single file which is what Webpack produces when I use the target as node. This is what ncc - node.js compiler collection also achieves.
I am now using Vite for my next project. I am able to bundle the code by using the SSR bundling feature provided by Vite. But I am not able to bundle third-party dependencies into this single file excluding core/built-in node.js modules. This is my Vite build script:
import path from 'path';
import { build } from 'vite';
import builtinModules from 'builtin-modules';
async function main() {
const result = await build({
mode: 'production',
appType: 'custom',
root: path.join(process.cwd(), 'backend'),
ssr: {
format: 'esm',
target: 'node',
// By default Vite bundles everything except the one we pass via `external` array.
external: builtinModules
},
build: {
manifest: false,
rollupOptions: {
input: 'backend/main.mjs',
output: {
assetFileNames: 'bundle.js'
}
},
outDir: '../dist/backend',
ssr: true,
emptyOutDir: true
},
plugins: [],
});
}
main();
In the above code, builtinModules is simply the string array of all the core node.js modules:
// builtinModules
[
'assert', 'async_hooks',
'buffer', 'child_process',
'cluster', 'console',
'constants', 'crypto',
'dgram', 'diagnostics_channel',
'dns', 'domain',
'events', 'fs',
'http', 'http2',
'https', 'inspector',
'module', 'net',
'os', 'path',
'perf_hooks', 'process',
'punycode', 'querystring',
'readline', 'repl',
'stream', 'string_decoder',
'timers', 'tls',
'trace_events', 'tty',
'url', 'util',
'v8', 'vm',
'worker_threads', 'zlib'
]
For my original source code:
// main.mjs
import path from 'path';
import Koa from 'koa';
async function main() {
console.log('Source folder', path.join(__dirname, 'src'));
const app = new Koa();
app.listen(3000);
}
main();
The produced output for the above Vite build configuration is:
// bundle.js
import path from "path";
import Koa from "koa";
async function main() {
console.log("Source folder", path.join(__dirname, "src"));
const app = new Koa();
app.listen(3e3);
}
main();
Ideally, I expected that it would bundle the koa package in the final output leaving behind only native path module. But that's not the case.
So, how do I enable bundling of my entire backend/Node.js source code compile into single file leaving only core node.js modules as external as Node.js is the runtime for my code? The vite server options also provide the noexternal option but setting it to the true works only for webworker like runtime where none of the built-in node modules are available.
Latest vite versions are using preloading you should tweak some rollup options to disable code splitting
import { defineConfig } from 'vite' // 2.8.0
import react from '#vitejs/plugin-react' // 1.0.7
export default defineConfig ({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {}
},
},
},
})
I have an electron app that builds and runs in development, but when packaging the app with electron-builder, the preload script is not packaged in the right location.
This is a well documented issue and there are very similar questions here and here for example, but none of the replies or solutions are working in my case.
From my electron.js file:
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(app.getAppPath(), 'src/preload.js'),
contextIsolation: true,
},
});
// In production, set the initial browser path to the local bundle generated
// by the Create React App build process.
// In development, set it to localhost to allow live/hot-reloading.
const appURL = app.isPackaged
? url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true,
})
: 'http://localhost:3000';
mainWindow.loadURL(appURL);
mainWindow.webContents.openDevTools();
}
My preload script:
const { contextBridge, shell } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
openBrowserWindow: (url) => shell.openExternal(url)
}
)
And my Electron app package.json:
"build": {
"extends": null,
"appId": "com.app",
"productName": "App",
"directories": {
"output": "dist"
},
"mac": {
"target": {
"target": "pkg",
"arch": [
"universal"
]
},
"darkModeSupport": "true",
"extendInfo": "app"
},
"pkg": {
"installLocation": "/Applications",
"overwriteAction": "upgrade"
},
"files": [
"**",
"../app/src/*",
"src/preload.js"
],
"extraResources": [
"../app/src/*",
"src/preload.js"
],
"extraFiles": [
"../app/src/*",
"src/preload.js"
]
}
Above I have tried to make sure the "src/preload.js" file is copied over in different ways, but I still get the error:
Unable to load preload script: ...app/Contents/Resources/app.asar/src/preload.js
Error: Cannot find module '...app/Contents/Resources/app.asar/src/preload.js'
The preload script is in fact copied over, but it is not part of the app.asar file. It is copied in to a src folder outside of the Resources folder which contains the app.asar file:
How do I correctly configure electron-builder so this file is in the right location and can be accessed at package runtime?
If you do:
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.resolve(app.getAppPath(), 'preload.js'),
contextIsolation: true,
},
});
Does it works ? (worked for me with electron-webpack and electron-builder)
preload: path.join(app.getAppPath(), 'src/preload.js'),
As you are not packaging the preload.js into the app package file (asar default), this won't work like this. app.getAppPath() will indicate the app package file(or directory, in case you are setting asar as false)
Your code is indicating /xxx.app/Contents/Resources/app.asar/src/preload.js or /xxx.app/Contents/Resources/app/src/preload.js
Your preload script file is not there but in the 2nd parent's directory.
So here is the correct path in your case,
path.join(app.getAppPath(), '..', '..', 'src', 'preload.js');
First, add console logs for testing.
console.log({dirname: __dirname})
console.log({getAppPath: app.getAppPath()})
console.log({resourcesPath: process.resourcesPath})
const mainWindow = new BrowserWindow({ ... })
Second, you have to add contextIsolation: true.
If you are using electron-builder and for some reason you cannot add contextIsolation: true you can use this workaround:
package.json
"build": {
...
"extraResources": [
...
"app/preload.js" // <---- add your path
],
}
electron.js
const preloadPath =
process.env.NODE_ENV === 'development'
? path.join(__dirname, '../preload.js') // <---- add your path
: path.join(process.resourcesPath, '/app/preload.js'); // <---- add your path
const mainWindow = new BrowserWindow({
...
webPreferences: {
contextIsolation: false,
preload: preloadPath,
...
}
})
What is path.join(process.resourcesPath, '/app/preload.js') ?
After building your app you can find your extra resources here
C:\Users\<user>\AppData\Local\Programs\<app>\resources - for Windows.
For MacOS you can right click on your app and click on Show Package Contents > Resources
I wanted to use the following code to send a message from the renderer to the main process, which then writes it to a log file using electron-log. My main.js looks like this:
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const { ipcMain } = require('electron');
const log = require('electron-log');
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
frame: true,
width: 400,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false
}
})
ipcMain.on('infoLog', (event, args) => {
log.info(args)
});
....
Now I tried to address the IPC in my App.vue accordingly:
import Navbar from '#/components/Navbar'
const { ipcRenderer } = require('electron')
export default {
name: 'App',
components: {
Navbar
},
created: function () {
ipcRenderer.send('infoLog','A async message to main')
}
}
When I start it with yarn electron:serve I see this error in the console of the window:
Uncaught ReferenceError: __dirname is not defined
at eval (webpack-internal:///./node_modules/electron/index.js:4)
at Object../node_modules/electron/index.js (chunk-vendors.js:2778)
at __webpack_require__ (app.js:849)
at fn (app.js:151)
at eval (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/App.vue?vue&type=script&lang=js:5)
at Module../node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/App.vue?vue&type=script&lang=js (app.js:938)
at __webpack_require__ (app.js:849)
at fn (app.js:151)
at eval (webpack-internal:///./src/App.vue?vue&type=script&lang=js:2)
at Module../src/App.vue?vue&type=script&lang=js (app.js:1099)
What I don't understand is that I set it up exactly like Electron's doc:
https://www.electronjs.org/docs/api/ipc-main
You have 2 different issues here:
the correct webpack configuration to support node.js code
missing node integration to use node API like require
The stacktrace you are seeing here likely comes from an incorrect webpack configuration. Unless told otherwise, webpack tries to replace __dirname with something different. Here we don't want that - node provides __dirname and we want to use it, so we have to tell webpack to leave __dirname alone.
You'll find an example in the webpack documentation.
For webpack 5 adding a node section should help:
module.exports = {
//...
node: {
global: false,
__filename: false,
__dirname: false,
}
};
After you solved this problem you'll likely fall over the issue that your browser window does not know require. You can reintroduce specific node API like the IPC by using a preload script. Don't activate the full node integration without knowing what you are doing.
For an example, have a look at this answer.
I have an angular application wrapped in Electron. I have generated the installer using electron-builder. As per the recommendations for communication with renderer process I have used preload.js as preload script.
The script works fine in the development environment. However, once I package the app and install it, it shows me the error Cannot read property 'exposeInMainWorld' of undefined
Here is my preload.js
window.onload = () => {
const {
contextBridge,
ipcRenderer
} = require("electron");
const validChannels = ['event-1', 'event-2', 'event-3'];
// Expose protected methods that allow the renderer process to use the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels To Main Process
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
// From Main Process
if (validChannels.includes(channel)) {
console.log('receive: ' + channel);
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
};
My Main.js
this._win = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
nodeIntegration: false,
webSecurity: true,
allowEval: false,
allowRunningInsecureContent: false,
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "./preload.js") // use a preload script
},
title: this._appTitle,
autoHideMenuBar: true,
icon: path.join(__dirname, '/../dist/some-path/favicon.ico')
});
I have kept both main.js and preload.js inside a folder electron at the root level where package.json is available. In case you need to check my builder-config.yaml, it's present at this link.
Please suggest what to do.
P.S. : the preload.js works absolutely fine while in dev mode. The issue is only after packaging the app with electron-builder
I got this kind of error when I wrote:
getPlatform: () => window.remote.getPlatform(),
Then I get Uncaught Error: Uncaught TypeError: Cannot read property 'getPlatform' of undefined
in
console.log('window.remote.getPlatform()', window.remote.getPlatform && window.remote?.getPlatform())
When I change it to
getPlatform: () => process.platform,
everything works just fine.
I got this one working. Actually the version of electron mentioned in my builder-config.yaml was 5.0.13. The contextbridge api was not introduced then. When I changed the version of electron to 9 and above, it works just fine. Thanks
I'm new to frontend world, I would like to write some test using protractor-image-comparison. I followed installation instructions from https://github.com/wswebcreation/protractor-image-comparison. Also I make configuration according to this page.
When I try to use functions form this lib I get following error: "TypeError: Cannot read property 'checkFullPageScreen' of undefined". I'm getting a warrning in protractor.conf.js in
const protractorImageComparison = require('protractor-image-comparison');
"Could not find a declaration file for module
'protractor-image-comparison'.
'/home/rafa/repos/example/src/example/node_modules/protractor-image-comparison/index.js'
implicitly has an 'any' type. Try npm install
#types/protractor-image-comparison if it exists or add a new
declaration (.d.ts) file containing declare module
'protractor-image-comparison';"
So I did, I made simple *.d.ts file with `declare module protractor-image-comparison' in it, but it didn't solve the problem just the warning disappear. It's propably the config issue, but I can't handle it or maybe I made wrong declaration. This is my config file :
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const reporter = require("cucumber-html-reporter");
const path = require("path");
const jsonReports = path.join(process.cwd(), "/reports/json");
const htmlReports = path.join(process.cwd(), "/reports/html");
const targetJson = jsonReports + "/cucumber_report.json";
const cucumberReporterOptions = {
jsonFile: targetJson,
output: htmlReports + "/cucumber_reporter.html",
reportSuiteAsScenarios: true,
theme: "bootstrap",
};
exports.config = {
allScriptsTimeout: 110000,
restartBrowserBetweenTests: true,
//SELENIUM_PROMISE_MANAGER: false,
specs: [
'./e2e/**/login.feature'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'custom',
frameworkPath: require.resolve('protractor-cucumber-framework'),
cucumberOpts: {
format: "json:" + targetJson,
require: ['./e2e/steps/*.ts', "./e2e/timeout.ts"],
},
useAllAngular2AppRoots: true,
onPrepare: () => {
browser.ignoreSynchronization = true;
const protractorImageComparison = require('protractor-image-comparison');
browser.protractorImageComparison = new protractorImageComparison(
{
baselineFolder: "report/screens/baseline",
screenshotPath: "report/screens/actual"
}
);
},
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e'
});
},
onComplete: () => {
reporter.generate(cucumberReporterOptions);
}
};
Ok, I solved it. The reason why I was getting this TypeError is that I lunched few test scenarios and onPrepare was lunched only in the begining. I move config of protractor-image-comparison to cucumber befor hook and everything works fine now.