项目作者: euprogramador

项目描述 :
Este repositório demonstra como usar o angular juntamente com o selenium hub para testes de unidade e aceitação em ambientes standalone e continuous integration/delivery
高级语言: TypeScript
项目地址: git://github.com/euprogramador/angular-selenium-ci-docker.git


angular-selenium-ci-docker

Este repositório demonstra como usar o angular juntamente com o selenium hub para testes de unidade e aceitação em ambientes standalone e continuous integration/delivery

Build Status

Introdução

Testes são uma forma de garantir que a codificação do programa especificado, esteja em conformidade com o que foi solicitado.
Entretanto o benefício real de uma disciplina de desenvolvimento guiado por testes é realmente vista ao se fazer alterações futuras. Quando um teste acusa um erro, em que você programou, não ter que ficar depurando para achar a origem dos problemas é incrível. Quando corrigido, ter a garantia de que o que foi implementado e TESTADO, continua funcionando é muito bom.

Mas para que tenhamos esta certeza é necessário força de vontade, montar ambientes que permitam esta prática nem sempre é fácil. A escrita destes tipos de testes também tem seus percalços.

Este pequeno guia fala da montagem de um ambiente que pode ser utilizado para o desenvolvimento de sistemas usando uma abordagem dirigida para a integração contínua.

Phamtomjs

Phamtomjs é uma ferramenta incrível. A idéia de um browser sem UI e com uma api especificada acoplada é muito interessante. è fácil de configurar e, as diversas ferramentas de testes apoiam o seu uso. Entretanto phamtomjs tem problemas que podem nos levar a pensar se ele é uma boa escolha para o uso em testes, Cris Le apontou em seu blog em 2013 do porque não usar phatomjs.

Basicamente:

  • Usa QtWebkit
  • Não tem como inspecionar os elementos

Para isso é melhor usar um browser que o usuário usa. Podemos usar o chrome, firefox, IE ou safari. O IE e o safari não permitem o uso em containeres de forma facilitada, ou pelo menos não conheço ainda.

Selenium HUB

Uma idéia interessante é usar o selenium hub e fazer com que os projetos apontem para o hub, no hub configuramos as instâncias de browser.

  1. +-----------------+ +--------------+ +------------+
  2. | suite de testes | | |===============> | instancia |
  3. | de unidade ou |=========> | Hub selenium | | do chrome |
  4. | integração | | | +------------+
  5. +-----------------+ +--------------+
  6. ||
  7. =========================> +--------------+
  8. | instancia |
  9. | dos outros |
  10. | browsers |
  11. +--------------+

Docker com selenium

Então podemos usar o docker para executar as instâcias do selenium hub e dos outros browsers.
As imagens podem ser conferidas em: https://hub.docker.com/u/selenium/

Docker com selenium hub e multiplas instancias de browser

Para iniciar o grid via docker:

  1. docker run -d -p 4444:4444 --name selenium-hub selenium/hub

Com isso temos um hub inicializado. Agora vamos inicializar os nodes que serão conetados ao hub para oferecer instancias para executar os testes.

Para disponibilizar uma instância de browser chrome use o comando docker abaixo:

  1. docker run -d --link selenium-hub:hub selenium/node-chrome

Para disponibilizar uma instância de browser firefox use o comando docker abaixo:

  1. docker run -d --link selenium-hub:hub selenium/node-firefox

Docker com selenium standalone

As instâncias standalone são instâncias que não precisam do hub para funcionar, elas já incorporam o browser e o hub juntos em uma única imagem, isso permite mais agilidade para um teste local.

Para executar uma instância standalone use:

  1. docker run -d -p 4444:4444 selenium/standalone-chrome

Angular

Este tutorial esta usando o angular cli para configurar um ambiente e ajustar a execução de um teste de unidade e integração usando o selenium, para isso usaremos a versão standalone que executa os testes em uma instancia especifica do browser.

O projeto deve ser inicializado normalmente usando o angular cli mais recente, no exemplo usamos o resolvedor de pacotes yarn, mas os mesmos comandos servem para o npm também.

  1. ng new projeto-selenium

