Cypress i CI z Azure Devops

Cypress i Continuous Integration

Moim zdaniem od współczesnego testera wymagamy czegoś więcej niż tylko samego poprawnego utworzenia testów automatycznych. Jednym z atrybutów wyróżniających nowoczesnych inżynierów QA jest umiejętność samodzielnego zarządzania procesem ciągłej integracji. W tym poście dotykam zagadnienia właśnie wpięcia testów w proces CI. Zamierzam wykorzystać platformę Azure DevOps - uruchomię jedynie testy, bez kroku deploy backendu i frontendu, te komponenty wystawiłem jako osobny proces nieopisany w tym materiale.

Azure DevOps

Konto jest darmowe. Więc aby zacząć przygodę z CI zakładamy konto i możemy zaczynać pracę. Na stronie:

https://dev.azure.com

Tworzę nowy projekt jak na screenshotcie.

Przechodzę do zakładki pipeline i klikam Create Pipeline. Wybieram moje repozytorium kodu, w mojej sytuacji jest to GitHub. Repozytorium: react-redux-realworld-example-app wybieram je. Muszę nadać odpowiednie uprawnienia, zgadzam się. Następnie muszę wybrać sposób konfiguracji mojego pipeline. Testy uruchamiam przy pomocy narzędzia npm więc sugestia użycia prekonfiguracji dla NodeJS wydaje się trafna. Wybieram więc zasugerowane ustawienie. W następnym kroku kreator proponuje mi konfiguracje w pliku .yml:

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '10.x'
  displayName: 'Install Node.js'

- script: |
    npm install
    npm run build
  displayName: 'npm install and build'

W tym momencie należy zastanowić się jak uruchamiam testy aby odpowiednio zdefiniować potrzebne kroki. Zaczynając od góry pliku, w moim przypadku na początek uruchomienie testów tylko na branchu master jest niewystarczające. Chciałbym uruchamiać testy na każdym branchu. Osiągnę to za pomocą zmiany sekcji trigger, która powinna wyglądać w ten sposób:

trigger:
  paths:
    include:
      - '*'

Wersja Node 10.x jest poprawna, na pewno wystarczająca, pozostawiam tę sekcję bez zmian. Przed wykonaniem testów muszę wykonać polecenie npm install. Aby zainstalować wszystkie potrzebne zależności do uruchomienia testów. Zamieniam istniejącą sekcję script na:

- script:
    npm install
  displayName: 'Install dependecies'  

Następnym krokiem jest walidacja zainstalowania narzędzia Cypress. Można to osiągnąć używając polecenia cypress verify. Nie mam jeszcze tego skryptu w swoim repozytorium. Jednak wiem, że będę uruchamiał go za pomocą skryptu npm: npm run cy:verify. Dodają tę sekcję do projektu.

- script:
    npm run cy:verify
  displayName: 'Cypress verify'

Kolejnym etapem jest uruchomienie testów. Zazwyczaj uruchamiam je za pomocą polecenia: npx cypress run. Tu zamierzam zmienić delikatnie konwencję. Zamierzam dla spójności również użyć skryptu npm: npm run cy:verify

- script:
    npm run cy:run
  displayName: 'Execute cypress tests'

To wszystkie zmiany które potrzebuję od strony Azure DevOps. Naciskam Save and Run i mam gotowy pierwszy etap mojej pracy. Plik azure-pipelines.yml wygląda tak:

trigger:
  paths:
    include:
      - '*'

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '10.x'
  displayName: 'Install Node.js'

- script:
    npm install
  displayName: 'Install dependecies'  

- script:
    npm run cy:verify
  displayName: 'Cypress verify'

- script:
    npm run cy:run
  displayName: 'Execute cypress tests'

Podejrzewam, jednak że będą to nie wystarczające zmiany. Wiem, że testy komunikują się ze środowiskiem lokalnym, które jest niedostępne na agencie Azure DevOps. Moje przewidywania sprawdzają się po uruchomieniu pierwszego buildu kończy się błędem.

