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.
Install the test runner
Primero necesitas ejecutar el kit de herramientas de inicio WebdriverIO en el directorio raíz de tu proyecto:
- npm
- Yarn
npx wdio . --yes
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:
exports.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
- 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.
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
- Yarn
npm install --save-dev selenium-webdriver
yarn add --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:
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.
- npm
- Yarn
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn add --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
- Yarn
npm install --save-dev @playwright/test
yarn add --dev @playwright/test
:::precaución Dependencias
Este tutorial fue escrito [email protected]
y @playwright/[email protected]
. 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.
:::
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 ejecuta tu aplicación en modo desarrollo a través de la API _electron.launch
. Para apuntar esta API a su aplicación de Electron puede pasar la ruta al punto de entrada de su proceso principal (en este caso main.js
).
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('launch app', async () => {
const electronApp = await electron. aunch({ args: ['main.js'] })
// cerrar la aplicación
await electronApp.close()
})
Después de eso, tendrás acceso a una instancia de la clase ElectronApp
de Playwright. Esta es una clase poderosa que tiene acceso a módulos del proceso principal, por ejemplo:
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('get isPackaged', async () => {
const electronApp = await electron. aunch({ args: ['main. s'] })
const isPackaged = await electronApp. valuate(async ({ app }) => {
// Esto se ejecuta en el proceso principal de Electron, aquí siempre es
// el resultado del require('electron') en el script principal de la aplicación.
return app.isPackaged
})
console.log(isPackaged) // false (porque estamos en modo de desarrollo)
// cerrar la aplicación
esperar electronApp.close()
})
También puede crear objetos individuales de Página desde instancias de Electron BrowserWindow. Por ejemplo, para tomar la primera ventana del navegador y guardar una captura de pantalla:
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('launch app', async () => {
const electronApp = await electron. aunch({ args: ['main.js'] })
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// cerrar la aplicación
await electronApp.close()
})
Al juntar todo esto usando el ejecutor de pruebas de PlayWright, vamos a crear un archivo de prueba example.spec.js
con una sola prueba y afirmación:
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. valuate(async ({ app }) => {
// Esto se ejecuta en el proceso principal de Electron, aquí siempre es
// el resultado del require('electron') en el script principal de la aplicación.
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()
})
Luego, ejecuta Playwright Test usando npx playwright test
. Deberías ver como pasa la prueba, y tener una intro.png
captura de pantalla en tu sistema de archivos.
☁ $ npx playwright test
Running 1 test using 1 worker
✓ example.spec.js:4:1 › example test (1s)
La prueba de Playwright ejecutará cualquier archivo que coincida con la expresión regular .*(test|spec)\.(js|ts|mjs)
. Puedes personalizar esta coincidencia en las opciones de configuración de la prueba de Playwright.
:::Leer más
Revisa la documentación de Playwright para las APIs completas de clase Electron y ElectronApplication.
:::
Usar un controlador de prueba personalizado
También es posible escribir tu propio controlador personalizado usando el IPC-over-STDIO incorporado de node. js. Los controladores de prueba personalizados requieren que escribas un código de aplicación adicional, pero tienen una carga inferior y le permiten exponer métodos personalizados a su suite de pruebas.
Para crear un controlador personalizado, usaremos la API child_process
de node. js. El conjunto de pruebas generará el proceso de Electron, luego establecerá un protocolo de mensajería simple:
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) => {
// ...
})
// envía un mensaje IPC a la aplicación
appProcess.send({ my: 'message' })
Dentro de la aplicación Electron, puedes escuchar mensajes y enviar respuestas usando la API de Node.js process
:
// escucha mensajes de la suite de pruebas
process.on('message', (msg) => {
// ...
})
// envía un mensaje a la suite de pruebas
process.send({ my: 'message' })
Ahora podemos comunicar desde la suite de test a la aplicación Electron usando el objeto appProcess
.
Por conveniencia, es posible que desees encapsular appProcess
en un objeto de controlador que ofrezca más funciones de alto nivel. Aquí hay un ejemplo de cómo puedes hacer esto. Empecemos por crear una clase de TestDriver
:
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 }
En el código de tu aplicación, puedes escribir un manejador simple para recibir llamadas RPC:
const METHODS = {
isReady () {
// hacer cualquier configuración necesaria
return true
}
// definir tus métodos RPC aquí
}
const onMessage = async ({ msgId, cmd, args }) => {
let method = METHODS[cmd]
if (! ethod) method = () => new Error('Método inválido: ' + cmd)
try {
const resolve = await method(. .args)
proceso. end({ msgId, resolve })
} catch (err) {
const reject = {
message: err.message,
stack: err.stack,
name: err.name
}
process. end({ msgId, reject })
}
}
if (process.env.APP_TEST_DRIVER) {
process.on('message', onMessage)
}
Luego, en tu suite de pruebas, puedes usar tu clase TestDriver
con el framework de automatización de prueba de tu elección. El siguiente ejemplo usa ava
, pero otras opciones populares como Jest o Mocha también funcionan:
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. fter.always('cleanup', async t => {
await app.stop()
})