Wywoływanie smoke testów za pomocą akcji GitHub

Autor
Damian
Terlecki
16 minut
Inne

Ostatnio, po przypadkowym uszkodzeniu funkcji wyszukiwania na moim blogu, postanowiłem zacząć dodawać automatyczne testy dla mojej strony. Od czasu do czasu lubię dodawać zmiany bezpośrednio za przez GitHuba. Z tego też powodu bardzo polubiłem podglądy wdrożeń Netlify. Doszedłem do wniosku, że idealnym rozwiązaniem byłoby, uruchamianie testów automatycznych na takim środowisku w celu szybkiej oceny sytuacji i zdalnej weryfikacji błędów, bez konieczności uruchamiania własnego środowiska.

Poniższy schemat pokazuje, co dokładnie mam na myśli:

Automatyzacja przepływu pracy

Statusy i notyfikacje Netlify

Jedną z głównych zalet Netlify jest to, że zapewnia funkcjonalność podglądów naszej strony, zanim zmiany zostaną scalone z główną gałęzią. Możemy włączyć takie podglądy w konfiguracji projektu. Dla każdego pull requesta (bądź jego aktualizacji) otrzymamy URL do "testowego środowiska". Nasza strona będzie miała wyłączone indeksowanie, więc jeśli jakiś robot jakimś cudem zawita na testową stronę, nie wpłynie to na ranking wyszukiwania.

Podglądy wdrożeniowe Netlify

Domyślnie Netlify przy okazji weryfikuje kilka rzeczy (nagłówki, przekierowania, sprawdzanie poprawności zawartości mieszanej http/https) i wysyła aktualizacje z powrotem do GitHuba za pomocą API. Najważniejsza rzecz, jaka nas interesuje, to aktualizacje statusu commitów.

Na każdą aktualizację pull requesta przypadać będą najczęściej dwie takie aktualizacje statusu. Pierwsza to informacja o wystartowaniu wdrożenia, a druga to natomiast rezultat wdrożenia (powodzenie lub niepowodzenie). Jest to bardzo dobre miejsce do rozpoczęcia akcji GitHubowej, która zainicjuje nam smoke testy / testy końcowe.

Powiadomienia Netlify

Akcje GitHub

Stworzenie akcji GitHub jest dosyć proste. Możemy to zrobić albo poprzez interfejs użytkownika na stronie (pozwala na weryfikację poprawności formatowania) bądź po prostu tworząc plik w repozytorium, np.: .github/workflows/smoke-tests.yml.

Wyzwalanie

Ponieważ Netlify publikuje aktualizację statusu dla naszego commita, będziemy nasłuchiwać na zdarzeniu status:

name: Smoke testy

on:
  status

Jest to jednak kłopotliwe, ponieważ:

Note: This event will only trigger a workflow run if the workflow file is on the master or default branch.

Co więcej, $GITHUB_SHA będzie odnośnikiem do ostatniego commita w domyślnej gałęzi. Nie do końca tego oczekujemy. Będziemy potrzebować odniesienia do ostatniego commita z powiązanego pull requesta, aby dodać własny status do testów, a także odniesienia do testowego scalenia obu gałęzi do pobrania scalonego kodu. Wrócimy do tego nieco później.

W tym momencie ważne jest zadanie sobie jednego pytania. Jeśli zaktualizujemy status akcją wywoływaną przez zmianę statusu, czy nie stworzymy przez to zapętlenia? Na szczęście sprawa ta została uwzględniona przez zespół GitHub, dzięki czemu możemy spać spokojnie.

Serwis Selenium

Niczym dziwnym nie będzie to, że wykorzystamy Selenium do przeprowadzenia naszych testów. Aby uprościć konfigurację, serwer Selenium ustawimy jako usługę, dzięki czemu nie będziemy musieli się martwić instalacją przeglądarki i innymi zależnościami.

jobs:
  test:

    runs-on: ubuntu-latest
    
    services:
      selenium:
        image: selenium/standalone-chrome
        ports:
          - 4444:4444
        options: -v /dev/shm:/dev/shm