Com isso teremos uma pasta chamada projeto-selenium, que contém o projeto.

angular cli já inicia um ambiente com o karma e protractor configurados para um ambiente que temos uma GUI, como o nosso objetivo será de automátizar a execução em um ambiente de nuvem, esta abordagem não é apropriada para ser executada, uma vez que na nuvem, não teremos uma GUI. A abordagem aqui sugerida rodou perfeitamente usando gitlab pipelines e bitbucket pipelines, necessitando de poucos ajustes, que abordaremos mais a frente.

Uma vez com o projeto inicializado, precisamos ajustar o karma e o protractor para usar o selenium que foi inicializado anteriormente.

angular + karma + selenium

Para isso ajustamos o arquivo karma.conf.js e adicionar o plugin karma-webdriver-launcher:

  1. module.exports = function (config) {
  2. config.set({
  3. basePath: '',
  4. frameworks: ['jasmine', '@angular/cli'],
  5. plugins: [
  6. require('karma-jasmine'),
  7. require('karma-webdriver-launcher'), //// aqui substituimos o karma-chrome-launcher pelo karma-webdriver-launcher
  8. require('karma-jasmine-html-reporter'),
  9. require('karma-coverage-istanbul-reporter'),
  10. require('@angular/cli/plugins/karma')
  11. ],
  12. ....

Agora precisamos informar para o plugin onde está localizado o nosso selenium, fazemos isso usando uma configuração de browser customizada e adicionamos esta configuração no karma.conf.js:

  1. ...
  2. customLaunchers: {
  3. swd_chrome: {
  4. base: 'WebDriver',
  5. config: {
  6. hostname: '127.0.0.1' // endereço do host local pois fizemos bind de portas 4444 na imagem docker do selenium
  7. },
  8. browserName: 'chrome',
  9. version: '',
  10. name: 'Chrome',
  11. pseudoActivityInterval: 30000
  12. },
  13. },
  14. ....
  15. browsers: ['swd_chrome'],
  16. ....

Também ajustamos para o report dos testes serem feitos no console.

Precisamos também adicionar a dependência do webdriver e reporter no package.json:

  1. yarn add karma-webdriver-launcher karma-verbose-reporter --dev

ou

  1. npm i karma-webdriver-launcher karma-verbose-reporter --save-dev

Segue abaixo o karma.conf.js completo com os ajustes usados:

  1. // Karma configuration file, see link for more information
  2. // https://karma-runner.github.io/0.13/config/configuration-file.html
  3. module.exports = function (config) {
  4. config.set({
  5. basePath: '',
  6. frameworks: ['jasmine', '@angular/cli'],
  7. plugins: [
  8. require('karma-jasmine'),
  9. require('karma-webdriver-launcher'),
  10. require('karma-jasmine-html-reporter'),
  11. require('karma-verbose-reporter'),
  12. require('karma-coverage-istanbul-reporter'),
  13. require('@angular/cli/plugins/karma')
  14. ],
  15. customLaunchers: {
  16. swd_chrome: {
  17. base: 'WebDriver',
  18. config: {
  19. hostname: '127.0.0.1'
  20. },
  21. browserName: 'chrome',
  22. version: '',
  23. name: 'Chrome',
  24. pseudoActivityInterval: 30000
  25. },
  26. },
  27. client:{
  28. clearContext: false, // leave Jasmine Spec Runner output visible in browser
  29. captureConsole: true,
  30. mocha: {
  31. bail: true
  32. }
  33. },
  34. coverageIstanbulReporter: {
  35. reports: [ 'html', 'lcovonly' ],
  36. fixWebpackSourcePaths: true
  37. },
  38. angularCli: {
  39. environment: 'dev'
  40. },
  41. reporters: ['progress', 'kjhtml', 'verbose'],
  42. port: 9876,
  43. colors: true,
  44. logLevel: config.LOG_INFO,
  45. autoWatch: true,
  46. browsers: ['swd_chrome'],
  47. singleRun: false
  48. });
  49. };

Para rodar o teste agora usamos o comando abaixo:

  1. ng test --colors --hostname=172.17.0.1 --code-coverage

o IP 172.17.0.1 é o ip da instância local do docker, pode ser usado o seu ip real. O karma apenas precisa achar a instância do selenium em execução. Também estamos instruindo a gerar o report de cobertura de código.

ao executar este comando obtemos o seguinte console:

  1. $ ng test --colors --hostname=172.17.0.1
  2. The option '--hostname' is not registered with the test command. Run `ng test --help` for a list of supported options.
  3. 10% building modules 1/1 modules 0 active16 08 2017 13:13:58.193:WARN [karma]: No captured browser, open http://172.17.0.1:9876/
  4. 16 08 2017 13:13:58.204:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:9876/
  5. 16 08 2017 13:13:58.204:INFO [launcher]: Launching browser swd_chrome with unlimited concurrency
  6. 16 08 2017 13:13:58.209:INFO [launcher]: Starting browser chrome via Remote WebDriver
  7. 16 08 2017 13:14:04.815:WARN [karma]: No captured browser, open http://172.17.0.1:9876/
  8. 16 08 2017 13:14:05.100:INFO [Chrome 59.0.3071 (Linux 0.0.0)]: Connected on socket 6keTI5DY6-_6XjF8AAAA with id 57429342
  9. Chrome 59.0.3071 (Linux 0.0.0): Executed 0 of 3 SUCCESS (0 secs / 0 secs)
  10. Chrome 59.0.3071 (Linux 0.0.0): Executed 1 of 3 SUCCESS (0 secs / 0.178 secs)
  11. Chrome 59.0.3071 (Linux 0.0.0): Executed 2 of 3 SUCCESS (0 secs / 0.242 secs)
  12. Chrome 59.0.3071 (Linux 0.0.0): Executed 3 of 3 SUCCESS (0 secs / 0.276 secs)
  13. Chrome 59.0.3071 (Linux 0.0.0): Executed 3 of 3 SUCCESS (0.296 secs / 0.276 secs)
  14. Suites and tests results:
  15. - AppComponent :
  16. * should create the app : ok
  17. * should have as title 'app' : ok
  18. * should render title in a h1 tag : ok
  19. Browser results:
  20. - Chrome 59.0.3071 (Linux 0.0.0): 3 tests
  21. - 3 ok

Desta forma o desenvolvimento dos testes pode continuar e ao alterar os arquivos o karma roda novamente os testes.

karma em ambiente de CI

caso queira executar apenas uma única vez sem o watch dos recursos basta adicionar a flag —single-run:

  1. ng test --colors --hostname=172.17.0.1 --single-run --code-coverage

Angular + protractor + selenium

Protractor é um framework usado para testes de aceitação. Ele já vem preconfigurado para a execução em uma máquina, entretanto para usar em um ambiente de integração contínua juntamente com o selenium precisamos de alguns ajutes:

o arquivo é o protractor.conf.js, que precisamos modificar.

A configuração é simples, basta adicionar a linha abaixo:

  1. seleniumAddress: 'http://127.0.0.1:4444/wd/hub',

Esta linha informa a localização do selenium para o protractor.

também devemos dizer para o protractor que não usaremos a api diretamente:

  1. directConnect: false,

Segue o arquivo completo protractor.conf.js:

  1. // Protractor configuration file, see link for more information
  2. // https://github.com/angular/protractor/blob/master/lib/config.ts
  3. const { SpecReporter } = require('jasmine-spec-reporter');
  4. exports.config = {
  5. seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
  6. allScriptsTimeout: 11000,
  7. specs: [
  8. './e2e/**/*.e2e-spec.ts'
  9. ],
  10. capabilities: {
  11. 'browserName': 'chrome'
  12. },
  13. directConnect: false,
  14. baseUrl: 'http://localhost:4200/',
  15. framework: 'jasmine',
  16. jasmineNodeOpts: {
  17. showColors: true,
  18. defaultTimeoutInterval: 30000,
  19. print: function() {}
  20. },
  21. onPrepare() {
  22. require('ts-node').register({
  23. project: 'e2e/tsconfig.e2e.json'
  24. });
  25. jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  26. }
  27. };

Após isso basta chamar o teste e2e:

  1. ng e2e --hostname=172.17.0.1
  2. ** NG Live Development Server is listening on 172.17.0.1:49152, open your browser on http://172.17.0.1:49152 **
  3. Date: 2017-08-16T17:05:08.851Z
  4. Hash: d88d4dc0d2a223e579d7
  5. Time: 9101ms
  6. chunk {inline} inline.bundle.js, inline.bundle.js.map (inline) 5.83 kB [entry] [rendered]
  7. chunk {main} main.bundle.js, main.bundle.js.map (main) 2.28 MB {inline} [initial] [rendered]
  8. chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 204 kB {inline} [initial] [rendered]
  9. chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 11.3 kB {inline} [initial] [rendered]
  10. webpack: Compiled successfully.
  11. [14:05:09] I/update - chromedriver: file exists /tmp/projeto-selenium/node_modules/webdriver-manager/selenium/chromedriver_2.31.zip
  12. [14:05:09] I/update - chromedriver: unzipping chromedriver_2.31.zip
  13. [14:05:10] I/update - chromedriver: setting permissions to 0755 for /tmp/projeto-selenium/node_modules/webdriver-manager/selenium/chromedriver_2.31
  14. [14:05:10] I/update - chromedriver: chromedriver_2.31 up to date
  15. [14:05:10] I/launcher - Running 1 instances of WebDriver
  16. [14:05:10] I/hosted - Using the selenium server at http://127.0.0.1:4444/wd/hub
  17. Jasmine started
  18. projeto-selenium App
  19. should display welcome message
  20. Executed 1 of 1 spec SUCCESS in 2 secs.
  21. [14:05:14] I/launcher - 0 instance(s) of WebDriver still running
  22. [14:05:14] I/launcher - chrome #01 passed

Com isso o protractor usou a instância do chrome do selenium.

Pipelines

Pipelines podem ser ligados a um repo git fazendo com que a cada commit sejam executados diversos passos como testes, documentação, etc….

Bitbucket

Bitbucket possui um conceito de pipelines embutido no código que é executado a cada commit podemos colocar estes comandos para usar o recurso de pipelines do bitbucket.

Para isso criamos um arquivo bitbucket-pipelines.yml:

  1. image: netczuk/node-yarn:node-6-yarn-0.18.1
  2. pipelines:
  3. default:
  4. - step:
  5. caches:
  6. - node
  7. script:
  8. - yarn
  9. - ./node_modules/.bin/ng test --host $(hostname -i) --single-run --coverage
  10. - ./node_modules/.bin/ng e2e --host $(hostname -i)
  11. services:
  12. - selenium
  13. definitions:
  14. services:
  15. selenium:
  16. image: selenium/standalone-chrome

Com isso podemos usar os testes no bitcket e esta configuração executa os testes usando o browser chrome.

NOTA: o bitbucket tem uma quantidade de 50 minutos para uso gratuito por mês.

Gitlab

O Gitlab também possui recursos de pipeline, atualmente é o que uso, fiz a instalação do gitlab com runner docker e habilitei a instalação do pipelines do gitlab.

para usar os testes de integração continua no gitlab, criei os arquivos karma.conf.ci.js e protractor.conf.ci.js, com configurações personalizadas para o ambiente de CI/CD do gitlab, isso foi necessário pois o host que roda o selenium precisa ser nomeado, para acessar o selenium, não podendo usar $(hostname -I).

karma.conf.ci.js:

  1. // Karma configuration file, see link for more information
  2. // https://karma-runner.github.io/0.13/config/configuration-file.html
  3. module.exports = function (config) {
  4. config.set({
  5. basePath: '',
  6. frameworks: ['jasmine', '@angular/cli'],
  7. plugins: [
  8. require('karma-jasmine'),
  9. require('karma-webdriver-launcher'),
  10. require('karma-jasmine-html-reporter'),
  11. require('karma-verbose-reporter'),
  12. require('karma-coverage-istanbul-reporter'),
  13. require('@angular/cli/plugins/karma')
  14. ],
  15. customLaunchers: {
  16. swd_chrome: {
  17. base: 'WebDriver',
  18. config: {
  19. hostname: 'selenium-unit' // nome do service que será disponibilizado no gitlab pipelines
  20. },
  21. browserName: 'chrome',
  22. version: '',
  23. name: 'Chrome',
  24. pseudoActivityInterval: 30000
  25. },
  26. },
  27. client:{
  28. clearContext: false, // leave Jasmine Spec Runner output visible in browser
  29. captureConsole: true,
  30. mocha: {
  31. bail: true
  32. }
  33. },
  34. coverageIstanbulReporter: {
  35. reports: [ 'html', 'lcovonly' ],
  36. fixWebpackSourcePaths: true
  37. },
  38. angularCli: {
  39. environment: 'dev'
  40. },
  41. reporters: ['progress', 'kjhtml', 'verbose'],
  42. port: 9876,
  43. colors: true,
  44. logLevel: config.LOG_INFO,
  45. autoWatch: true,
  46. browsers: ['swd_chrome'],
  47. singleRun: false
  48. });
  49. };

protractor.conf.ci.js

  1. // Protractor configuration file, see link for more information
  2. // https://github.com/angular/protractor/blob/master/lib/config.ts
  3. const { SpecReporter } = require('jasmine-spec-reporter');
  4. exports.config = {
  5. seleniumAddress: 'http://selenium-e2e:4444/wd/hub', // endereço aqui mudou. será fornecido pelo gitlab pipelines
  6. allScriptsTimeout: 11000,
  7. specs: [
  8. './e2e/**/*.e2e-spec.ts'
  9. ],
  10. capabilities: {
  11. 'browserName': 'chrome',
  12. },
  13. directConnect: false,
  14. baseUrl: 'http://localhost:4200/',
  15. framework: 'jasmine',
  16. jasmineNodeOpts: {
  17. showColors: true,
  18. defaultTimeoutInterval: 30000,
  19. print: function() {}
  20. },
  21. onPrepare() {
  22. require('ts-node').register({
  23. project: 'e2e/tsconfig.e2e.json'
  24. });
  25. jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  26. }
  27. };

agora criamos o arquivo .gitlab-ci.yml:

  1. image: netczuk/node-yarn:node-6-yarn-0.18.1
  2. stages:
  3. - build
  4. - tests
  5. download_dependencies:
  6. stage: build
  7. cache:
  8. key: "$CI_COMMIT_REF_NAME"
  9. untracked: true
  10. paths:
  11. - node_modules/
  12. script:
  13. - yarn
  14. unit_tests:
  15. stage: tests
  16. services:
  17. - name: selenium/standalone-chrome
  18. alias: selenium-unit
  19. cache:
  20. key: "$CI_COMMIT_REF_NAME"
  21. untracked: true
  22. policy: pull
  23. paths:
  24. - node_modules/
  25. script:
  26. # necessário rodar este script antes pois ele não faz parte do processo de instalação
  27. # vide: https://github.com/karma-runner/karma-sauce-launcher/issues/117 (comentario voltrevo de 9 de abril)
  28. - node node_modules/wd/scripts/build-browser-scripts.js
  29. - ng test --config=karma.conf.ci.js --single-run --hostname=$(hostname -i) --code-coverage
  30. tests_e2e:
  31. stage: tests
  32. services:
  33. - name: selenium/standalone-chrome
  34. alias: selenium-e2e
  35. cache:
  36. key: "$CI_COMMIT_REF_NAME"
  37. untracked: true
  38. policy: pull
  39. paths:
  40. - node_modules/
  41. script:
  42. - ng e2e --host $(hostname -i) --config protractor.conf.ci.js

Travis

Este projeto está habilitado usando o travis. Os comandos são os mesmos basta olhar o arquivo .travis.yml.