Chrome

Charla Polymer en 3er Betabeers Guadalajara

Recientemente tuve el privilegio de poder exponer una charla sobre Polymer en la tercera edición de Betabeers Guadalajara @betabeersGUADA, que es el capítulo alcarreño del conocido evento de desarrolladores, startupters y cervezas español.

La iniciativa de Guadalajara no podría llevarse a cabo sin la gente de Beperk (@beperk), a los que agradezco enormemente la organización de estos eventos. No es fácil en una ciudad pequeña y tan influenciada por Madrid reunir a los pocos e incomunicados desarrolladores que dormimos en las afueras de la Alcarria y encima que nos lo pasemos bien y nos vayamos conociendo. Seguro que con el tiempo sale algo grande de ahí, aunque ya de momento merece mucho la pena.

Algunas de las cosas que pasaron en la charla se pueden revisitar en este hastag:#bbGuada y en el twitter oficial de @betabeersGUADA

Un señor presentando Polymer

Gracias por la foto @SergioCC14

Volviendo al tema de Polymer…

En la charla pude desarrollar en vivo los conceptos que expuse en un post de este mismo Blog sobre la introducción a Polymer. Puedes encontrar el ejemplo práctico paso a paso en este enlace: Introducción a Polymer

También (y sobre todo) recomiendo la página oficial del proyecto Polymer (https://www.polymer-project.org/, que es tremendamente didáctica. Además de la documentación clásica, se incluyen ejemplos paso a paso muy sencillos de seguir. En la sección de Vídeos puedes ver las presentaciones de Google I/O 2013 y 2014, que son maravillosas y lo explican todo muy fácil (además en un inglés muy clarito). Finalmente también recomiendo la página oficial de WebComponents (http://webcomponents.org/) donde se puede abstraer un nivel más arriba de Polymer y consultar las novedades y artículos sobre este conjunto de estándares para HTML5. Las secciones Articles y Presentations son imprescindibles.

Las transparencias de la presentación y comentarios (escritos con el tono coloquial de presentación y no de redacción -perdón por las expresiones-) aparecen a continuación:

Polymer - Betabeers1

Hola a todos, gracias por venir y gracias a los organizadores de Beperk que hace esta charla posible.

Mi nombre es Alberto Moratilla y os voy a hablar de una tecnología que probablemente sea protagonista en los pŕoximos años en el desarrollo Web: Polymer y los WebComponents.

Esta charla surgió por una entrada que escribí en mi blog, que utilizo para explorar tecnologías que no tengo la oportunidad de explorar en mi vida profesional y conviene conocer para no quedarse atrás. No siempre uno puede utilizar las tecnologías que más le gustan sobre todo si son demasiado novedosas como es el caso de Polymer.

Por tanto no soy ningún experto en Polymer, y tampoco creo que abunden porque está todavía en fase expertimental (aún va por la versión 0.5.0 si no recuerdo mal), asi que espero no meter mucho la pata.

Polymer - Betabeers2

En los últimos tiempos se está produciendo un trasvase de funcionalidad desde el back-end al front-end. Hasta hace poco tiempo se procuraba que la lógica de la aplicación residiera en el servidor, utilizando el cliente para la visualización de la información e interacción básica que desencadenaba acciones que se llevaban a cabo en el servidor. En frameworks como JSF, Struts o Spring MVC el programador acaba creando páginas en servidor que son procesadas para ser enviadas al cliente sobre las que no se suele ejercer un control final.

Con la aparición de HTML5 con sus API y la mejora de JavaScript (ECMAScript), y de los intérpretes de los navegadores, en especial gracias al motor V8 de Google, se está trasladando el protagonismo de la aplicación hacia el navegador: aplicaciones en navegador potentes que se comunican con el servidor para el intercambio de datos (servicios REST) usando AJAX y JSON. Es lo que se conoce como Single Page Application o Web Application: una página Web HTML que contiene bibliotecas JavaScript que le permiten capacidades de comunicación con el servidor y de manipulación del DOM para crear una experiencia de usuario superior. De este modo el servidor queda para implementar la lógica de negocio y proveer de unos puntos de acceso a información utilizados por la aplicación Web.

Polymer - Betabeers3

Como la complejidad en el lado del cliente iba creciendo, el código cada vez era más complejo y se debían encontrar forma de estructurarlo al menos como se hace en el back-end. Empezaron a surgir frameworks para JavaScript (y sus evoluciones en forma de ECMAScript).

Algunos ejemplos podemos verlos en este gráfico de demanda de empleos en tecnología, como son EmberJS o BackboneJS. Su objetivo es facilitar el desarrollo de aplicaciones más serias que los fragmentos de script típicos que se suelen incluir en las páginas Web clásicas. Cualquiera que hayáis tenido la oportunidad de hacer alguna página con efectos de JQuery, validación, envío por AJAX de forma más o menos manual pero con JQuery detrás habréis tenido la sensación de que al final se os va un poco de las manos.

Cabe destacar en este gráfico el triunfo de AngularJS sobre los demás. Es un framework para SPA creado por un grupo de desarrollo de Google y que está apostando bastantes recursos para que triunfe. Sobre todo gracias a su relación con Google Chrome. Ahora muchas empresas se están planteando dar el salto de soluciones típicas de JSF o incluso GWT a este framework. Si no lo conocéis o si lo conocéis y os gusta el mundo del desarrollo de Front-end merece mucho la pena que profundicen porque es una apuesta segura.

Polymer - Betabeers4

Una vez descrito este “estado del arte” vamos con Polymer, que se situaría en un nivel por encima de los frameworks vistos anteriormente.

Polymer no es un framework o una librería más que hace uso de HTML5 que pueda tener cierto éxito dependiendo de que se haya puesto de moda en ciertas comunidades o esté siendo utilizado por alguna compañía cool del momento. Polymer es la implementación que Google ha hecho de los WebComponents. Hay otra implementación destacada, que es X-Tag de mozilla.

Por lo tanto la parte valiosa de esta presentación son los Web Components y cómo son implementados por Polymer. Veréis que además de una parte técnica de implementación, lo que es realmente importante es ver cómo cambia la filosofía del desarrollo de aplicaciones web complejas en el navegador.

Como digo no creo que sea una moda pasajera o algo que no llegue a implementarse. Me extrañaría mucho viendo las características que tiene, y el esfuerzo que están haciendo en Google para su desarrollo y utilización (hay productos internos que los utilizan). O el hecho de que W3C esté desarrolando los estándares en los que se basan los Web Components.

Polymer - Betabeers5

Pues como su nombre indica, son componentes que conforman una página Web. A mí me gusta mucho el ejemplo que suelen poner de un combobox de HTML normal y corriente (un select – option a nivel de tag).

Un título H1 o un enlace en HTML tienen una representación sencilla y no parecen hacer mucho, pero cuando escribimos un select con options, el navegador renderiza algo más complejo. Además es algo que tiene un estado que podemos consultar o manipular con Javascript. Por tanto parece un componente con cierta complejidad que usamos en formularios normalmente. Además dispone de un tag específico en HTML lo que facilita enormemente su reutilización.

Polymer - Betabeers6

Pero desafortunadamente este caso es prácticamente un caso aislado dentro de HTML. Además, HTML no nos provee de los suficientes elementos que necesitamos. Primero porque cada uno solemos llegar a un extremo en el diseño en el que tenemos que emplear controles o partes personalizadas (desde controles hasta menús y otros elementos). Y segundo porque los diseños van cambiando.

Es necesario por lo tanto tener un sistema en el que podamos crear estos componentes por nosotros mismos, facilitando la reutilización y la interoperabilidad.

La alternativa actual suele estar relacionada con JQuery UI o tecnologías similares, que arrastran una serie de dependencias. Por ejemplo aquí vemos un control de tipo Slider con jQuery UI.

Podemos ver cómo se hace uso de tag divs con los ID y de funciones de Javascriptm para dotarlo de funcionalidad. Es una opción válida y fantástica en su momento pero cuando las cosas se empiezan a complicar es bastante engorroso. ¿Podemos ver el contenido de una página Web sabiendo sólo su código? ¿Viendo que sólo hay divs con ciertos ID o clases?

Polymer - Betabeers7

Lo que vemos ahora en pantalla es un control equivalente, de tipo slider usando polymer.

Lo primero que vemos es que tenemos un tag especial que no está definido en HTML5, sino que tiene pinta de que álguien particular lo ha elegido. Además los atributos también son específicos del tag.

Debajo vemos la representación del slider. Quizá os suene un poco si habéis visto alguna pantalla hecha con Material Design de Google. Una pena que sólo haya puesto una imagen porque se puede ver que tiene animaciones bastante curiosas.

Debajo podemos ver lo que realmente conlleva una vez se está utilizando, pero ojo! no es el código que nosotros manejamos. A menos que queramos modificar o extender el componente. Nosotros a la hora de trabajar únicamente utilizamos el código superior.

Como podemos ver se parece bastante al ejemplo del combobox con select-option de HTML que hemos visto antes. Una solución elegante y encapsulada.

Por tanto los webComponents son componentes encapsulados y reutilizables que están compuestos por código HTML5, una parte de Javascript y CSS, pero todo ello encapsulado detrás de un tag con un nombre específico.

Ya podéis imaginar que estos elementos los podemos esciribir nosotros po podemos utilizar elementos de terceros. Un ejemplo claro es el que nos provee Google con sus Paper Elements u otras páginas en las que desarrolladores ofrecen los suyos. También podéis imaginar que la cantidad de componentes web será casi infinita con tanta gente colaborando, y que cubrirán casi todas nuestras necesidades. Y sino es muy fácil hacer los nuestros.

Polymer - Betabeers8

Para soportar los WebComponents se han creado 4 estándares de W3C que deberían implementar los navegadores (tal y como hace Chrome) o al menos se deberían soportar a base de Polyfills (que es un fragmento de código Javascript que suple las carencias del navegador (ejemplo cĺásico es el dotar a bordes redondeados a Internet Explorer 8 mediante un script CSS por ejemplo).

Son cuatro tecnologías:

  • Custom Elements: para poder definir nuevos tags en el código, tal y como hemos visto. Esto tags están en minúsculas y tienen que tener obligatoriamente un guión. Así los podemos distinguir de los tags normales.
  • HTML imports: nos permiten incrustar el código de un fichero HTML dentro de otro con un tag de tipo link: . Igual que hacmos un link de css o un tag script de Javavascript, que se pega el contenido tal cual y el navegador lo procesa como un todo.
  • Templates: la capacidad para definir plantillas de código HTML + Javascript + CSS que se aplican a nuestro elementos. Lo hemos visto en el código que realmente se está utilizando en un slider pero que nosotros no vemos como desarrolladores hasta que lo inspeccionamos con la herramienta de depuración.
  • Shadow DOM. Independizar el template o el código que genera del resto de la página, y además está preparado para que un nodo del árbol DOM pueda albergar otros subárboles DOM (expansión). De este modo se evitan los conflictos con otros elementos. ¿Cuántas veces usando JQuery no se ha armado un Belén al coincidir los atributos class, id; las CSS, etcétera. Elimina los efectos laterales. El código de los componentes está aislado completamente. Se podría decir que se establecen unos límites en el arbol DOM.

Más info en: http://webcomponents.org/

Polymer - Betabeers9

Creo que con esto más o menos nos hemos hecho una idea de lo que son los componentes Web. Ahora cuando veamos un ejemplo usando la implementación de Polymer nos quedará más claro. Pero básicamente se podría resumir en:

  • Encapsulación: se tiene un componente con HTML + Javascript + CSS que queda completamente sellado y listo para utilizar. Como una pieza dentro de un engranaje. Ella sola se basta para funcionar (aunque claro, dependiendo del tipo de componente igual no tiene sentido por sí sola, como por ejemplo unos Tabs que no tienen contenido).
  • Interacción: tienen una interfaz para comunicarse y configurarse. A través de javascript podemos manipular su contenido, leerlo, conectarlo con otros elementos…
  • Reutilización: pues derivado de lo anterir, están listo para ser utilizados. Incluso los que no hemos creado nosotros son muy sencillos de reutilizar. Gracias a los templates y al shadow DOM no se “pegan” con otras partes de la Web. Seguro que cuando habéis pegado un componente complejo de Javavascript en vuestro diseño tenéis que pasar un tiempo adaptando.
  • Catálogo de componentes reutilizables como el caso de los Paper Elements de Polymer o los de componente.kitchen. La gente publica sus componentes y podemos reutilizarlos por nuestra parte.

Polymer - Betabeers10

Vamos ahora con Polymer, que como habéis visto ya es la implementación que Google propone para los Web Components. Junto con X-Tags (pero éstos en menor medida), son los Web Componentes más interesantes.

Como características más destacadas:

  • Para nuevos navegadores. Es Chrome el que tira del carro de estas novedades. Google se lo está trabajando mucho, de modo que hay una comunicación continua entre los equipos de desarrollo de los nuevos frameworks como AngularJS o Polymer con el equipo de desarrollo de Chroime. Lo podéis ver por ejemplo en el desarrollo de las herramientas devtools de Chrome, que están todo el día metiendo novedades.
  • Es algo reciente (2013). En Youtube tenéis los vídeos de presentación de Google I/O 2013 y muchos otros comentando polymer que seguro que lo hacen mejor que yo.
  • Existe cierta compatibilidad hacia atrás a través de código Javascript gracias a los Polyfills. Aún así esta tecnología tenéis que tener cuidado a la hora de utilizarla porque no está madura (versión 0.5) y compatibilidad no muy alta. Quizá sea algo demasiado experimental, aunque hay algunas páginas que empiezan a usarlo (como GitHub).
  • Dispone de un catálogo de elementos basados en material design (Android 5) pero para la Web.

Polymer - Betabeers11

Polymer cuenta actualmente con dos bibliotecas de elementos. Por una parte los “Core elements” y por otra los “Paper elements”. Si váis a la página del Proyecto Polymer podréis usarlos.

Tenemos:

  • Core elements: para la funcionalidad básica que se espera en una página web de forma independiente a su apariencia. Se trata de componentes básicos como llamadas Ajax, y efectos de animaciones como Collapse, iconos, barras… estructuración de la página, etc…
  • Paper elements, que es la implementación del Material Design usando Web Components que es la apariencia que Google ha adoptado a partir de Android 5.0

Como digo en la página de Polymer hay unas demos del funcionamiento de cada uno de ellos así como la documentación para utilizarlo, que ya sabéis que es del tipo: pongo el tag, añado unos atributos y listo.

Polymer - Betabeers12

Como ya había dicho antes, todavía es un proyecto que está en desarrollo y que tiene que mejorar sobre todo en compatibilidad con los navegadores que lo soportan. Esto es un trabajo más de los navegadores que de polymer.

Hay dos versiones: una es la versión nativa que cuenta con la compatibilidad sobre todo de Google Chrome (webkit) en sus diferentes versiones y alguno más como firefox por ejemplo. Esta información está sacada de la página de Polymer en la vesión 0.5.0. Es posible que otros navegadores también soporten Web Components de forma específica. sin que aparezcan. Esto hay que verlo en su momento.

Polymer - Betabeers13

Voy a intentar hacer una pequeña demo de Polymer si los medios y el directo acompaña.

Voy a utilizar Yeoman + Grunt + Bower, aunque en la página Web de Polymer os podéis bajar un ZIP que ya lleva todo lo necesario para empezar.

Estos tres elementos me van a permitir:

  • YO (Yeoman): descargar un esqueleto de la aplicación con todo montado.
  • Grunt: correr un servidor para que podamos ver la demo (no funciona en el sistema de ficheros), y si quisiera generar una versión para distribuir con las hojas de estilo optimizadas, código Javascript ofuscado, corriendo test, etcétera.
  • Bower: controlar las dependencias: si quiero añadir más librerías por ejemplo.

Vamos con ello.

* En este punto se puede continuar con el ejemplo descrito paso a paso en el post: Introducción a Polymer. En él se puede ver cómo se instala Yeoman en Ubuntu 14.04 y cómo se desarrolla el ejemplo del componente contador que realicé en la presentación de Betabeers Guadalajara

Polymer - Betabeers14

Bootstrap 3: ¿Qué tipo de grid usa cada dispositivo?

Una de las mejores novedades que trae Bootstrap 3 es la capacidad para adaptar su sistema de grid a los diferentes dispositivos y así facilitar los diseños “responsive” con muy poco coste en tiempo y esfuerzo. De este modo clasifican sus elementos de grid con un sufijo, dependiendo del dispositivo que se trate:

  • XS: Extra small devices Phones (<768px)
  • SM: Small devices Tablets (≥768px)
  • MD: Medium devices Desktops (≥992px)
  • LG: Large devices Desktops (≥1200px)

Puedes ver más detalles sobre cada uno y cómo utilizarlo en http://getbootstrap.com/css/#grid-options

El problema está en saber qué es un dispositivo small, large o medium, o qué resolución va a usar cada uno. El criterio más objetivo debería ser la resolución, pero tenemos tablets con resoluciones de 1024×768 y 10 pulgadas, y tenemos móviles de 4 pulgadas full HD (1920×1080). ¿En un móvil de 4 pulgadas se debería adaptar a los “Large devices Desktop (>1200px) mientras que la tablet sería un “Medioum Desktop Desktop >992px?

El resultado es que cuando hago un diseño y lo pruebo en mi Nexus 4 (1280px), usa el modo XS, pero cuando lo pruebo en mi Asus TF101 (1024px), usa MD, a pesar de tener menos resolución de pantalla. La razón es que dependiendo de cada dispositivo se utiliza un factor multiplicador de modo que compensa el aumento de resolución de pantalla con el tamaño real en centímetros. Así, un iPad normal (1024) y uno con Retina Display (2048) muestran del mismo tamaño los elementos, pero el segundo con el doble de resolución.

Como en realidad lo que queremos es que nuestro diseño se adapte perfectamente a los dispositivos, lo mejor es saber qué modo de Bootstrap 3 utiliza cada uno.

Utilizando Google Chrome Canary (una versión de Chrome para desarrolladores con herramientas especiales), se puede emular correctamente una buena cantidad de dispotivos móviles usando Google Chrome. Mi comprobación empírica es que es fiable totalmente. Para comprobarlo por ti mismo puedes seguir estas instrucciones: https://developers.google.com/chrome-developer-tools/docs/mobile-emulation

Probando los más significativos he dado con este resultado usando Chrome para estos dispositivos:

  • Amazon Kindle Fire HD 7″ – SM
  • Amazon Kindle Fire /Fire HD 8.9″ – LG
  • BB PlayBook – MD
  • BB Z10 / Z30 – XS
  • HTC Evo, Touch HD, Desire HD, Desire – XS
  • IPAD 1/2/iPad Mini – MD
  • IPAD 3/4 – MD (En prueba real he podido comprobar que en realidad es SM…)
  • IPhone 3GS/4/5 – XS
  • Nexus 10 – LG
  • Nexus 7 / 7.2 – SM
  • Nexus S/4/5 – XS
  • Samsung Galaxy Note / S3 / S4 – XS
  • Sony Xperia S / Z – XS

O por la otra dimensión:

  • XS: BB Z10 / Z30; HTC Evo, Touch HD, Desire HD, Desire; IPhone 3GS/4/5; Nexus S/4/5; Samsung Galaxy Note / S3 / S4; Sony Xperia S
  • SM: Amazon Kindle Fire HD 7; Nexus 7 / 7.2
  • MD: BB PlayBook; IPAD 1/2/iPad Mini; IPAD 3/4
  • LG: Amazon Kindle Fire /Fire HD 8.9″; Nexus 10

* Estos datos están sacados del emulador de dispositivos de Chrome Canary. Se deberían probar en los dispositivos reales para estar completamente seguros…

Testing end to end (e2e) con WebdriverJS + NodeJS + Mocha + should.js

El testing en el desarrollo de software es una de las corrientes que más éxito está teniendo en los últimos tiempos. Las nuevas metodologías de desarrollo basado en test (TDD) o en comportamiento (BDD) permite crear aplicaciones más robustas y centradas en los requisitos más que en el contenido propio del código.

Dentro del testing se podría hacer una clasificación simple entre tres tipologías de test:

  • El testing unitario o de componente, se ocupa de probar las partes individuales en las que se puede dividir un código: si una clase o métodos funcionan correctamente o una parte determinada del programa. Ayuda a desarrollar partes concretas del código pero que no tienen que ver de forma directa con lo que el usuario espera del producto final. Por ejemplo podemos hacer un test para comprobar que un generador de números aleatorios funciona correctamente, pero solamente se comprueba una parte del sistema de seguridad que lo utiliza y que es lo que finalmente le interesa al usuario.
  • El testing de integración, para comprobar que los componentes reaccionan de forma adecuada a la hora de unirse. Es decir, controlan las interfaces entre los componentes o subsistemas.
  • El testing de fin a fin (end to end o e2e) es el que se ocupa de probar la aplicación final “de principio a fin”. Conceptualmente tiene mucho más que ver con lo que percibe el usuario del producto, que es al fin y al cabo la misión de nuestros programas: satisfacer al usuario y sus requisitos. Aspectos como el funcionamiento, el rendimiento o en consumo de recursos pueden ser comprobados con este tipo de test. En una página Web es especialmente relevante ya que debe responder de forma adecuada a todos los eventos de navegación que se producen en ella.

Una cosa que no debemos olvidar es que el testing solamente prueba que un software falla bajo determinadas condiciones, pero no indica que es totalmente fiable. Que un software funcione un número consistente de veces bajo unas condiciones no quiere decir que exista una posibilidad, aunque remota, de que falle bajo esas mismas condiciones. Se trata de que el número de fallos sea el menor posible, pero que si no falla, puede que lo haga en el futuro, sobre todo porque quizá no hayamos conseguido las condiciones de test adecuadas sobre las que se produce el fallo.

Dentro de los test “end to end”  (e2e),  están los test de comportamiento, que se encargan de comprobar que el programa (o aplicación Web en nuestro caso) funciona de forma correcta en cuanto al comportamiento: hacen pruebas como si se tratara de un usuario real. Es evidente que los test de comportamiento son muy adecuados para las aplicaciones Web actuales en las que la interacción juega un papel muy importante.

Un ejemplo de test de comportamiento podría ser el siguiente:

  • Accede a la página http://www.rusizate.com
  • Comprueba que el título de la página contiene la cadena de texto “Rusizate” [test1]
  • Localiza el botón de menú que da acceso a los contenidos para turistas y que tiene la clase “boton-189”.
  • Pulsa sobre el botón seleccionado
  • Comprueba que el título de la página que aparece contiene el texto “turistas” [test2]
  • etcétera…

La descripción del test debería ser lo más exhaustiva posible. Cuantos más aspectos probemos, más seguros estaremos de que la aplicación cumple con los requisitos que se han establecido, y también podremos dirigir nuestros desarrollos de un modo más adecuado a las necesidades del usuario final.

Para hacer los test end to end de nuestras aplicaciones web basadas en comportamiento se utilizará Selenium – Webdriver, que prové de una interfaz para poder manejar los navegadores más populares. Cada navegador dispone de un “driver” para que puedan ser manipulados por código como si de un usuario final se tratase. De este modo Internet Explorer, Chrome, Firefox, Safari u Opera disponen de drivers para que puedan ser utilizados por Webdriver.

Existen diferentes lenguajes para usar Webdriver: Java, Python o Javascript. Bajo mi punto de vista lo mejor es utilizar un lenguaje interpretado que pueda ser editado fácilmente. Los lenguajes compilados requieren el paso intermedio de compilación a ejecutable (o bytecode en Java), lo que ralentiza excesivamente la creación y depuración de los test. Los test no suelen ser programas complejos, sino que son una enumeración de los puntos que se deben cumplir, por lo que no es necesario contar con la potencia y complejidad de lenguajes compilados.

Un lenguaje intepretado que se puede utilizar con Webdriver es Javascript, que gracias a NodeJS, basado en el motor V8 de Google, está ganando enteros dentro del mundo del desarrollo. Gracias a NodeJS es posible ejecutar Javascript en la parte del cliente con un buen rendimiento, así como aprovecharse de su sistema de librerías NPM (Node Package Manager), que facilita la instalación y utilización de nuevas características que extienden la potencia de este entorno.

Volviendo a Selenium-Webdriver, existe un paquete descargable a través de NPM que instala todo lo que necesitamos de selenium-webdriver. Además deberemos descargar el driver de Chrome, llamado Chromedriver.exe, que será la interfaz a partir de la cual se manejará Google Chrome para realizar nuestras pruebas e2e. También vamos a utilizar Mocha, que es un framework de testing en Javascript, que facilitará la realización de nuestras pruebas, a través de funciones y comprobaciones que lleva incorporadas. Para la comprobación de los asserts de los test se empleará should.js, también disponible a través del sistema NPM.

Preparando el entorno

Prerequisitos:

  • Tener instalada una versión reciente de nodejs
  • Acceso a una shell.

Creando el directorio e instalando librerías:


mkdir testing
cd testing

npm install -g mocha
npm install selenium-webdriver
npm install should

Instalando el driver para Google Chrome

1. Acceder a http://chromedriver.storage.googleapis.com/index.html y descargar la última versión correspondiente al sistema operativo.

2. Hacer que el chromedriver.exe esté en el path del sistema: en windows hay que ir a panel de control->sistema->variables de entorno

3. Reiniciar la shell para que admita el nuevo cambio.

Creando el fichero testRus.js que contiene el test

Descargar el fichero de GitHub clonando el respositorio :

git clone https://github.com/4lberto/test_e2e.git

O simplemente copiar y pegar el código que pongo más abajo.

Ejecución del test

mocha testRus.js

Si todo ha ido bien y la página que se va a testear no ha cambiado, aparecerá una ventana de Google Chrome en nuestro escritorio y se realizarán las comprobaciones, como si un vídeo de screencast estuviéramos viendo. Debería pasar los 3 test de los que se compone el código de testRus.js. Debería ser algo así:

 

 

Comentarios sobre el código y el funcionamiento de WebdriverJS

Promises

Una característica de webdriveJS que tiene Javascript y que no tienen otro tipo de lenguajes para manejar Webdriver, como por ejemplo con Java, es el manejo de la naturaleza asíncrona de un navegador Web y de Javascript. Cuando cargamos una página y hacemos click en un botón, puede suceder que pase un tiempo hasta que la consecuencia de la acción se ha producido o Webdriver ha sido capaz de resolver el resultado de la instrucción. Es decir, si pedimos el contenido HTML de un elemento que hemos localizado a través de su clase, puede que este contenido HTML no esté disponible al instante. Si este contenido es utilizado en la siguiente instrucción puede que no pueda usarse todavía y la aplicación falle.

Para tratar esta asincronía, los creadores de WebdriverJS han recurrido a uná técnica llamada “promises”. Básicamente consiste en que cuando se solicita una información que puede que no esté disponible al instante, se devueelve de forma inmediata un objeto “promise” de esa información, es decir, un objeto que representa el objeto prometido, pero que no tiene por qué tener esta información. Este objeto será completado con la información correspondiente en cuanto sea posible, y será en ese momento cuando lance una notificación a sus observadores. ¿Quién son sus observadores? Pues son dos funciones: una que se lanza si todo ha ido correcto (no ha habido problemas a la hora de cargar la información); y otra función que se lanzará su ha habido algún error o problema.

En código se ve mucho mejor el funcionamiento del objeto promise:

var prom = driver.findElement(webdriver.By.className('miClase')).getInnerHTML();
prom.then(function(){//función correcta}, function(){//función error} );

Como se puede ver, en la variable “prom” queda alojado el objeto de tipo promise que debería tener alojado el contenido HTML del tag que tiene como clase “miClase”. Este objeto, en la instrucción siguiente, se le añaden dos observadores, que son dos funciones anónimas. Cuando el objeto “prom” disponga de la información real y no haya habido problemas, hará una llamada a la primera función anónima. Es obvio que es el método “then” el que nos ayuda a establecer ambas funciones observadoras que serán notificadas de la carga correcta o incorrecta de la información.

Por ejemplo, si queremos mostrar por pantalla el título de una página que se acaba de cargar podemos hacer:

var titulo = driver.getTitle();
titulo.then(function(titulo)
{
    console.log("título de la página:" + titulo);
}, function(){
    console.log("error");
});

Nuevamente, la variable titulo actúa como promise, pero su valor cuando pasa por esa instrucción no tiene por qué ser conocido. Sin embargo en la siguiente instrucción, con la propiedad “then” del promise, se registran dos funciones que serán llamadas cuando el objeto promise reciba el valor adecuado.

Por tanto, los promises son una forma de tratar con la asincronía existente en Javascript de forma elegante y nos permitirá amoldarnos a los eventos de la página.

Función wait()

Otro método que tiene Webdriver para tratar con los problemas de sincronía es el método driver.wait(función, tiempo_time_out). Este método espera hasta el tiempo_time_out para continuar con la ejecución, o hasta que se cumpla una condición (sea verdadera).

Se entenderá mejor con un ejemplo: si tenemos un botón que hace una llamada AJAX para mostrar una información que se recibe desde el servidor, lo que incluye cierto retardo en responder (pongamos 1 segundo), y muestra el resultado en el contenido de una capa, no nos servirá de mucho el sistema de promises, puesto que el contenido de la capa puede estar perfectamente definido (y tratado) aún cuando aún no ha llegado la respuesta desde el servidor. Es decir, puedo tener una capa vacía en la que se carga el resultado de la llamada. Con el sistema de promises puedo recuperar que el contenido efectivamente es vacío pero no podía esperar a recibir el contenido AJAX.

Utilizando la función wait podemos hacer un código parecido al siguiente:

driver.wait(function(){
    return driver.findElement(webdriver.By.id('resultado')).getInnerHTML().then(function(html)
    {
        return html.length>0;
    }
}, 3000).then(
    function()
    {
        //test del contenido...
    },
    function()
    {
        //trata el timeout...
    }
);

Esta función lo que haces es entrar en un bucle de comprobación constante que ejecuta la función de clausura que está en su interior y que comprueba que hay algo dentro del contenido HTML (con length>0) de un elemento cuyo id es “resultado”. Esto es, comprueba constantemente a ver el resultado de esa comprobación. Mientras sea false sigue haciendo comprobación hasta que encuentre un true o por el contrario pasen los 3000 milisegundos:

  • Si ha encontrado una salida positiva a su función de comprobación, ejecutará la primera de las funciones.
  • Si ha agotado el tiempo de time-out, ejecuta la segunda función, que es al función de error y debería tratar el problema.

Una vez han sido establecidos los antecendentes, vamos a pasar a ver el código del test sobre la página rusizate.com. Se trata de un test sencillo y nada exhausitivo, pero que debería darnos una idea sobre cómo se hacen este tipo de test. Los pasos son los siguientes:

  1. Acceso a la página y que se pulse el botón de aceptar las condiciones del uso de cookies, comprobando que una vez pulsado la capa de aviso que aparece, ha desaparecido.
  2. Salta a la página “Para gente curiosa”, comprobando que el título de la página que sucede a este salto es el adecuado.
  3. Utiliza el buscador, introduciendo el término “hermitage” y comprobando que el número de resultados es mayor 0.

Como he mencionado antes, el framework que se va a utilizar para hacer el testing es Mocha y las comprobaciones se van a hacer utilizando should.js. Todas estas tecnologías emplearán el driver para Google Chrome de Webdriver y se ejecutarán en nodejs.

El código es el siguiente:


var assert = require('assert'),
should = require('should'),
fs = require('fs');

var webdriver = require('../node_modules/selenium-webdriver/'),
test = require('../node_modules/selenium-webdriver/testing');
test.describe('Rusizate', function() { // Descripción del test
var driver;

//Antes de la ejecución de la prueba
test.before(function() {
    driver = new webdriver.Builder().
    withCapabilities(webdriver.Capabilities.chrome()).
    build();
});

//Test 1: accede a la página y acepta las condiciones legales
test.it('Acceso y aceptación de condiciones', function() {
    driver.get('http://www.rusizate.com');
    driver.findElement(webdriver.By.id('ca_banner')).then(function(div_banner)
    {
        div_banner.findElement(webdriver.By.className('accept')).click();
        driver.wait(function()
        {
            return div_banner.getCssValue("display").then(function(valor){
            return valor.indexOf("none")>-1;
        });
     }, 3000).then(function()
     {
        div_banner.getCssValue("display").then(function(valor){
        valor.should.equal("none");
     });
    });
    });
   });

//Test 2: Salta a la página "Para la gente curiosa"
test.it('Salta a Gente Curiosa', function() {
    driver.findElement(webdriver.By.className('item-117')).click();
    driver.wait(function() {
        return driver.getTitle().then(function(title) {
            return title.indexOf("curiosa")>-1;
        });
    }, 3000).then(function()
    {
        driver.getTitle().then(function(titulo)
        {
            titulo.should.include('curiosa');
        });
    });
 });

//Test 3: Utiliza el buscador y comprueba que devuelve 2 resultados
test.it('Buscador - buscar la palabra Hermitage', function() {
    driver.findElement(webdriver.By.id('mod-search-searchword')).sendKeys('hermitage');
    driver.findElement(webdriver.By.className('form-inline')).submit();

    driver.wait(function() {
        return driver.getTitle().then(function(title) {
        return title.indexOf("Buscar")>-1;
    });
 }, 10000).then(function()
 {
     driver.findElements(webdriver.By.tagName('dt')).then(function(resultados)
     {
         resultados.length.should.be.above(0); //Encuentra más de 1 resultado.
     });
 });
 });

//Cierra la instancia al finalizar la prueba
test.after(function() {
    driver.quit();
 });
});

Como puntos a destacar:

  • Carga de los módulos necesarios de webdriver, que no están instalados de forma global sino en un directorio por debajo del que estamos trabajando.
  • La descripción del test y sus fragmentos que se producen antes del test (test.before…) y después del test (test.afeter…) que permiten inveocar al driver y cerrarlo respectivamente.
  • Cada una de las pruebas que se llevan a cabo a través de test.it(descripción, funcion).

Entrando en detalle sobre el primer test, por que el resto cson cosas estándar, tenemos el acceso a la página web y la búsqueda de la capa en la que se muestra el aviso de conformidad con las cookies. Una vez localizado el elemento utilizando el ID, hay que localizar el botón que está dentro. Como se ha comentado antes, es información que puede estar disponible de forma asíncrona, asi que hay que usar los promises y su propiedad “then”.

Una vez localizado el botón se hace click sobre el elemento. Como la acción que esto desencadena sobre la interfaz consiste en que la capa que muestra el mensaje desaparece poco a poco, no se puede comprobar el efecto de manera inmediata.

Para esperar el tiempo necesario se emplea el método “wait”, comprobando constantemente durante 3 segundos a ver si ha desaparecido o no la capa (con display none). La comprobación posterior es redundante y consiste en cuando todo ha ido bien, es decir, que ha desaparecido la capa antes de los 3 segundos del time out, realiza una acción. En este caso la acción consiste en volver a comprobar el display:none. Esto en un escenario real podíamos decir que está repetido, pero se ha dejado por motivos didácticos.


//Test 1: accede a la página y acepta las condiciones legales
test.it('Acceso y aceptación de condiciones', function() {
    driver.get('http://www.rusizate.com');
    driver.findElement(webdriver.By.id('ca_banner')).then(function(div_banner)
    {
        div_banner.findElement(webdriver.By.className('accept')).click();
        driver.wait(function()
        {
            return div_banner.getCssValue("-display").then(function(valor){
                return valor.indexOf("none")>-1;
            });
         }
         , 3000).then(function()
         {
             div_banner.getCssValue("display").then(function(valor){
                 valor.should.equal("none");
          });

      });
  });
});

En cuanto al segundo paso no puede ser más sencillo: se localiza en la página el botón que da acceso a la página para la gente curiosa (localizado con un class unívoco), y hacemos click.

Del mismo modo que antes, se tiene que esperar un tiempo hasta que se produzca el evento de carga, que bien puede ser que espere cargando la página hasta que aparezca la palabra “curiosa”. Como en el caso anterior, la comprobación del test vuelve a ser la misma que la condición de salida.

 //Test 2: Salta a la página "Para la gente curiosa"
test.it('Salta a Gente Curiosa', function() {
    driver.findElement(webdriver.By.className('item-117')).click();
    driver.wait(function() {
        return driver.getTitle().then(function(title) {
            return title.indexOf("curiosa")>-1;
        });
     }, 3000).then(function()
     {
         driver.getTitle().then(function(titulo)
         {
             titulo.should.include('curiosa');
         });
     });
});

Finalmente vamos a ver cómo se realiza una búsqueda. Es muy sencillo. Simplemente se localiza el campo de texto donde se introducen los valores y se invoca el método “sendKeys” que imita la introducción por teclado de una cadena de texto. A continuación se localiza el formulario y se invoca el método “submit”.

De nuevo queda esperar a la carga de resultados con la comprobación del título de la página durante 10 segundos. Una vez tenemos la página de resultados se buscan los elementos de resultado (esta vez en plural) a través del nombre del tag “dt” (previamente sabemos que estos tag sólo se usan por los resultados). Usando should.js hacemos la comprobación de que hay más de un resultado.


//Test 3: Utiliza el buscador y comprueba que devuelve 2 resultados
test.it('Buscador - buscar la palabra Hermitage', function() {
    driver.findElement(webdriver.By.id('mod-search-searchword')).sendKeys('hermitage');
    driver.findElement(webdriver.By.className('form-inline')).submit();

    driver.wait(function() {
        return driver.getTitle().then(function(title) {
            return title.indexOf("Buscar")>-1;
        });
    }, 10000).then(function()
    {
        driver.findElements(webdriver.By.tagName('dt')).then(function(resultados)
        {
            resultados.length.should.be.above(0); //Encuentra más de 1 resultado.
        });
    });
});

Para ejecutar el test, como se ha dicho antes,  simplemente hay que ejecutar en la línea de comandos “mocha testRus.js” y deberían pasar los 3 test indicados. Si en alguno de ellos no fuera positivo el resultado, devolverá un aviso indicando qué test ha fallado y por qué razón gracias a Should.js y Mocha.

Finalmente se cierra la instancia del driver de Chrome para Webdriver.

Espero que este post os anime a mejorar la calidad de vuestras aplicaciones (y páginas) Web mediante la utilización de sistemas de testing unitarios (quizá empleando Karma) y testing finales como es el caso de Webdriver.

Web Workers en HTML5. Paseo Aleatorio

Ver Demo

Una de las novedades de HTML5 es la posibilidad de crear hilos de ejecución para ejecutar tareas en segundo plano con el fin de no entorpecer la interacción del usario con la página. De este modo acciones que requieran un gran tiempo de procesamiento, especialmente en dispositivos con poca capacidad de proceso, y que bloquean la interface (más de 250ms), pueden ser ejecutadas en segundo plano. Estos hilos han sido denominados Web Workers, y como todo lo de HTML5, se programan usando Javascript. De momento sólo funcionan en las versiones más actuales de Firefox y Google Chrome. El código de este ejemplo está creado usando Google Chrome 20+.

Para usar un WebWorker simplemente hay que instanciarlo en el código JavaScript:

var worker = new WebWorker ("hilo1.js");

Como se puede ver, recibe por parámetro un fichero de Javascript, que contiene el código que se va a ejecutar cuando se lance el hilo. Para ejecutar un hilo simplemente hay que enviarle un mensaje que será captado mediante un listener para ese evento como veremos después.

worker.sendMessage();

Dentro del código del hilo (hilo1.js) está lo necesario para responder a esta llamada, devolviendo otro mensaje.

self.addEventListener('message',function(e)
{
 self.postMessage('fin');
});

Finalmente el hilo principal también registra un listener para “escuchar” los mensajes del web worker:

 worker.addEventListener('message',function(e){
 alert(e.data); //pinta fin en una ventana de alert
 });

El código del WebWorker es Javascript y tiene una serie de limitaciones muy importantes que determinan la arquitectura a la hora de usarlos. No tienen acceso a:

  • document
  • parent
  • window
  • otras librerías como jQuery o Prototype

Con estas limitaciones queda claro que dentro de los hilos no se puede hacer cualquier cosa ni pueden operar de forma independiente sin la interacción con el programa princopal. Realmente los hilos deberían limitarse a recibir y devolver información de tipo texto o JSON al hilo principal, de modo que sea éste el que haga las operaciones de manipulación de componentes del DOM. Por ejemplo, un caso de interacción típico sería:

  1. El hilo principal instancia un WebWorker para que haga una llamada AJAX y le devuelva la información en JSON. Esta información la mostrará en una capa DIV. Si lo hace el hilo principal tarda 1000ms debido al tamaño del JSON (o más en un ordenador/dispositivo lento).
  2. El WebWorker contiene en su código un “EventListener” donde está progrmaado el procesamiento de la acción indicada en el punto 1. Esta acción se ejecutará cuando la pida el hilo principal.
  3. El hilo principal llama al WebWorker y en éste se ejecuta el evento, lanzando la petición AJAX, que recibe una gran cantidad de texto en formato JSON. Parsea esta información a un objeto (y esta operación le lleva bastante tiempo).
  4. El WebWorker notifica al hilo principal que ha acabado, pasándole por parámetro el objeto JSON ya procesado.
  5. El hilo principal recive el evento de ese WebWorker y dispara una función que printa el objeto JSON en el DIV (algo muy rápido una vez se tiene el JSON).

De forma genérica se podría decir que los pasos que tiene que seguir una aplicación que use Web Workers debería ser:

  1. Hilo principal instancia el WebWorker y le ordena comenzar.
  2. El WebWorker tiene un EventListener que se activa cuando el hilo principal le ordena comenzar. En esta llamada se puede incluir información como un texto o un JSON.
  3. El WebWorker procesa y hace una llamada al hilo principal.
  4. El hilo principal tiene otro EventListener que se activa cuando el WebWorker termina. En esta llamada se puede incluir información como un texto o un JSON.

En nuestro ejemplo lo que intentamos programar es un programa que calcule caminos aleatorios (http://es.wikipedia.org/wiki/Camino_aleatorio) partiendo de un valor dado (100). Cada camino tiene 100 valores que son calculados por los hilos que lanzamos, y su resultado será pintado en un objeto canvas.

Un camino aleatorio parte de un punto, que en nuestro caso será el valor 100, y va generando, mediante números aleatorios diferentes valores basados en el paso anterior, así, partiendo del valor 100, el siguiente paso puede ser 101, el siguiente 104, el siguiente 102, luego 98… etcétera. Esto generará una gráfica con forma de rayo. Se trata simplemente de un proceso que puede ser computacionalmente costoso y que si se llevara a cabo en primer plano bloquearía la interfaz de usuario. Los hilos generarán estas series de 100 valores por cada camino en segundo plano y lo enviarán en formato JSON al hilo principal para que simplemente los pinte en el canvas.

En primer lugar vamos a analizar qué necesitamos:

  1. Un worker que genere series de 100 valores que representan caminos aleatorios y que envía al hilo principal en formato JSON
  2. Un generador de caminos aleatorios, incluyendo una función básica como es un generador de números aleatorios.
  3. Un listener en el hilo principal que se encarga de pintar el camino recibido en formato JSON en un canvas.
  4. Un control para parar el proceso del hilo cuando se desee.

Vamos por partes. En primer lugar tenemos que construir el listener para lanzar el evento de paso de mensaje. Hasta que este evento no se produce no hay lanzamiento del hilo:

self.addEventListener('message',function(e)
{
 var iteraciones = e.data.iteraciones;
 for(w=0;w<iteraciones;w++)
 {
 if (!stop)
 main(e);
 else
 {
 break;
 self.close();
 }
 }
});

Hay que notar un par de cosas:
1. Que existe una variable – global al worker – llamada “stop” con un valor booleano. Será modificada por otro evento para indicar que se debe para el hilo (de ahí el break y el self.close(), que cierra el bucle y termina el hilo).
2. Los parámetros que se le pasan al web worker, que será mediante JSON. En este caso se indican en el campo iteraciones el número de paseos aleatorios que va a generar cada hilo. Se lee mediante e.data.iteraciones.

Una vez tenemos el número de iteraciones se llama a la función principal, main(e) que genera un camino aleatorio y lo envía al hilo principal:

function main(e)
{
 var volatilidad = e.data.volatilidad;
 var media = e.data.media;
 var base = e.data.base;

var salida = "[";
 for(j=0;j<100;j++) //series de 100 valores
 {
 salida+="{\"V\":"+base+"},"; //compone la salida en array de JSON
 base=(siguiente_paso(media, volatilidad, base));
 }
 salida = salida.substring(0,salida.length-1);
 salida +="]";
 self.postMessage(JSON.parse(salida)); //Devuelve la salida en formato JSON
}

Se puede ver que se toman los parámetros de volatilidad, media y base desde el JSON de entrada desde el hilo principal. A partir de ahí, llamando a la función siguiente_paso, se va generando mediante un bucle el listado de puntos del camino aleatorio. Se hace en una cadena de texto con formato JSON para posteriormente ser transformada a objeto JSON con la función JSON.parse. Finalmente se envía al hilo principal a través de self.posMessage.

La operación siguiente_paso genera el siguiente valor del camino aleatorio, partiendo del valor actual y aplicándole un número aleatorio de una función normal 0,1 -N(0,1)- . Para su cálculo se opera con la volatilidad y el crecimiento medio indicado. Alta volatilidad permite obtener caminos más dispersos. La media es el crecimiento medio orgánico en porcentaje de cada paso.

function siguiente_paso(media, volatilidad, anterior)
{
 return (anterior+((anterior*media)+(volatilidad*anterior*randN())));
}

La función randN() es la que mayor calculo requiere y hace costoso el programa. Se emplean 100.000 iteraciones para su cálculo, aunque probablemente el óptimo sea mucho menor (12). Se ha elegido un número tan alto de iteraciones para hacer artificialmente costoso (pero preciso) el programa e ilustrar claramente la utilidad de los web workers:

function randN()
{
 var iteraciones = 100000; //lo óptimo son sólo 12, pero para simular carga de CPU
 var salida = 0;
 for(i=0;i<iteraciones;i++)
 {
 salida+=Math.random();
 }
 salida-=(iteraciones/2);
 salida*=Math.sqrt(12/iteraciones);

return salida;
}

Finalmente se ha incluíduo un mecanismo de parada del hilo que actúa en el bucle principal mediante el valor booleano de la variable “stop”, que se comprueba en cada iteración del bucle si debe continuar con el proceso o parar. La API de Web Workers permite enviar otros tipos de mensajes más allá del método “postMessage()”. Mediante el método “terminate()” enviamos un mensaje al Web worker que activa el evento “close” y que se captura mediate el listener correspondiente. Por tanto debemos añadir el siguiente código a nuestro worker:

self.addEventListener('close',function(e)
{
 stop=true;
});

La mejor manera de para un hilo como el que hemos desarrollado es haciendo una parada controlada del proceso, es decir, generando una serie de números completa. Para ello nos valdremos de una vaiable global que se modifica mediante el evento close y que está dentro del bucle de ejecución del worker. De este modo, si esta variable es true se parará el hilo y se cerrará, saliendo del bucle y cerrando el hilo mediante el método “close()”

if (!stop)
 main(e);
else
{
 break;
 self.close();
}

En el lado del hilo principal, el mecanismo consiste en lanzar tantos workers como se indique y registrar un listener por cada uno de ellos con el fin de recibir los mensajes de los hilos. Se guarda la referencia en un array para poder terminarlos posteriormente. El array es una variable global al hilo.

function start_workers()
{
 var num_workers = parseInt(document.getElementById("hilos").value);
 for (i = 0 ;i<num_workers;i++)
 {
 array_workers[i] = new Worker('js/w1.js');
 array_workers[i].addEventListener('message',function(e){
 pinta_serie_canvas(e.data);
 });
 array_workers[i].postMessage({'volatilidad':document.getElementById("volatilidad").value,'media':document.getElementById("media").value,'base':100,'iteraciones':document.getElementById("iteraciones").value}); //ojo:
 }
 }
 

Como se puede ver en el código, se instancia cada objeto web worker pasándole por parámetro el fichero javascript que define su comportamiento y que se ha descrito en líneas anteriores. Posteriormente se hace la llamada mediante la función postMessage. Se le pasa por parámetro JSON los valores de la volatilidad, media, iteraciones y valor 100 de partida. Como se puede ver se leen de unos input text que tienen id’s correspondientes. Cada web worker se mete en un array para tener su referencia.

El listener para cada mensaje de vuelta de los hilos, que contiene el paseo aleatorio de 100 números en formato JSON, hace una llamada la función pinta_serie_canvas, que lo dibuja en un canvas de HTML5

function pinta_serie_canvas(serie)
{
 var x_step = Math.max(1,Math.floor(800/serie.length)); //incremento eje X, al menos 1. siempre entero

 var canvas = document.getElementById('canvas');
 var context = canvas.getContext('2d');
 context.strokeStyle = generaColor();
 context.beginPath();

 var nuevaPosX = 0;
 var nuevaPosY = 250;
 for(j=1;j<serie.length;j++) //el primer valor es la base, es el de partida
 {

 context.moveTo(nuevaPosX,nuevaPosY); //mueve el cursor a la posición de partida para este paso
 nuevaPosX +=x_step;
 nuevaPosY +=-Math.floor(Math.round(serie[j].V - serie[j-1].V)); //avanza la diferencia que haya en v.absoluto

 context.lineTo(nuevaPosX,nuevaPosY);
 context.stroke(); //pinta la línea
 }

 context.closePath();
}

Básicamente se trata de partir del valor medio de la parte izquierda del canvas (punto 0, 250; ya que el canvas tiene 800*500 píxeles) e ir incrementando los valores de X (en 8 píxeles) y de Y (en tantos píxeles como números enteros haya de diferencia entre el punto actual y el siguiente) e ir pinando una línea mediante lineTo. También se genera un color diferente para cada camino pintado. La forma de manejar el canvas es sencilla.

Finalmente se añade el mecanismo para cancelar la ejecución de cada hilo. Tenemos guardadas su referencias dentro de un array, que recorreremos mandando el mensaje de close -mediante terminate()- a cada uno. Este proceso lo realiza la función terminate_workers();

function terminate_workers()
{
 for(l=0;l<array_workers.length;l++)
 {
 array_workers[l].terminate();
 array_workers[l]=null;
 }
}

Mediante la función terminate se envía un mensaje de tipo “close” al hilo, que como se ha visto antes, pone a true la variable “stop” que es tenida en cuenta en el bucle principal del worker.

Finalmente se ha incluído código HTML para crear la interfaz y algo de JavaScript para poder ligar los eventos de los botones al programa principal.

Podéis descargar el ejemplo haciendo un clone del repositorio en: https://github.com/4lberto/webworkers_paseo_aleatorio

Obsérvese cómo el proceso de cálculo de cada camino aleatorio no bloquea la interfaz web del usuario.

GitHub de las extensiones de Chrome

Para quien pueda interesarle, he publicado los códigos fuentes de dos extensiones de Chrome de las que hablo en algún post anterior

Contador de tiempo abierto:

Contador de páginas:

HTML5 localStorage y JSON para dotar de persistencia a una Extensión de Chrome

Uno de los problemas a la hora de trabajar con Javascript+HTML que se ha tenido siempre es cómo almacenar información persistente de la aplicación que hemos creado en el navegador. Javascript fue concebido principalmente para manejar el árbol DOM y hacer modificaciones en la interfaz de usuario para hacerla más atractiva, de donde surgió aquel término llamado DHTML (Dynamic HTML). La tendencia actual es a crear aplicaciones autónomas en el navegador, tan potentes como las tradicionales aplicaciones ejecutables en un sistema operativo pero dentro del navegador, gracias a la aparición de nuevas funcionalidades, como las llamadas asíncrontas y sobre todo a la optimización de los motores de ejecución de Javascript en los navegadores, que se convierten en máquinas virtuales potentes sobre las que ejecutar scripts, con los benficios de la interfaz de HTML.

Como decía, uno de los problemas es dotar de más autonomía a la aplicación escrita en Javascript+HTML, y esto suele pasar por disponer de algún mecanismo de persistencia que permita almacenar la información del usuario para que la próxima vez disponga de la misma configuración o datos históricos. Antes de HTML5 se empleaban algunas técnicas como el uso de cookies para estas labores; o el método más correcto de tener un servidor de aplicaciones con un perfil de usuario en el que se almacena la información en remoto, pero esto requiere de conexión a Internet y de una infraestructura de servidor que realice esta tarea, con el coste que ello tiene.

Sin embargo, desde la aparición de HTML5 y de los navegadores que implementan esta especificación, podemos usar una de sus muchas ventajas: el almacenamiento local o localStorage.

Mediante localStorage podremos almacenar en el espacio que nos proporciona el navegador pares de atributos “string”,”string” hasta un máximo de 5 megas de espacio (las limitaciones a cadenas de texto y almacenamiento corresponden a la implementación que hacen los navegadores actuales de HTML5). De este modo podremos almacenar y recuperar estos valores en cualquier momento en nuestro código Javascript.

Para almacenar una cadena de texto en una variable procederemos esta forma:

localStorage.setItem("mivariable","mivalor");
//o lo que es lo mismo:
localStorage.mivariable = "mivalor";

La lectura del valor sigue el procedimiento esperado:

var valor = localStorage.getItem("mivariable");
//o lo que es lo mismo:
var valor = localStorage.mivariable;

La información permanecerá en localStorage de manera persistente y fiable, de modo que podemos apoyarnos en ella para almacenar datos como la configuración de la aplicación o información histórica el usuario.

A continuación se va a emplear localStorage de Chrome para ampliar la extensión básica que se creó en el post https://mysticalpotato.wordpress.com/2011/01/15/extension-sencilla-para-google-chrome/, de modo que se almacena el histórico del tiempo de uso. El resultado de esta modificación se corresponde con la versión 0.3 de la extensión, que puede descargarse en https://chrome.google.com/extensions/detail/lkhahcnmgjhbbdkieijbndecclgcibjp?hl=en#

Para almacenar el histórico de tiempo necesitamos conocer qué valores tenemos que almacenar y cuando. Necesitamos conocer el tiempo de inicio y el de final en cada sesión, que se encuentran en formato de milisegundos en la página background.html, que si se revisa el post anterior de la extensión, se verá que se carga cuando la extensión comienza la ejecución. De este modo el par de datos (inicio, fin), nos dará el tiempo de inicio y el tiempo que ha durado la sesión (fin-inicio). Están en formato Date, que se transforma a milisegundos desde el 1 de enero de 1970, para poder localizarlo con precisión en el tiempo. El resultado del almacenamiento es por lo tanto un array de pares de tiempo.

En primer lugar debemos crear un objeto en Javascript que modele este tiempo:

//Time object
function tiempo (inicio, fin)
{
	this.inicio = inicio;
	this.fin = fin;
}

Lo que queremos almacenar por lo tanto será un array de objetos tiempo. El problema radica ahora en encontrar la forma de transformar el array de tiempo a una cadena de texto y viceversa. El mejor método es hacer uso de JSON. En post anteriores se ha podido ver cómo emplear JQuery y algunas librerías especiales para manejar este tipo de objetos. En esta ocasión usaremos métodos nativos del navegador Chrome. La explicación es que la extensión es específica de Chrome, y tenemos la certeza absoluta de que el usuario dispone de este requisito para correr la app.

Almacenaremos una String de este tipo, que contiene un array de objetos tiempo en formato JSON:

[{"inicio":1295726332072,"fin":1295726339310},{"inicio":1295726339311,"fin":1295726527498},{"inicio":1295726527501,"fin":1295727082467}]

Suponiendo que en tiempo_instancia tenemos un objeto “tiempo” correctamente formado, su transformación a JSON es sencilla:

JSON.stringify( tiempo_instancia );

El problema está en cuándo realizar el almacenamiento de la información: cuando se cierra el navegador o se desactiva la extensión. Estos dos eventos tienen en común que el cierre de background.html, de manera que se puede capturar el evento y llamar a una función:

 //Event when extension closes.
window.onunload =
	function () {
	guardaEstadisticas();
};

Efectivamente, cuando se cierra la extensión o se cierra el navegador se llama a la función guardaEstadisticas(). Ésta se encarga de almacenar el objeto tiempo de la sesión actual, lo que significa encontrarse frente a dos situaciones: que no exista ningún valor anterior o que ya haya un array con uno o varios valores previos. En el primer caso habrá que añadir los corchetes “[]” para indicar que es un array de un sólo elemento. En el segundo caso habrá que recuperar el array , transformándolo desde JSON a array de objetos de Javascript, hacer un push para añadir el último objeto al array y volver a pasarlo a cadena de texto en formato JSON:

function guardaEstadisticas()
{
	//When extension ends, adds the time it's been used
	var tiempo_instancia= new tiempo(tiempo_inicio.valueOf(), (new Date()).valueOf());
	if(!localStorage.COT_history) //first time 
	{
		localStorage.COT_history = "[" + JSON.stringify( tiempo_instancia ) + "]";
	}
	else  //there was some data before
	{
		var historia = JSON.parse(localStorage.COT_history);
		historia.push(tiempo_instancia);
		localStorage.COT_history = JSON.stringify(historia);
		console.log("Añadido:" + JSON.stringify(historia));
	}
}

Como se puede comprobar, pasar de String en formato JSON a array de objetos se puede hacer a través del método JSON.parse que provee Javascript de Chrome. La comprobación de si existe registro anterior es sencilla: if(!localStorage.COT_history) . Como se puede ver, se ha elegido la clave COT_history para llamar a esa variable en el almacenamiento del navegador.

Otro aspecto es cómo se muestran las estadísticas. En este caso he empleado una página nueva llamada history.html enlazara con un enlace normal desde popup.html. Se encarga de recuperar la información del localStorage, pasarla de JSON a array de objetos tiempo y mostrarla mediante un bucle en una tabla, con la particularidad que muestra los tiempos en modo descendiente hasta un máximo de 15 tiempos.

function pintaEstadisticas()
{
	var tiempos = JSON.parse(localStorage.COT_history);
	var salida = "<table><thead><tr><td>Start Time</td><td>Opened for</td></tr></thead><tbody>";
	var limite = Math.max (tiempos.length-15, 0 );
	for (i = tiempos.length-1;i>=limite;i--)
	{
		salida = salida + "<tr><td>";
		salida = salida + (new Date(Number(tiempos[i].inicio))).toString().substr(0,24);
		salida = salida +"</td><td>" + formatTime(Number(tiempos[i].fin)-Number(tiempos[i].inicio));
		salida = salida + "</td></tr>";
	}
	salida = salida + "</tbody></table>";

	document.write(salida);
}

El resultado es:

Finalmente existe la posibilidad de borrar las estadísticas, que no es otra cosa que borrar la información que corresponde a la clave local “COT_history”. Para ello se ha incluido un enlace en history.html que lanza una función que realiza el reset del almacenamiento:

function clear_stats()
{
	localStorage.COT_uses = 1;
	localStorage.removeItem("COT_history");
	window.location.reload()

	return null;
}

Para concluir, podemos ir a la ventana de extensiones pulsando en la llave de opciones de Chrome -> tools -> extensions y activar el “developer modo”, ir a la extensión y pulsar sobre el enlace background.html. Aparecerá la ventaja de inspección (en cualquier página pulsando Ctrl+Shift+j). Pulsando sobre el icono Storage (3º por la derecha) podremos ver los datos del localStorage.

El resultado de este post y otras modificaciones más dieron lugar a la version 0.3 de la aplicación, que puede verse aquí: https://chrome.google.com/extensions/detail/lkhahcnmgjhbbdkieijbndecclgcibjp?hl=en#

Adjunto el zip de la extensión. OJO: RENOMBRAR A .ZIP