Dogu가 오픈소스 제품이 아니였을 당시, 사무실 내 장비를 GitHub Runner를 통해 연결하고 커밋을 할 때마다 Runner 장비에서 checkout 받은 후 E2E 테스트를 실행하도록 했습니다. 그 당시는 테스트 케이스가 많지 않던 상황이었는데요, Dogu를 오픈소스화 하면서 프라이빗한 GitHub Runner를 떼어내던 상황에서 잠시 E2E 테스트를 꺼두게 되었습니다. 오픈소스화 이후 약 1달 반 이후에 다시 E2E 테스트를 실행하도록 바꾸었는데 그 과정을 공유하고자 합니다.

GitHub - dogu-team/dogu: Test automation platform for web, mobile, game applications with many test frameworks
Test automation platform for web, mobile, game applications with many test frameworks - GitHub - dogu-team/dogu: Test automation platform for web, mobile, game applications with many test frameworks

TL;DR

이전에 Dogu의 E2E 테스트는 부족한 영역이었지만, 오픈소스화 후 테스트를 개선하기 위해 노력함. 프라이빗 GitHub Runner 기존 방식에서 Dogu를 이용해 E2E 테스트를 실행하는 방식으로 변경. 테스트 환경과의 연결을 통해 로컬환경 테스트를 실행하도록 설정. 여러 플랫폼에서 동시에 실행 가능한 Dogu 루틴 기능 사용. dogu-github-action을 사용하여 자동화된 E2E 테스트 통합. CI와의 연결 이후 E2E 테스트의 효율성 향상으로 개발자들의 업무 부담 감소 및 제품 품질 향상.

Dogu를 위한 테스트

오픈소스 이전에는 사실 E2E 테스트가 커버해주는 영역이 제품의 기능에 비해 훨씬 적었습니다. (Dogu 팀원 모두 기능 개발을 하느라 우선순위가 내려갔습니다..🥲) 그런 상태에서 오픈소스화를 하게 되었고, 팀 내부에서 릴리즈 하기 전 QA를 하기로 프로세스를 정했습니다.

E2E가 꺼진 약 1달 반 동안, 릴리즈 하는 날은 흡사 전장을 방불케 합니다. 테스트와의 전쟁이죠. 많은 기능을 손으로 직접 해야했었고, 테스트 해야 할 플랫폼도 3개가 있었습니다.

"□□□은 mac ARM64 장비에서 테스트 하시면 될 것 같고요, △△△은 windows x64 장비에서 테스트 해주세요."
"mac x64는 누가 할까요..?"
...

실제로 테스트할 때 참고했던 테스트 케이스 문서

위와 같이 많고 반복적인 테스트를 릴리즈하는 날 당일에 하고, 이슈가 발견되면 빨리 고쳐야하니 항상 바쁘고, 이런 날이 매 주마다 반복되니 팀원들이 지쳐가기 시작합니다. 이런 상황이 익숙하시다구요? 자동화된 E2E 테스트가 필요하단 증거입니다!😎

이런 상황에서, 외부에서 이런 말씀을 주시더군요. "Dogu는 어떻게 테스트 하시고 있나요?". 이 당시에는 Dogu를 테스트해볼 수 있는 기능이 많이 부족한 상태였습니다. 이후 저희는 하나의 결론에 도달했습니다. 우리가 우리 제품(Dogu)을 많이 써야 유저분들에게도 어떤 부분이 도움이 되고, 어떤 부분이 개선이 필요한지 알 수 있기에, "Dogu E2E 테스트를 Dogu를 이용해 하자!" 라는 것이였죠. 그래서 저희 팀은 최소한의 노력으로 다시 CI에서 E2E를 추가하기로 했습니다.

마주친 이슈들...

사실 E2E 테스트까지 쉽게 도달할 수 있을 것 같았지만, 항상 작업은 저희의 예상을 빗나가네요...😭 작은 이슈들도 많았지만, 큰 이슈들 몇 가지에 발목을 잡혔습니다.

로컬 환경 실행하기

매 커밋마다 빌드 후 도커 이미지를 말아서 클라우드 저장소에 올리고, 서버 장비에 해당 도커 이미지를 받아 실행하고 E2E 장비에서 클라이언트를 띄우는 방식은 비용 문제나, 시간이 오래걸린다는 의견이 많아 오픈소스 하기 이전처럼 E2E 장비에서 로컬로 받아서 테스트를 수행하기로 했습니다.

