End to end test of electron app on Windows - electron

What I am trying to achieve:
I'd like to set up an electron project with a proper headless end-to-end testing configuration.
Issues encountered
Spectronjs seems to be the solution to achieve so. However there is no configuration to prevent the window from opening on each test. Reading some of the threads on the repository + the documentation of electron in regards to testing mentions Xvfb. I've tried to get my head around this but understand so far that this cannot be installed on Windows? And that there is no alternative.
The list on the page includes other options such as Appvoyer or CicleCI but these again are new and I am barely able to find a guide around these not to mention, I am not really liking that I have to do all these steps (link to github/bitbucket account, etc).
I've also tried to go through demo applications from the electronjs list page, but not all of them do have tests, and when they do, they are sometime written in what seems to be a different programming language, or specifically aimed for angular or react, while on my end I am aiming to use vuejs.
Can anyone point me to a clean example of an offline end to end headless test of an electron app on Windows?

There are several options how to E2E test an Electron app, unfortunately none of them is truly headless. On Windows you do not need Xvfb, it is a Linux thing. On Windows there is a "screen" available even in CI environments (I have experience with Appveyor and Azure Pipelines).
Puppeteer-core (Puppeteer-core does not contain chromium)
Spectron
Selenium-webdriver
Webdriver.io
In the past I used Spectron, but I recently switched to Puppeteer and I am very happy with the switch.
Short Puppeteer try out test file:
const electron = require("electron");
const puppeteer = require("puppeteer-core");
const delay = ms =>
new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
(async () => {
try {
const app = await puppeteer.launch({
executablePath: electron,
args: ["."],
headless: false,
});
const pages = await app.pages();
const [page] = pages;
await page.setViewport({ width: 1200, height: 700 });
await delay(5000);
const image = await page.screenshot();
console.log(image);
await page.close();
await delay(2000);
await app.close();
} catch (error) {
console.error(error);
}
})();
I am testing and building an electron app in Azure Pipelines (free for open-source projects) on Win, Linux and MacOS with this config:
azure-pipelines.yml

Related

Unable to load chrome extention with playwright for automation

