De Sync a Async
Debido a cambios en V8, el equipo de WebdriverIO anunció desaprobar la ejecución de comandos sincrónicos para abril de 2023. El equipo ha estado trabajando para hacer la transición lo más fácil posible. En esta guía explicamos cómo puedes migrar lentamente tu suite de pruebas de sincronización a asíncrona. Como un proyecto de ejemplo utilizamos el Cupino Boilerplate, pero el enfoque es el mismo con todos los demás proyectos.
Promesas en JavaScript
La razón por la que la ejecución sincrónica era popular en WebdriverIO es porque elimina la complejidad del tratamiento de las promesas. Particularmente si usted viene de otros idiomas donde este concepto no existe de esta manera, puede ser confuso al principio. Sin embargo, las promesas son una herramienta muy poderosa para tratar con código asíncrono y el JavaScript de hoy en día hace que sea realmente fácil abordarlo. Si nunca trabajó con Promises, Recomendamos revisar la guía de referencia MDN a la misma, ya que estaría fuera de ámbito explicarla aquí.
Transición de Async
El testrunner WebdriverIO puede manejar la ejecución asíncrona y sincronizada dentro de la misma suite de pruebas. Esto significa que usted puede migrar lentamente sus pruebas y PageObjects paso a paso. Por ejemplo, el Boilerplate de Cucumber ha definido un gran conjunto de definición de paso para que pueda copiar en su proyecto. Podemos seguir adelante y migrar una definición de paso o un archivo a la vez.
WebdriverIO ofrece un código de código que permite transformar su código de sincronización en código asíncrono casi completo automáticamente. Ejecute primero el código tal y como se describe en la documentación y utilice esta guía para la migración manual si es necesario.
En muchos casos todo lo que es necesario hacer es hacer que la función en la que se llama a los comandos WebdriverIO async
y añadir un await
en frente de cada comando. Mirando el primer archivo clearInputField.ts
para transformar en el proyecto boilerplate nos transformamos de:
export default (selector: Selector) => {
$(selector).clearValue();
};
para:
export default async (selector: Selector) => {
await $(selector).clearValue();
};
Eso es todo. Puede ver el commit completo con todos los ejemplos de reescritura aquí:
Commits:
- transpila definiciones de pasos [af6625f]
Esta transición es independiente de si utiliza TypeScript o no. Si usa TypeScript, asegúrese de cambiar eventualmente la propiedad tipos
en su tsconfig.json
de webdriverio/sync
a @wdio/globals/types
. También asegúrese de que su objetivo de compilación está establecido en al menos ES2018
.
Casos especiales
Por supuesto, siempre hay casos especiales en los que hay que prestar un poco más de atención.
ForEach Loops
Si tienes un bucle forEach
, p.e. para iterar sobre los elementos, es necesario asegurarse de que el callback del iterador se maneja correctamente de forma asíncrona, por ejemplo.:
const elems = $$('div')
elems.forEach((elem) => {
elem.click()
})
La función que pasamos a forEach
es una función de iteración. En un mundo sincrónico haría clic en todos los elementos antes de avanzar. Si transformamos esto en código asíncrono, tenemos que asegurarnos de esperar a que cada función iteradora finalice la ejecución. Al añadir async
/espera
estas funciones del iterador devolverán una promesa que necesitamos resolver. Ahora, forEach
no es ideal para iterar sobre los elementos ya que no devuelve el resultado de la función iterador, la promesa que tenemos que esperar. Por lo tanto necesitamos reemplazar por cada
por mapa
que devuelve esa promesa. The map
as well as all other iterator methods of Arrays like find
, every
, reduce
and more are implemented so that they respect promises within the iterator functions and are therefor simplified for using them in an async context. El ejemplo anterior se ve transformado así:
const elems = await $$('div')
await elems.forEach((elem) => {
return elem.click()
})
Por ejemplo, para obtener todos los elementos <h3 />
y obtener su contenido de texto, puedes ejecutar:
await browser.url('https://webdriver.io')
const h3Texts = await browser.$$('h3').map((img) => img.getText())
console.log(h3Texts);
/**
* returns:
* [
* 'Extendable',
* 'Compatible',
* 'Feature Rich',
* 'Who is using WebdriverIO?',
* 'Support for Modern Web and Mobile Frameworks',
* 'Google Lighthouse Integration',
* 'Watch Talks about WebdriverIO',
* 'Get Started With WebdriverIO within Minutes'
* ]
*/
Si esto parece demasiado complicado, puede que quiera considerar el uso simple para bucles, por ejemplo.:
const elems = await $$('div')
for (const elem of elems) {
await elem.click()
}
Asserciones WebdriverIO
Si utiliza el ayudante de aserción WebdriverIO expect-webdriverio
asegúrese de establecer una espera
delante de cada espera
llamada, ej.:
expect($('input')).toHaveAttributeContaining('class', 'form')
necesita ser transformado en:
await expect($('input')).toHaveAttributeContaining('class', 'form')
Sincronizar métodos de PageObject y pruebas de sincronización
Si ha estado escribiendo PageObjects en su suite de pruebas de una manera sincrónica, ya no podrá utilizarlos en pruebas asincrónicas. Si necesita utilizar un método PageObject en pruebas de sincronización y asíncrona, recomendamos duplicar el método y ofrecerlos para ambos entornos, ej.:
class MyPageObject extends Page {
/**
* define elements
*/
get btnStart () { return $('button=Start') }
get loadedPage () { return $('#finish') }
someMethod () {
// sync code
}
someMethodAsync () {
// async version of MyPageObject.someMethod()
}
}
Una vez que haya finalizado la migración, puede eliminar los métodos sincrónicos de PageObject y limpiar el nombre.
Si no desea mantener dos versiones diferentes de un método PageObject también puede migrar todo el PageObject a asíncrona y utilizar browser. all
to execute the method in a synchronous environment, e.g.:
// before:
// MyPageObject.someMethod()
// after:
browser.call(() => MyPageObject.someMethod())
El comando call
se asegurará de que el asincrónico someMethod
se resuelva antes de pasar al siguiente comando.
Conclusión
Como puedes ver en la reescritura PR resultante la complejidad de esta reescritura es bastante fácil. Recuerda que puedes reescribir una definición de paso en ese momento. WebdriverIO es perfectamente capaz de manejar la ejecución sincronizada y asíncrona en un único framework.