Koniecznie musimy użyć parametru -v /dev/shm:/dev/shm, w przeciwnym przypadku, jest wielce prawdopodobne to, że przeglądarka będzie się zamykać ze względu na problemy z brakiem pamięci. Ponieważ nasze główne zadanie nie będzie uruchamiane w kontenerze, musimy również zmapować port kontenera na port samej maszyny. 4444 jest w tym przypadku domyślnym portem dla serwera Selenium.

Utworzenie statusu PENDING

Pierwszy krok rozpoczyna się od utworzenia statusu pending dla najnowszego commita z pull requesta. Będzie to wyzwolone również zdarzeniem aktualizacji status utworzonym przez Netlify. Więcej informacji na temat tego interfejsu można znaleźć w dokumentacji API.

    steps:
    - name: Utworzenie statusu PENDING
      if: ${{ github.event.state == 'pending' }}
      run: >-
        curl --location --request POST
        'https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.commit.sha }}'
        --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}'
        --header 'Content-Type: application/json'
        --data-raw '{
        "state": "pending",
        "target_url": "https://github.com/${{ github.repository }}/actions?query=workflow%253A%22${{ github.workflow }}",
        "context": "${{ github.workflow }}",
        "descrption": "Waiting for deployment."
        }'

W docelowym adresie URL ustawimy link do akcji GitHubowych z pasującą nazwą.

Powiązanie commita z pull requestem

Ponieważ nasza akcja jest uruchamiana w kontekście zdarzenia status, nie mamy dostępu do informacji o pull requeście. Niemniej jednak możemy pobrać takie informacje za pomocą API GitHuba. Najprostszym sposobem jest użycie interfejsu GraphQL. Upraszczając nieco – istnieje akcja octokit/graphql-action@v2.x, która ułatwia wywołanie punktu końcowego i zapisanie rezultatu w zmiennej wyjściowej.

Bawiąc się GitHubowym Exploratorem API GraphQL, dosyć szybko możemy utworzyć zapytanie zwracające numer pull requesta.

Odtąd, kolejne kroki będziemy również wykonywać w przy założeniu, że status zdarzenia to success, ponieważ oczekujemy, że wdrożenie zakończyło się bez problemów i przygotowujemy się do uruchomienia testów.

    - name: Pobierz numeru PR
      uses: octokit/graphql-action@v2.0.0
      if: ${{ github.event.state == 'success' }}
      id: get_pr_number
      with:
        query: |
          query getPRMergeSha($sha: String, $repo: String!, $owner: String!) {
            repository(name: $repo, owner: $owner) {
              commit: object(expression: $sha) {
                ... on Commit {
                  associatedPullRequests(first:1) {
                    edges {
                      node {
                        number
                      }
                    }
                  }
                }
              }
            }
          }
        owner: ${{ github.event.repository.owner.login }}
        repo: ${{ github.event.repository.name }}
        sha: ${{ github.event.commit.sha }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Pozostało jedynie wyodrębnić tę liczbę z danych w formacie JSON przy użyciu narzędzia jq. Możemy odwoływać się do danych pobranych w poprzednim kroku, używając składni: ${{ steps.<step_id>.outputs.<variable> }}:

    - name: Wyodrębnij numer PR
      if: ${{ github.event.state == 'success' }}
      id: extract_data
      env:
        JSON_DATA: ${{ steps.get_pr_number.outputs.data }}
      run: |
        PR_NUMBER=$(echo "${JSON_DATA}" | jq '.repository.commit.associatedPullRequests.edges[].node.number')
        echo "::set-output name=pr_number::${PR_NUMBER}"

Odwrotnie, użyjemy echo "::set-output name=<variable_name>::<value>" do ustawienia takiej zmiennej.

Zaciągnięcie kodu i wykonanie testów

Mając pod ręką numer pull requesta jesteśmy w stanie utworzyć referencję do merge commita. Taki commit jest tylko zatwierdzeniem testowym i nie znajdziemy go na lokalnej gałęzi. Tu znowu pomożemy sobie akcją action/cehckout@v2:

    - name: Checkout
      uses: actions/checkout@v2
      if: ${{ github.event.state == 'success' }}
      with:
        ref: refs/pull/${{ steps.extract_data.outputs.pr_number }}/merge

Pozostała część polega na zainstalowaniu zależności projektowych i uruchomieniu testów. Netlify zapisuje adres URL wdrożenia pod zmienną target_url w danych związanych ze zdarzeniem status.

    - run: yarn install
      if: ${{ github.event.state == 'success' && steps.yarn-cache.outputs.cache-hit != 'true' }}
    - run: yarn test-ci
      if: ${{ github.event.state == 'success' }}
      env:
        SITE_URL: ${{ github.event.target_url }}

Dla zobrazowania, do swoich testów używam frameworków WebDriverIO i Cucumber. Podczas inicjowania testów pobieram adres URL witryny za pomocą zmiennej środowiskowej: process.env.SITE_URL.

Aktualizacja statusu

Ostatecznie, aby zaktualizować status commita, tak abyśmy mogli go zobaczyć na stronie z pull requestem, ponownie wywołamy API statusowe GitHuba. W tym samym kontekście ustawiamy stan na sukces lub porażkę, w zależności od rezultatu naszych testów.

    - name: Create FINISHED status
      if: ${{ github.event.state == 'success' || failure() }}
      run: >-
        if [ ${{ job.status }} == success ]; then echo success; else echo failure; fi | xargs -I{status}
        curl --fail --location --request POST
        'https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.commit.sha }}'
        --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}'
        --header 'Content-Type: application/json'
        --data-raw '{
        "state": "{status}",
        "target_url": "https://github.com/t3rmian/devpot/actions?query=workflow%253A%22${{ github.workflow }}",
        "context": "${{ github.workflow }}",
        "descrption": "Finished smoke tests!"
        }'