Bash exited with code '1'.

Testy uruchomiły się jednak host:

http://localhost:4100

Nie jest dostępny. Zdecydowanie jestem mile zaskoczony jasną komunikacją błędów. W przypadku błędu z URL otrzymałem output:

Cypress could not verify that this server is running:

  > http://localhost:4100

We are verifying this server because it has been configured as your `baseUrl`.

Cypress automatically waits until your server is accessible before running tests.

We will try connecting to it 3 more times...

A w przypadku błędu URL do API:


We received this error at the network level:

  > Error: connect ECONNREFUSED 127.0.0.1:5000

-----------------------------------------------------------

The request we sent was:

Method: DELETE
URL: http://localhost:5000/users

Od razu wiadomo gdzie leży problem. Na te potrzeby zdeployowałem aplikację, którą testuję, aby była dostępna w sieci. Jest to swego rodzaju skrót na potrzeby pisania postu. Jak najbardziej możliwe jest ustawienie testów w procesie deployu całej aplikacji, mam tu na myśli frontend i backend Conduit. Zaczynam jednak od wpięcia samych testów. Nadal jednak potrzebuję dokonać zmian w projekcie testowym, aby dostosować projekt do nowych wymagań i środowiska uruchomieniowego.

Zamierzam przechowywać adresy URL w zmiennych przechowywanych w Azure DevOps. Są to poprostu zmienne środowiskowe, opakowane w funkcje na CI. Muszę to wziąc pod uwagę podczas dokonywania zmian w kodzie. Okazuję się również że nie mogę modyfikować obiektu config w Cypress gdziekolwiek tylko wymyślę. Jest to możliwe jedynie poprzez funkcje plugin. Edytuję więc plik: cypress/plugins/index.js do postaci:


module.exports = (on, config) => {
  config.baseUrl = process.env.BASE_URL || config.baseUrl;
  config.env.apiUrl = process.env.API_URL || config.env.apiUrl;
  (...)
  return config
}

Pobieram wartość zmiennej środowiskowej za pomocą polecenia process.env.BASE_URL. Jeżeli taka nie istnieje pozostawiam wartość domyślną. Bardzo ważne jest, aby pamiętać o zwróceniu obiektu config z funkcji. Bez tego elementu ta zmiana nie zadziała.

Następnie muszę dodać zmienne w Azure DevOps. Przechodzę do sekcji Pipelines, wybieram mojego pipe’a. Klikam Edit. W prawym górnym rogu wybieram Variables. Dodaje dwa Variable, które pozwolą mi na ustawienie adresu dostępnego w sieci. Dodaje je i zapisuje od tego momentu mój pipeline działa poprawnie.

zmienne środowiskowe azure devops

Dzięki integracji AzureDevOps przy każdym PR zostanie uruchomiony nasz pipeline, który może być wymagany na przykład do możliwości zmergowania zmian. Nie jest to domyślna konfiguracja, jednak jest to możliwe. Oznacza to, że możemy wymusić uruchomienie testów dla każdego PR, a sam proces uruchomienia testów został przed chwilą przeprowadzony.

integracja azure devops i github

Podsumowanie

Uruchomienie testów na Azure DevOps okazało się stosunkowo prostym zadaniem. Integracja przeszła w miarę bez problemowo. Warto pamiętać o zwróceniu obiektu config w plug-inie do Cypressa, zapomniałem to zrobić i straciłem na to dużo czasu. :) Oprócz tego jestem bardzo zadowolony z efektu. Myslę, że każdy tester powinien zapiąć swoje testy do CI najszybciej jak to tylko możliwe. Platforma jest drugoplanowa moim zdaniem.

Kod można zobaczyć tutaj:

https://github.com/12masta/react-redux-realworld-example-app/tree/8-cypress