그러나 Dogu를 오픈소스화 시킨 이후, GitHub Docs에서 프라이빗 저장소에서만 추천한다고 가이드가 있었기에, 저희는 프라이빗 GitHub Runner를 대부분 제거한 상태였습니다. 저희는 이 가이드를 지키면서 Dogu를 통해 Dogu를 테스트 하기 위해 내부적으로 사용하는 테스트 Dogu 환경에 E2E 장비를 연결합니다. 그리고 E2E 장비 로컬에서의 Dogu Agent 실행을 위해 E2E 장비에 테스트 환경과의 연결에 사용한 Dogu Agent 앱을 튜닝했습니다. 이유를 들자면 Dogu E2E 에서 디바이스 스트리밍 관련 기능을 테스트하는데, 두개의 Dogu Agent에서 동시에 디바이스를 인식하게 되면 문제가 생기기 때문입니다.

여러 클라이언트 동시에 제어하기

일단 Dogu는 웹 브라우저와 디바이스 팜 구축을 위한 데스크탑 앱을 사용하는데, E2E 테스트 장비에서 두 클라이언트를 모두 띄워서 실행해야 했습니다. 저희가 제공하는 자동화 테스트 기능으로는 리모트 테스트와 루틴이 있는데, 리모트 환경에서 테스트 하기에는 부족했습니다.

리모트 테스트는 유저가 테스트 스크립트를 저장소에 커밋하기 전에 테스트 스크립트가 잘 작동하는지 로컬에서 Dogu에 연결된 디바이스를 통해 테스트 하기위해 고려된 기능이였기 때문입니다. 여러 디바이스에서 실행과 두 클라이언트를 띄워서 실행하는 기능은 아직 없는 상태입니다.

그래서 여러 디바이스에서 동시에 실행이 가능하고, 확장 가능한 루틴을 선택했습니다. 루틴은 GUI 또는 YAML로 작성하고, 루틴에서 제공하는 action을 통해 여러 환경(플랫폼)과 스크립트를 실행할 수 있도록 설계를 했었기에 가능했던 것 같습니다. Dogu E2E 테스트도 하나의 루틴에서 macOS arm64, macOS x64, Windows 장비를 모두 돌리도록 했습니다. Dogu에서 Git 연동 기능도 추가해 로컬에서 실행할 수 있는 환경 또한 쉽게 갖출 수 있도록 했습니다.

아래는 Dogu를 테스트 하기 위해 실제 사용하는 루틴 YAML 형식입니다!

name: e2e
on:
  workflow_dispatch:
jobs:
  e2e-macos:
    runs-on:
      group:
        - e2e-macos
    steps:
      - name: env
        run: printenv
      
      - name: fix diverged main
        run: git reset --hard origin/main

      - name: Checkout
        uses: dogu-actions/checkout
        with:
          clean: true
      
      - name: create dotEnv
        run: |
          ~/.dogu_bin/env-generator gen-all e2e

      - name: Run newbie
        run: yarn newbie:cicd

      - name: Run newbie:python
        run: export PATH=/opt/homebrew/bin:/usr/local/Cellar/poetry/1.5.1/bin:$PATH && yarn newbie:python
      
      - name: Build Projects
        run: printenv && yarn workspace dogu run build

      - name: Run influx, pgsql, redis, nexus, turn-server
        run: |
          yarn workspace console-web-server run start:e2e-background
        env:
          PATH: /usr/local/bin:$PATH
          
      - name: Newbie nm-space
        run: yarn workspace dogu run newbie:nm-space

      - name: Build nm-space
        run: cd nm-space && yarn workspace nm-space run build

      - name: Download, Build third-party
        run: |
          export PATH=/opt/homebrew/bin:/usr/local/Cellar/cmake/3.27.0/bin:/usr/local/go/bin:$PATH && yarn third-party:download:build     

      - name: Run e2e
        run: |
          yarn workspace e2e run util:install-chromedriver && yarn workspace e2e run start:ci

    record: true
  e2e-windows:
    runs-on:
      group:
        - e2e-windows
    steps:
      - name: Printenv
        run: set

      - name: Checkout
        uses: dogu-actions/checkout
        with:
          clean: true
      
      - name: create dotEnv
        run: |
          $HOME/.dogu_bin/env-generator gen-all e2e

      - name: Run newbie
        run: yarn newbie:cicd

      - name: Run newbie:python
        run: yarn newbie:python
      
      - name: Build Projects
        run: set && yarn workspace dogu run build

      - name: Run influx, pgsql, redis, nexus, turn-server
        run: |
          yarn workspace console-web-server run start:e2e-background
      
      - name: Newbie nm-space
        run: yarn workspace dogu run newbie:nm-space

      - name: Build nm-space
        run: cd nm-space && yarn workspace nm-space run build

      - name: Download, Build third-party
        run: |
          yarn third-party:download:build

      - name: Run e2e
        run: |
          yarn workspace e2e run util:install-chromedriver && yarn workspace e2e run start:ci
    record: true