Hello guys I just started using playwright and JavaScript. I am having a trouble figuring out how to load chrome extension from local folder while opening browser for automation. I want to automate chrome extension but so far the only clue that I have got is to use this code
const { chromium } = require('playwright');
(async () => {
const pathToExtension = require('path').join(__dirname, 'my-extension');
const userDataDir = '/tmp/test-user-data-dir';
const browserContext = await chromium.launchPersistentContext(userDataDir,{
headless: false,
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`
]
});
const backgroundPage = browserContext.backgroundPages()[0];
// Test the background page as you would any other page.
await browserContext.close();
})();
which I am unable to understand that how this would be configured and in which file should I add this code. Can anybody help me in this regard please?

Download file with Playwright

How to download a file with Playwright?
I'm aware of this question
How to catch a download with playwright?
but that example code does not work. Using the latest released Playwright, there is no 'pageTarget' function on the browser instance:
const client = await browser.pageTarget(page).createCDPSession();
All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded files are deleted when the browser closes.
Download event is emitted once the download starts. Download path becomes available once download completes:
const [ download ] = await Promise.all([
page.waitForEvent('download'), // wait for download to start
page.click('a')
]);
// wait for download to complete
const path = await download.path();
...
https://github.com/microsoft/playwright/blob/master/docs/api.md#class-download
Playwright is going to support downloads in a cross-browser compatible way soon, you can track this feature request.
For now the above Chromium-specific snippet could be fixed by changing the line to:
const client = await context.newCDPSession(page);
which uses the new method for creating CDP sessions.

Passing arguments to a running electron app

I have found some search results about using app.makeSingleInstance and using CLI arguments, but it seems that the command has been removed.
Is there any other way to send a string to an already started electron app?
One strategy is to have your external program write to a file that your electron app knows about. Then, your electron app can listen for changes to that file and can read it to get the string:
import fs
fs.watch("shared/path.txt", { persistent: false }, (eventType: string, fileName: string) => {
if (eventType === "change") {
const myString: string = fs.readFileSync(fileName, { encoding: "utf8" });
}
});
I used the synchronous readFileSync for simplicity, but you might want to consider the async version.
Second, you'll need to consider the case where this external app is writing so quickly that maybe the fs.watch callback is triggered only once for two writes. Could you miss a change?
Otherwise, I don't believe there's an Electron-native way of getting this information from an external app. If you were able to start the external app from your Electron app, then you could just do cp.spawn(...) and use its stdout pipe to listen for messages.
If shared memory were a thing in Node, then you could use that, but unfortunately it's not.
Ultimately, the most elegant solution to my particular problem was to add a http api endpoint for the Electron app using koa.
const Koa = require("koa");
const koa = new Koa();
let mainWindow;
function createWindow() {
let startServer = function() {
koa.use(async ctx => {
mainWindow.show();
console.log("text received", ctx.request.query.text);
ctx.body = ctx.request.query.text;
});
koa.listen(3456);
};
}
Now I can easily send texts to Electron from outside the app using the following url:
localhost:3456?text=myText

Service worker installs via localhost, but fails when deployed to GitHub pages

I am working on a PWA and I have installed and activated a service worker on my site. It works perfectly well while testing on local server but when I ship my code live, it fails.
This is my SW:
const cacheName = 'v1';
const cacheFiles = [
'/',
'/css/styles.css',
'/images/test1.png',
'/images/test2.png',
'/js/app.js',
'/js/sw-registration.js'
]
// Install event
self.addEventListener('install', function(event) {
console.log("SW installed");
event.waitUntil(
caches.open(cacheName)
.then(function(cache){
console.log('SW caching cachefiles');
return cache.addAll(cacheFiles);
})
)
});
// Activate event
self.addEventListener('activate', function(event) {
console.log("SW activated");
event.waitUntil(
caches.keys()
.then(function(cacheNames){
return Promise.all(cacheNames.map(function(thisCacheName){
if(thisCacheName !== cacheName){
console.log('SW Removing cached files from', thisCacheName);
return caches.delete(thisCacheName);
}
}))
})
)
});
// Fetch event
self.addEventListener('fetch', function(event) {
console.log("SW fetching", event.request.url);
event.respondWith(
caches.match(event.request)
.then(function(response){
console.log('Fetching new files');
return response || fetch(event.request);
})
);
});
This is the error I'm getting:
Uncaught (in promise) TypeError: Request failed (sw.js:1)
I don't understand why it fails to cache my files online (github pages) when it works locally. Can someone help me understand?
Thank you.
EDIT: I tried to deploy the site via Netlify and it works there. So it has to be something to do with Github pages. I would still like to know what it is, if anyone can shed any light.
As mentioned in Service Worker caches locally but fails online, when deploying to gh-pages, your web app's content will normally be accessed from a subpath, not in the top-level path, for the domain.
For instance, if your files are in the gh-pages branch of https://github.com/<user>/<repo>, then your web content can be accessed from https://<user>.github.io/<repo>/.
All of the URLs in your cacheFiles array are prefixed with /, which isn't what you want, given that all of your content is accessible under /<repo>/. For instance, / is interpreted as https://<user>.github.io/, which is different from https://<user>.github.io/<repo>/.
The solution to your problem, which will lead to a configuration that works regardless of what the base URL is for your hosting environment, is to prepend each of your URLs with ./ rather than /. For instance:
const cacheFiles = [
'./',
'./css/styles.css',
// etc.
];
The ./ means that the URL is relative, with the location of the service worker file used as the base. Your service worker file will be deployed under https://<user>.github.io/<repo>/, so that will end up being the correct base URL to use for the rest of your content as well.

Electron, I can't use BrowserWindow to explore file system

I'm trying to show a folder of the machine running an Electron app, I already look and see I can load the url file:///d:/ on my machine and see the folder content but noway to do that with a BrowserWindow. I can see in the DevTools an error saying
Not allowed to load local resource.
Is there a workaround or any setup to be able to do that?
I use the following code:
ipcMain.on('openExplorer', (event, arg) => {
exploreWindow = new BrowserWindow({ width: 120, height: 82, title: "MyApp", icon: "assets/images/favicon.ico" });
exploreWindow.setTitle('Files Explorer');
addr = 'file:///d://' ;
console.log(addr);
exploreWindow.loadURL(addr);
exploreWindow.openDevTools();
});
For security reasons it is a good idea to not grant a BrowserWindow access to the filesystem. If you still want to do so you could use the inter process communication module to access the filesystem from your mainfile and send it to your BrowserWindow.
See:
Electron Documentation for ipcMain
Electron Documentation for ipcRenderer

Resources