Saltar al contenido principal

Pruebas automatizadas

La automatización de pruebas es una manera eficiente de validar que el código de la aplicación funciona como se pretende. Mientras Electron no mantiene activamente su propia solución de pruebas, esta guía recorrerá un par de maneras en que puedes ejecutar pruebas automatizadas de extremo a extremo en tu aplicación Electron.

Usando la interfaz WebDriver

Para ChromeDriver - WebDriver para Chrome:

WebDriver es una herramienta de código abierto para pruebas automatizadas de aplicaciones web en varios navegadores. Provee la capacidad de navegar por páginas web, sistema de usuarios, ejecución de JavaScript, y más. ChromeDriver es un servidor independiente que implementa el protocolo de cable de WebDriver para Chromium. Ha sido desarrollado por miembros de los equipos de Chromium y WebDriver.

Hay algunas maneras en que puedes configurar las pruebas usando WebDriver.

Con WebdriverIO

WebdriverIO (WDIO) es un framework para automatización de pruebas que provee un paquete Node.js para pruebas con WebDriver. Su ecosistema además incluye varios complementos (p. ej. reportes y servicios) que pueden ayudarte a armar su configuración de prueba.

Instalar el testrunner

Primero necesitas ejecutar el kit de herramientas de inicio WebdriverIO en el directorio raíz de tu proyecto:

npx wdio . --yes

Esto instala todos los paquetes necesarios para ti y genera un archivo de configuración wdio.conf.js.

Conecta WDIO a tu aplicación Electron

Actualiza las capacidades en tu archivo de configuración para apuntar al binario de tu aplicación Electron:

wdio.conf.js
export.config = {
// ...
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
binary: '/path/to/your/electron/binary', // Ruta a tu binario de Electron.
args: [/* cli arguments */] // Opcional, tal vez 'app=' + /ruta/hacia/tu/aplicación/
}
}]
// ...
}

Ejecutar tus pruebas

Para ejecutar tus pruebas:

$ npx wdio run wdio.conf.js

Con Selenium

Selenium es un framework de automatización web que expone enlaces a las APIs de WebDriver en muchos lenguajes. Sus enlaces Node.js están disponibles bajo el paquete selenium-webdriver en NPM.

Ejecurar un servidor ChromeDriver

Para usar Selenium con Electron, necesitas descargar y ejecutar el binario electron-chromedriver:

npm install --save-dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.

Recuerde el puerto número 9515, que usaremos más adelante.

Conectar Selenium a ChromeDriver

A continuación, instalar Selenium dentro de tu proyecto:

npm install --save-dev selenium-webdriver

El uso de Selenium-web driver con Electron es el mismo que con sitios normales, excepto que tienes tiene que especificar manualmente como conectar el ChromeDriver y donde se encuentra el binario o ejecutable de Electron:

test.js
const webdriver = require('selenium-webdriver')
const driver = new webdriver.Builder()
// El "9515" es el puerto abierto por el 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()

Usando Playwright

Microsoft Playwright es un framework de pruebas de extremo a extremo construido usando protocolos de depuración remotos específicos del navegador, similar a la API Node.js de Puppeteer "headless" pero dirigida hacia pruebas de extremo a extremo. Playwright tiene soporte experimental de Electron a través del soporte de Electron para el Protocolo de Chrome DevTools (CDP).

Instala las dependencias

Puede instalar Playwright a través de su gestor de paquetes Node.js preferido. El equipo de Playwright recomienda usar la variable de entorno PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD para evitar descargas innecesarias del navegador al probar una aplicación Electron.

PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright

Playwright también viene con su propio test runner, Playwright Test, que está construido para pruebas de punto a extremo. También puedes instalarlo como una dependencia de desarrollo en su proyecto:

npm install --save-dev @playwright/test

:::precaución Dependencias

Este tutorial fue escrito playwright@1.16.3 y @playwright/test@1.16.3. Echa un vistazo a la página de versiones de Playwright para aprender sobre cambios que podrían afectar el código a continuación.

:::

Usando test runners de terceros

Si estás interesado en usar un test runner alternativo (por ejemplo, Jest o Mocha), echa un vistazo a la guía de Playwright Test Runners de Terceros .

Escribe tus tests

Playwright launches your app in development mode through the _electron.launch API. To point this API to your Electron app, you can pass the path to your main process entry point (here, it is 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()
})

After that, you will access to an instance of Playwright's ElectronApp class. This is a powerful class that has access to main process modules for example:

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 }) => {
// This runs in Electron's main process, parameter here is always
// the result of the require('electron') in the main app script.
return app.isPackaged
})
console.log(isPackaged) // false (because we're in development mode)
// close app
await electronApp.close()
})

It can also create individual Page objects from Electron BrowserWindow instances. For example, to grab the first BrowserWindow and save a screenshot:

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()
})

Putting all this together using the PlayWright Test runner, let's create a example.spec.js test file with a single test and assertion:

example.spec.js
const { _electron: electron } = require('playwright')
const { test, expect } = require('@playwright/test')

test('example test', async () => {
const electronApp = await electron.launch({ args: ['.'] })
const isPackaged = await electronApp.evaluate(async ({ app }) => {
// This runs in Electron's main process, parameter here is always
// the result of the require('electron') in the main app script.
return app.isPackaged;
});

expect(isPackaged).toBe(false);

// Wait for the first BrowserWindow to open
// and return its Page object
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })

// close app
await electronApp.close()
});

Then, run Playwright Test using npx playwright test. You should see the test pass in your console, and have an intro.png screenshot on your filesystem.

☁  $ npx playwright test

Running 1 test using 1 worker

✓ example.spec.js:4:1 › example test (1s)
info

Playwright Test will automatically run any files matching the .*(test|spec)\.(js|ts|mjs) regex. You can customize this match in the Playwright Test configuration options.

Further reading

Check out Playwright's documentation for the full Electron and ElectronApplication class APIs.

Using a custom test driver

It's also possible to write your own custom driver using Node.js' built-in IPC-over-STDIO. Custom test drivers require you to write additional app code, but have lower overhead and let you expose custom methods to your test suite.

To create a custom driver, we'll use Node.js' child_process API. El conjunto de pruebas generará el proceso de Electron, luego establecerá un protocolo de mensajería simple:

testDriver.js
const childProcess = require('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) => {
// ...
})

// envía un mensaje IPC a la aplicación
appProcess.send({ my: 'message' })

From within the Electron app, you can listen for messages and send replies using the Node.js process API:

main.js
// listen for messages from the test suite
process.on('message', (msg) => {
// ...
})

// send a message to the test suite
process.send({ my: 'message' })

Ahora podemos comunicar desde la suite de test a la aplicación Electron usando el objeto appProcess.

For convenience, you may want to wrap appProcess in a driver object that provides more high-level functions. Here is an example of how you can do this. Let's start by creating a TestDriver class:

testDriver.js
class TestDriver {
constructor ({ path, args, env }) {
this.rpcCalls = []

// start child process
env.APP_TEST_DRIVER = 1 // let the app know it should listen for messages
this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })

// handle rpc responses
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)
})
}

// simple RPC call
// to use: driver.rpc('method', 1, 2, 3).then(...)
async rpc (cmd, ...args) {
// send rpc request
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 };

In your app code, can then write a simple handler to receive RPC calls:

main.js
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)
}

Then, in your test suite, you can use your TestDriver class with the test automation framework of your choosing. The following example uses ava, but other popular choices like Jest or Mocha would work as well:

test.js
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()
})