CI와 연결하기

이제 준비는 모두 갖추었습니다! 매 커밋마다 우리는 Dogu 루틴을 실행시키기만 하면 완성입니다. 그런데 이제 보니 GitHub Action에서 Dogu 루틴을 실행할 수 있도록 해야 했습니다. 저희는 GitHub Action에서 Dogu 루틴을 실행할 수 있도록 GitHub Custom Action을 만들기로 합니다. 그렇게 완성된 것이 dogu-github-action입니다!

아직 Dogu로부터 로그와 같은 정보를 가져오진 않지만, 루틴이 끝날 때 까지 기다리고, 끝나면 루틴 결과 주소를 알려주거나, 루틴이나 GitHub Action 취소 시 처리가 가능한 최소한의 요구사항을 충족하도록 했습니다.

이제 테스트 스크립트 작성하셔야죠?

Dogu E2E 테스트 스크립트는 Dest 라는 저희 팀에서 자체 제작한 테스트 프레임워크와 Selenium, Playwright를 사용해 작성되었습니다. Dogu 웹은 Selenium으로, Dogu Agent는 Electron으로 만들어진 데스크탑 앱이여서 Playwright로 작성해보았습니다. (Dest 는 더이상 개발하지 않을 예정이라 Jest 또는 Pytest 로 변경할 예정입니다!)

기존 E2E 스크립트는 이전 스펙을 기준으로 적혀있어서 테스트 케이스가 약 30개 정도 있었습니다. 저희는 130여개 되는 케이스를 테스트하도록 스크립트를 보강했습니다. 웹 UI가 바뀌거나, div요소가 하나 더 들어가거나 했을 때 Full XPath는 매우 위험할 수 있어 id 등의 고유하거나 특정 가능한 attributes들을 사용해 XPath를 만들어 스크립트를 작성합니다.

이제 Dogu가 제공하는 기능 중 많은 것을 E2E가 테스트해줍니다. 그것도 매 커밋마다죠! 저희는 이제 평소에 개발할 때 좀 더 편안하게 할 수 있습니다. 코드 변경으로 인한 기능의 비정상적인 동작은 E2E 테스트에서 잡아낼 수 있습니다.

Happy Testing!

최근 E2E 테스트 결과를 한번 보도록 하겠습니다. macOS ARM64 장비를 제외하고는 모두 실패했습니다.

M1 맥북에서 모든 E2E 항목이 성공한 모습

한번 Mac mini 장비에서 실패한 원인을 찾아보러 가봐야 할 것 같습니다.

실패한 테스트 케이스와 로그 정보

일단 로그 상으로는 //*access-id="add-host-form-name" XPath와 일치하는 요소를 찾지 못한 것 같습니다. 이 정보만으로는 뭔가 부족합니다. 왜 못찾았는지, 실제로 어떤 동작을 했는지까지는 알아내기 어렵기 때문이죠. 이를 위해 루틴에서 record: true 로 장비의 영상을 찍도록 했는데, 녹화 영상을 한번 보도록 합니다!

루틴 결과 리포트에서 녹화 영상 모습

이렇게 녹화영상을 통해 실제 동작한 내용을 다시 볼 수 있어 어떤 부분이 잘못되었는지 알기 편해졌습니다. E2E 테스트가 실패하더라도 디버깅을 위한 시간이 많이 단축되어 금방 문제 부분을 고칠 수 있습니다!

마치며...

E2E Passing!

이제 릴리즈 당일이 아니라 매 커밋 또는 PR마다 E2E 테스트를 수행하니 릴리즈 당일에 챙겨야 하는게 상당히 줄어들어 든 것 같습니다. Dogu 루틴과 테스트 스크립트 작성으로 매번 회원가입부터 시작해서 기능 테스트 하고, 플랫폼마다 테스트 하는게 엄청 쉬워졌습니다!

하지만 아직 E2E 테스트가 모든 케이스를 커버하진 못하고 있는게 아쉬운 점 중 하나입니다. iOS 스트리밍, 리모트 등 몇개의 기능을 아직 손으로 하고 있습니다만, 예전의 반복적으로 테스트하던 기능들(ex. 회원가입, 멤버 추가, 스트리밍 등등..)을 E2E에서 체크하니 훨씬 수월한 것 같습니다.

여러분들도 아직 손으로 테스트를 매번 반복적으로 하고 계신가요? 한번 테스트 자동화 환경을 구축하고 통해 효율적으로 테스트를 해보시는 것도 좋을 것 같습니다!