Tests automatisés
L'automatisation des tests est un moyen efficace de valider que le code de votre application fonctionne comme prévu. Bien qu'Electron ne maintienne pas activement sa propre solution de test, ce guide va présenter deux façons de faire des tests automatisés de bout en bout sur votre application Electron.
En utilisant l’interface WebDriver
Extrait de: ChromeDriver - WebDriver pour Chrome :
WebDriver est un outil open source pour les tests automatisés d'applications web sur plusieurs navigateurs. Il fournit des fonctions pour naviguer vers les pages web, simuler l'input utilisateur, l’exécution de JavaScript, etc... . ChromeDriver est un serveur autonome qui implémente le protocole de WebDriver (WebDriver's wire protocol) pour Chromium. Il est développé par des membres des équipes chrome et WebDriver.
Il y a plusieurs façons de configurer les tests en utilisant WebDriver.
Avec WebdriverIO
WebdriverIO (WDIO) est un framework d'automatisation de test fournissant un package Node.js pour tester avec WebDriver. Son écosystème comprend également divers plugins (par exemple, reporter et services) qui peuvent vous aider à assembler votre configuration de test.
Installation du lanceur de tests
Vous devez d'abord exécuter le toolkit de démarrage WebdriverIO dans le répertoire racine de votre projet:
- npm
- Yarn
npx wdio . --yes
npx wdio . --yes
Cela installe pour vous tous les packages nécessaires et génère un fichier de configuration wdio.conf.js
.
Connexion de WDIO à votre application Electron
Mettez à jour la propriété "capabilities" de votre fichier de configuration pour pointer vers le binaire de votre application Electron :
exports.config = {
// ...
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
binary: '/path/to/your/electron/binary', // Path to your Electron binary.
args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
}
}]
// ...
}
Lancement des tests
Pour exécuter les tests:
$ npx wdio run wdio.conf.js
Avec sélénium
Selenium est un framework d’automatisation Web exposant des liaisons aux API WebDriver dans de nombreux languages. Les liaisons Node.js sont disponibles dans le package selenium-webdriver
sur NPM.
Démarrage d'un serveur ChromeDriver
Afin d'utiliser Selenium avec Electron, vous devez télécharger le binaire electron-chromedriver
et le lancer :
- npm
- Yarn
npm install --save-dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
yarn add --dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
N'oubliez pas le numéro du port 9515
, qui servira plus tard.
Connexion de Selenium à ChromeDriver
Installez Selenium dans votre projet :
- npm
- Yarn
npm install --save-dev selenium-webdriver
yarn add --dev selenium-webdriver
L'utilisation de selenium-webdriver
avec Electron est pratiquement la même qu’avec les sites Web normaux, la différence est que vous devez spécifier manuellement comment connecter ChromeDriver et où trouver le binaire de votre application Electron:
const webdriver = require('selenium-webdriver')
const driver = new webdriver.Builder()
// "9515" est le port ouvert par le ChromeDriver.
.usingServer('http://localhost:9515')
.withCapabilities({
'goog:chromeOptions': {
// Here is the path to your Electron binary.
binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
}
})
.forBrowser('chrome') // note: use .forBrowser('electron') for selenium-webdriver <= 3.6.0
.build()
driver.get('http://www.google.com')
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver')
driver.findElement(webdriver.By.name('btnG')).click()
driver.wait(() => {
return driver.getTitle().then((title) => {
return title === 'webdriver - Google Search'
})
}, 1000)
driver.quit()
En utilisant Playwright
Microsoft Playwright est un framework de test complet utilisant les protocoles de débogage à distance spécifiques au navigateur, similaire à l’API sans affichage Puppeteer, mais orienté vers une solution globale de test. Playwright prend en charge expérimentalement Electron via la prise en charge par Electron du protocole Chrome DevTools (CDP).
Installation des dépendances
Vous pouvez installer Playwright via votre gestionnaire de packages Node.js préféré. L'équipe Playwright recommande d'utiliser la variable d'environnement PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD
pour éviter les téléchargements inutiles de navigateur lors du test d'une application Electron.
- npm
- Yarn
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn add --dev playwright
Playwright est également livré avec son propre moteur d'exécution de test, Playwright Test, qui est conçu pour mener les tests de bout en bout . Vous pouvez également l'installer en tant que dépendance de dev dans votre projet :
- npm
- Yarn
npm install --save-dev @playwright/test
yarn add --dev @playwright/test
Ce tutoriel a été écrit pour playwright@1.16.3
et @playwright/test@1.16.3
. Consultez la documentation sur les versions de Playwright pour en savoir plus sur les changements qui pourraient affecter le code ci-dessous.
Si vous souhaitez utiliser un lanceur de tests alternatif (par exemple, Jest ou Mocha), consultez le guide Third-Party Test Runner de Playwright.
Rédaction des tests
Playwright lance votre application en mode développement via l'API _electron.launch
. Pour faire pointer cette API vers votre application Electron, vous devez fournir en argument le chemin du point d'entrée de votre processus principal (ici, il s'agira de main.js
).
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('launch app', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
// close app
await electronApp.close()
})
Vous obtiendrez alors une instance de la classe ElectronApp
de Playwright. Il s'agit la d'une classe aux fonctionnalités puissantes ayant accès aux modules du processus principal comme dans l'exemple suivant:
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('get isPackaged', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
const isPackaged = await electronApp.evaluate(async ({ app }) => {
// Cela s'exécute dans le processus principal d'Electron, le parmètre est toujours
// le résultat du require('electron') dans le script main de l'application.
return app.isPackaged
})
console.log(isPackaged) // false (parce que nous sommes en mode développement)
// close app
wait electronApp.close()
})
Cette classe peut également créer des objets Page individuels à partir d’instances de BrowserWindow d'Electron . Par exemple, comme ci-dessous, pour capturer la premiere BrowserWindow et enregistrer une capture d’écran :
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('save screenshot', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// close app
await electronApp.close()
})
En recourant à toutes ces notions, utilisons maintenant le lanceur de test PlayWright Test et créons un fichier de test nommé example.spec.js
qui contient un seul test et une seule assertion :
const { _electron: electron } = require('playwright')
const { test, expect } = require('@playwright/test')
test('example test', async () => {
const electronApp = await electron.launch({ args: ['.'] })
const isPackaged = attendre electronApp. valuate(async ({ app }) => {
// Cela s'exécute dans le processus principal d'Electron, le paramètre ici est toujours
// le résultat du require('electron') dans le script principal de l'application.
return app.isPackaged
})
expect(isPackaged).toBe(false)
// Attends que la première BrowserWindow s'ouvre
// et retourne son objet Page
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// ferme l'app
await electronApp.close()
})
Il faut maintenat exécuter Playwright Test à l’aide de npx playwright test
. Vous devez voir la réussite du test dans votre console et avoir une capture d’écran intro.png
enregistrée dans le système de fichiers.
☁ $ npx playwright test
Exécute 1 test en utilisant 1 worker
✓ example.spec.js:4:1 › exemple test (1s)
Playwright Test exécutera automatiquement tous les fichiers satisfaisant a la regex .*(test|spec)\.(js|ts|mjs)
. Vous pouvez personnaliser l'expression régulière dans les options de configuration comme indiqué dans la documentation correspondante de Playwright Test.
Consultez la documentation de Playwright concernant la classe d' interfaçage avec Electron et les Apis concernant l'application Electron .
En utilisant un pilote de test personnalisé
Il est également possible d'écrire son propre driver personnalisé à l'aide de l'IPC-over-STDIO intégré à Node.js. Les pilotes de test personnalisés exigent que vous écriviez du code d'application supplémentaire, mais nécessitent moins de code et vous permettent d'exposer des méthodes personnalisées à votre suite de test.
Pour créer un pilote personnalisé, nous utiliserons l’API child_process
de Node.js. La suite de tests fengendrera le processus Electron, puis établira un protocole de messagerie basique :
const childProcess = require('node:child_process')
const electronPath = require('electron')
// spawn the process
const env = { /* ... */ }
const stdio = ['inherit', 'inherit', 'inherit', 'ipc']
const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
// listen for IPC messages from the app
appProcess.on('message', (msg) => {
// ...
})
// envoi d'un message IPC à l'application
appProcess.send({ my: 'message' })
Vous pouvez écouter des messages et envoyer des réponses à l’aide de l’API Node.js process
depuis l’application Electron, :
// écoute des messages provenant de la suite de test
process.on('message', (msg) => {
// ...
})
// envoi d'un message à la suite de test
process.send({ my: 'message' })
Nous pouvons maintenant faire communiquer la suite de tests et l’application Electron à l’aide de l’objet appProcess
.
Pour plus de commodité, vous pouvez encapsuler appProcess
dans un objet testeur qui fournira des fonctions de plus haut niveau. Voici un exemple de la façon dont vous pouvez le faire. Commençons par créer la classe TestDriver
:
class TestDriver {
constructor ({ path, args, env }) {
this.rpcCalls = []
// demarrage du processus enfant
env.APP_TEST_DRIVER = 1 // fait savoir à l'application
// qu'elle doit être à l'écoute de messages
this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })
// gestion des réponses rpc
this.process.on('message', (message) => {
// pop the handler
const rpcCall = this.rpcCalls[message.msgId]
if (!rpcCall) return
this.rpcCalls[message.msgId] = null
// reject/resolve
if (message.reject) rpcCall.reject(message.reject)
else rpcCall.resolve(message.resolve)
})
// wait for ready
this.isReady = this.rpc('isReady').catch((err) => {
console.error('Application failed to start', err)
this.stop()
process.exit(1)
})
}
// appel RPC simple
// to use: driver.rpc('method', 1, 2, 3).then(...)
async rpc (cmd, ...args) {
// envoi une requête rpc
const msgId = this.rpcCalls.length
this.process.send({ msgId, cmd, args })
return new Promise((resolve, reject) => this.rpcCalls.push({ resolve, reject }))
}
stop () {
this.process.kill()
}
}
module.exports = { TestDriver }
Dans votre code d'application, vous pouvez alors écrire un gestionnaire simple pour recevoir des appels RPC :
const METHODS = {
isReady () {
// do any setup needed
return true
}
// define your RPC-able methods here
}
const onMessage = async ({ msgId, cmd, args }) => {
let method = METHODS[cmd]
if (!method) method = () => new Error('Invalid method: ' + cmd)
try {
const resolve = await method(...args)
process.send({ msgId, resolve })
} catch (err) {
const reject = {
message: err.message,
stack: err.stack,
name: err.name
}
process.send({ msgId, reject })
}
}
if (process.env.APP_TEST_DRIVER) {
process.on('message', onMessage)
}
Maintenant vous pourrez utiliser votre classe de TestDriver
avec le framework d’automatisation de tests de votre choix. L’exemple suivant utilise ava
, mais d'autres frameworks comme Jest ou Mocha fonctionneront également :
const test = require('ava')
const electronPath = require('electron')
const { TestDriver } = require('./testDriver')
const app = new TestDriver({
path: electronPath,
args: ['./app'],
env: {
NODE_ENV: 'test'
}
})
test.before(async t => {
await app.isReady
})
test.after.always('cleanup', async t => {
await app.stop()
})