Teraz możemy cieszyć się zautomatyzowaną notyfikacją:

Własny status zatwierdzenia

Wskazówki

Informacje o oprogramowaniu zainstalowanym na GitHubowych środowiskach wirtualnych, można znaleźć w tym repozytorium w kolumnie "Included Software".

Za pomocą akcji action/setup-node@v1 możemy skonfigurować konkretną wersję NodeJS, a dzięki peter-evans/commit-comment@v1 jesteśmy w stanie zastąpić status prostym komentarzem:

    - name: Utworzenie komentarza PENDING
      uses: peter-evans/commit-comment@v1
      with:
        sha: ${{ github.event.commit.sha }}
        body: Wdrożenie podglądowe wystartowało, oczekiwanie na zakończenie w celu rozpoczęcia testów...
    - name: Use Node.js 10.x
      uses: actions/setup-node@v1
      with:
        node-version: 10.x

Aby wyświetlić zawartość zmiennych GitHuba, użyj:

    - name: Wyświetl kontekst GitHubowy
      env:
        GITHUB_CONTEXT: ${{ toJson(github) }}
      run: echo "$GITHUB_CONTEXT"

Na potrzeby testowania API np. w Postmanie wystarczy przełączyć się z autoryzacji tokenowej na Basic Auth.

2020/05/15: Wczoraj pojawiła się nowa wersja runnera akcji Github (v2.262.1), w której to dodano następującą poprawkę:

Sps/token migration fix, job.status/steps.outcome/steps.conclusion case match with GitHub check suites conclusion (#462)

Kod został już zaktualizowany, gdyż poprawka powodowała to, że na koniec testów przy pozytywnym rezultacie wysyłana była aktualizacja statusu ze stanem 'failure'. Zbiegiem okoliczności była też aktualizacja akcji octokit/graphql-action do wersji v2.2.0, co w połączeniu z wcześniej wspomnianą poprawką spowodowało błąd:

incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line at line 1, column 40:
&nbsp&nbsp&nbsp&nbsp... etPRMergeSha($sha: String, $repo: String!, $owner: String!) {

Na szczęście przejście na wersję 2.0.0 rozwiązuje problem.

2020/12/20: Zgodnie z niedawnym wątkiem na forum społecznościowym Netlify, deploy wersji podglądowej opiera się jednak na ostatnim commicie gałęzi źródłowej, a nie na merge commicie (co zostało błędnie opisane w dokumentacji). Jeśli chcesz dopasować wersję kodu do testów, musisz zmienić ref: refs/pull/${{ steps.extract_data.outputs.pr_number }}/merge na ref: refs/pull/${{ steps.extract_data.outputs.pr_number }}/head. Problem można również rozwiązać, wykonując rebase względem gałęzi docelowej – wtedy deploy podglądowy będzie już zawierał połączone zmiany.