Coderifleman's blog

frontend development stories.

「E2E」가 태그 돼 있는 글

  • 인생을 살다 보면 괴롭지만 꼭 해야만 하는 일을 만나게 된다. 프런트엔드개발자에겐 그런 일 중 하나가 바로 UI 테스트가 아닐까 싶은데, 이 고통스러운 일을 조금이나마 덜어줄 잘 만들어진 도구나 프레임워크를 찾지만 쉽지 않다. 처음엔 좋아 보여도 실제 테스트를 작성하다 보면 금세 그 도구가 가진 한계점을 만나게 된다. 그래서 그런지 다른 일보다도 더욱 도구에 의존하게 되고 개선된 또 다른 도구를 찾게 되는 것 같다.

    TestCafe는 자바스크립트 소식을 매주 정리해 공유하는 사이트인 JSer.info2016년 10월 24일 자 소식을 통해 알게 됐다. 해당 문서에 링크된 TestCafe로 브라우저 자동 테스트(일본어)를 읽어보았는데 생각보다 느낌이 좋아서 한번 리뷰해보자는 결론을 내렸다.

    TestCafe 소개

    TestCafe는 DevExpress가 개발한 E2E 테스트 프레임워크다. InfoQ에 TestCafe와 관련된 인터뷰 글(
    TestCafe with Smart Script Injection, Traffic and Markup Analysis Tools
    )이 있으니 관심 있는 사람은 참고하길 바란다. 같은 이름의 웹 서비스 및 클라이언트 앱도 서비스 중인데 이 서비스는 셀레니움 IDE 처럼 GUI로 조작하고 행위를 기록하여 재생할 수 있다.

    TestCafe는 webdriber.io나이트왓치와는 다르게 테스트 관련 스크립트를 주입해 동작하는 셀레니움 RC(Selenium RC)와 흡사한 방식으로 개발됐다. 사실 셀레니움 RC가 가진 한계를 극복하고자 셀레니움 웹드라이버(Selenium WebDriver)를 개발했는데 다시 셀레니움 RC와 비슷한 구조로 테스트 프레임워크를 만들었다고 해서 “그렇다면 과거에 경험했던 한계를 그대로 답습하는 게 아닌가?”하고 조금 의아했다.

    TestCafe의 개발자 이반 니쿨린(Ivan Nikulin)Why not use Selenium?에서 그 이유를 밝혔는데 간단히 말해서 테스트 환경에 대한 복잡한 설정 없이 실행할 수 있고, 모바일 기기에서도 원격 접속해 테스트할 수 있는 도구를 만들고 싶어 했던 거 같다. 또, 웹드라이버의 호환성 문제를 회피하기 위한 목적도 있는 것 같다.

    셀레니움은 분명 훌륭한 도구지만 설정이 복잡하고 웹드라이버 자체의 버그로 인해 테스트 작성에 종종 걸림돌이 되는 경우가 있다. 또 테스트 코드 자체를 디버깅하기가 까다로워 복잡한 테스트 케이스를 작성하는데 어려운 면도 가지고 있다. 과거 셀레니움 RC 방식에 한계가 있어 셀레니움을 만들었지만 새로운 문제들이 나타났다. 이러한 상황에서 TestCafe의 지향점이 좋은 해결책이 될 수 있을까?

    좋은 인상

    필자는 유료 웹툰을 서비스하고 있는 레진(Lezhin)을 이용해 로그인 테스트를 작성해 봤다. 예제 코드는 저장소 UYEONG/demo-testcafe를 참고한다. 이번 절에서는 이 예제를 이용해 필자가 받은 몇 가지 좋은 인상을 소개하겠다.

    test('사용자는 GNB 메뉴에서 로그인할 수 있다.', async (t) => {
        // Given
        const popupAttendanceLogin = new PopupAttendanceLogin(t);
    
        if (await popupAttendanceLogin.exist()) {
            await popupAttendanceLogin.close();
        }
    
        await t
            .click('#main-menu-toggle')
            .typeText('#login-email', ACCOUNT.USER_NAME)
            .typeText('#login-password', ACCOUNT.PASSWORD);
    
        // When
        await t
            .click('form.login-form button[type=submit]')
            .wait(1000);
    
        // Then
        await t.click('#main-menu-toggle');
    
        const email = await getElement('sidenav-email');
    
        assert(email.visible);
        assert(email.innerText === ACCOUNT.USER_NAME);
    });

    위는 레진에서 GNB 메뉴를 이용해 로그인이 정상적으로 이뤄지는지 테스트하는 코드다. 그리고 이 코드는 await/async를 이용해 비동기적 절차를 동기적으로 표현하고 있다. TestCafe는 바벨(Babel)을 내장하고 있어 별도의 설정 없이 최신 사양을 이용할 수 있다. 최신 사양으로 코드를 작성하고자 할 때 복잡한 세팅을 해줘야 하는 기존의 테스트 프레임워크와는 다른 부분이다.

    TestCafe에서 디버깅하기
    <그림 1. TestCafe에서 디버깅하기>

    또, await/async 방식으로 테스트 코드를 작성하면 디버깅이 쉽다는 장점이 있는데 체이닝을 펼치기 쉬우므로 각 액션을 단계별로 관찰할 수 있다. 나이트왓치는 파이프라인 방식으로 디자인돼 있어 디버깅이 다소 까다롭다.

    그럼 이제 실행을 해보자. 해당 저장소를 클론하고 다음 명령어를 입력하면 바로 테스트할 수 있다.

    $ git clone git@github.com:UYEONG/demo-testcafe.git
    $ npm install
    $ npm run test

    npm scripts에 등록한 test 명령은 다음과 같다.

    $ testcafe chrome tests/

    뭔가 추가적인 설정이 없으니 오히려 불안하다. 하지만 그 안락함에 금방 익숙해진다. 이것저것 세팅해줘야 했던 셀레니움 기반 프레임워크(참고)와는 사뭇 다른 경험이다.

    로그인 테스트 실행 결과
    <그림 2. 로그인 테스트 실행 결과>

    그림 2는 로그인 테스트가 진행되는 모습이다. 이 테스트의 진행 절차는 다음과 같다.

    1. www.lezhin.com 페이지에 접근한다.
    2. 최초에 출력된 팝업이 있다면 닫는다.
    3. 우측 상단의 메뉴 버튼을 클릭한다.
    4. 이메일 / 패스워드를 입력하고 로그인 버튼을 선택한다.
    5. 페이지가 갱신되면 다시 우측 상단의 메뉴 버튼을 클릭한다.
    6. 로그인이 정상적으로 완료 됐는지 확인한다.

    특정 절차에서 다음 절차로 넘어가기 위해선 지연 시간(Delay time)이 필요하다. 예를 들어 최초 페이지에 접근할 때는 콘텐츠가 모두 출력되는 시점을 기다려야 하고 팝업을 닫을 때는 애니메이션(FadeOut)이 종료되는 시간을 기다려야 한다. 나이트왓치에서는 이런 지연 시간을 직접 명시해줘야 한다.

    // 페이지에 최초 접근 시 body 엘리먼트가 보일 때까지 5000ms 기다린다.
    this._header
        .navigate()
        .waitForElementVisible('body', 5000);
        
    // 팝업을 닫을때 애니메이션 시간을 고려해 500ms 기다린다.
    this.click('@close');
    this.api.pause(500);

    하지만 TestCafe를 이용할 땐 지연 시간을 직접 입력할 일이 상대적으로 적다. TestCafe는 지연 시간을 직접 계산하고 관리한다. 실제로 위 로그인 테스트 코드를 보면 지연 시간을 명시한 지점은 로그인 버튼을 클릭한 시점 즉, 폼을 서브밋하고 갱신되기를 기다리는 딱 한 곳뿐이다.

    await t
        .click('form.login-form button[type=submit]')
        .wait(1000);

    지연 시간이라고 해도 거의 대충 시간을 짐작해 입력하는 일에 불과하다. 물론 비기능적 요구사항도 테스트에 포함돼야 하지만 애니메이션 종료 시점까지 일일이 명시해야 한다는 것은 분명 귀찮은 일이다.

    그리고 이벤트 지점을 커서로 표현해주거나 실제 타이핑을 하는 느낌을 살려 텍스트를 입력하는 부분도 인상적이다. 셀레니움 기반 테스트 프레임워크는 이런 자연스러운 느낌이 상대적으로 적다.

    TestCafe의 에러 리포팅
    <그림 3. TestCafe의 에러 리포팅>

    마지막으로 에러 리포팅도 상당히 깔끔한 편인데 어느 지점에서 어떠한 에러가 낫는지 알기 쉽게 출력해준다. 그림 3을 보면 24번째 행의 코드에 문제가 있음을 쉽게 알 수 있다.

    구구절절 설명했지만 TestCafe를 리뷰하면서 좋은 인상을 받은 점을 간단히 정리하면 다음과 같다.

    • 바벨을 내장하고 있어서 특별한 설정 없이 ES6+ 사양을 사용할 수 있다.
    • 테스트 코드 디버깅이 상대적으로 쉽다.
    • 특별한 설정 없이 커멘드 라인 명령으로 바로 테스트할 수 있다.
    • 특정 조작에 대한 지연 시간을 자동으로 관리한다.
    • 테스트 실패 및 에러 리포팅이 깔끔한 편이다.

    아쉬운 점

    분명 기존의 E2E 테스트 프레임워크보다 몇 가지 좋은 인상을 가지고 있는건 분명하다. 하지만 아쉬운 점도 있다. 일단 다양한 상황을 테스트하기엔 액션 셋과 API가 부족하다.

    TestCafe와 그 외 프레임워크의 API 목록
    <그림 4. TestCafe와 그 외 프레임워크의 API 목록>

    왼쪽 부터 차례대로 TestCafe, 나이트왓치, webdriver.io 가 제공하고 있는 액션 및 API 목록이다. 기본적인 액션은 제공하지만, 모바일에 특화된 액션이나 스크립트 권한 밖의 액션 등은 이용하기 힘들다. 시간이 지나면서 제공될 수 있는 API도 있지만 TestCafe가 가지고 있는 구조적 한계로 인해 아예 불가능한 API도 있다.

    또, E2E 테스트를 할 때 좋은 패턴들이 있는데 그중 하나가 PageObject다. 테스트에 필요한 반복적인 행위나 엘리먼트 셀렉터 등을 밖으로 노출 시키지 않고 페이지 단위(혹은 컴포넌트 단위)로 추상화해 제공할 수 있다(참고). PageObject는 테스트 코드의 가독성이나 유지 보수 측면에서 훌륭한 패턴이지만 TestCafe에서는 제공하지 않는다.

    // page-objects/popup-attendance-login.js
    import {Selector} from 'testcafe';
     
    const querySelector = Selector(q => document.querySelector(q));
     
    class PopupAttendanceLogin {
        elements = {
            wrapper: '#popup-attendance-login',
            closeBtn: '#popup-attendance-login .attlogin__close'
        };
    
        constructor(testController) {
            this.t = testController;
        }
    
        async exist() {
            const wrapper = await querySelector(this.elements.wrapper);
            return wrapper.visible;
        }
    
        async close() {
            const closeBtn = await querySelector(this.elements.closeBtn);
            await this.t.click(closeBtn);
        }
    }
     
    export default PopupAttendanceLogin;
    
    // tests/signin-test.js
    import PopupAttendanceLogin from '../page-objects/popup-attendance-login';
    
    test('사용자는 GNB 메뉴에서 로그인할 수 있다.', async (t) => {
        // Given
        const popupAttendanceLogin = new PopupAttendanceLogin(t);
    
        if (await popupAttendanceLogin.exist()) {
            await popupAttendanceLogin.close();
        }
        // ... 생략 ...

    그래서 필자는 위 코드처럼 직접 PageObject와 비슷한 객체를 직접 만들고 테스트 코드를 작성했다. 만약 프레임워크 자체에서 이 개념을 제공한다면 조금 더 편리하게 코드를 작성할 수 있을 것 같다.

    끝으로

    여기까지 TestCafe를 소개하고 필자가 느낀 좋은 인상과 아쉬운 점을 함께 이야기했다. 이 도구가 우리의 UI 테스트 환경의 답이 돼 줄 것이라 생각하지 않는다. 분명 실제로 테스트를 작성하기 시작하면 온갖 버그와 미흡한 점을 만나게 될 것이다. 하지만 아직 시작된 지 얼마 안 된 프로젝트라는 점을 미루어 볼 때 차차 개선될 것이라고 긍정적으로 생각할 수 있다.

    중요한 건 그들이 어떤 문제를 해결하고 싶어 하고 어디에 지향점을 두고 있느냐다. 그것이 내 앞에 놓인 문제 혹은 환경과 맞아떨어진다면 더할 나위 없는 좋은 도구가 될 것이다.

  • 나이트왓치를 소개하기 전에 E2E 테스트의 정의부터 셀레니움 웹드라이버 등 기본 개념부터 간단히 소개하겠다.

    E2E 테스트

    정의

    소프트웨어 테스트는 테스트의 규모(레벨)에 따라 유닛 테스트, 통합 테스트, 시스템 테스트, 인수 테스트 이렇게 4가지로 분류한다. 여기에서 E2E 테스트는 시스템 테스트에 속한다.

    E2E(End-to-End) 테스트는 전체 시스템이 제대로 작동하는지 확인 하기 위한 테스트로 시나리오 테스트, 기능 테스트, 통합 테스트, GUI 테스트를 하는데 사용한다. API와의 연동도 테스트 항목에 포함되기 때문에 일반적으로 목(Mock)이나 스텁(Stub)과 같은 테스트 더블을 사용하지 않으며 최대한 실제 시스템을 사용하는 사용자 관점에서 시뮬레이션 한다. 그래서 테스트 속도가 서비스 규모에 따라 상당히 느릴 수 있기 때문에 유닛 테스트나 기능 테스트를 위한 일반적인 테스트 자동화와 시스템 테스트를 위한 E2E 테스트 자동화를 함께 구성한다.

    E2E 테스트 프레임워크

    E2E 테스트 프레임워크는 다양한 종류가 있는데, 크게 헤드리스 브라우저를 의존하는 것과 셀레니움 웹드라이버를 의존하는 것으로 나눌 수 있다. 셀레니움 웹드라이버는 다음 절에서 자세히 설명한다.

    헤드리스 브라우저는 커맨드 라인 명령어로 조작할 수 있는 화면이 없는 브라우저로 Jsdom 기반의 좀비(Zombie.js), 웹킷 엔진 기반의 팬텀(Pantom.js), 겟코 엔진 기반의 슬리머(Slimer.js) 등이 있다. 잘 알려진 캐스퍼(Casper.js)는 팬텀과 슬리머를 조금 더 사용하기 쉽게 만들어 놓은 유틸리티 도구다. 헤드리스 브라우저는 기본적으로 크로스 브라우징 테스트가 불가능하며 어썰트(Assert)도 내장하고 있지 않기 때문에 필요하다면 추가를 해야한다.

    셀레니움 웹드라이버를 의존하는 프레임워크로는 webdriver.io, 큐컴버(Cucumber.js), 프로트랙터, 나이트왓치 등이 있다. 이들은 크로스 브라우징 테스트가 가능하고 어썰트도 내장하고 있다. 단, 각 프레임워크 마다 내장하고 있는 어썰트 라이브러리는 다르다.

    셀레니움 웹드라이버

    나이트왓치는 셀레니움 웹드라이버(Selenium WebDriver) API를 사용해 개발된 E2E 테스트 프레임워크이기 때문에 본격적으로 사용해보기 전에 셀레니움과 웹드라이버를 먼저 이해할 필요가 있다.

    셀레니움 웹드라이버의 원래 이름은 셀레니움(또는 셀레니움 1.0)이었다. 셀레니움은 웹 브라우저를 사용하여 웹 애플리케이션을 테스트하는 오픈 소스 도구다. 이때 사람의 손으로 직접 웹 브라우저를 조작하는 것이 아니라 작성된 스크립트에 따라 자동으로 조작한다. 이러한 방법을 브라우저 자동화(Browser Automation)라고 표현한다.

    셀레니움은 시카고에 위치한 소트워크스(ThoughtWorks) 사에서 개발을 시작했다. 소트워크스는 마틴 파울러(Martin Fowler)가 속한 그룹으로 유명하다.

    웹드라이버는 셀레니움의 단점을 보완하고자 구글의 엔지니어들이 개발하고 사용한 브라우저 자동 테스트 도구이다. 2006년 경 구글에서 근무 중이던 시몬 스튜어트(Simon Stewart)가 주도해 프로젝트를 시작하고 2009년에 처음으로 공식 발표했다.

    Selenium Projects
    <그림 1. 셀레니움 프로젝트의 흐름>

    과거 셀레니움은 자체 엔진인 셀레니움 RC(Remote Control)를 이용해 브라우저와 통신했다.

    셀레니움 RC는 자바나 파이썬 등의 언어로 스크립트를 작성하면 그 스크립트를 기반으로 브라우저를 조작하는 자바스크립트를 생성하고 해당 페이지에 삽입 후 브라우저를 조작하는 간단한 구조였다. 이러한 구조는 브라우저의 보안 제약이나 자바스크립트의 한계로 인해 실효성이 떨어지는 단점이 있었다. 이 단점이 시몬 스튜어트가 웹드라이버를 만들게 된 이유이기도 하다.

    그에 반해 웹드라이버는 브라우저의 확장 기능과 OS의 기본 기능 등을 이용하여 브라우저를 조작하는 구조였다. 이는 셀레니움 RC의 단점을 충족해줄 수 있는 방식이었다.

    Selenium webdriver high level block diagram
    <그림 1. 셀레니움 웹드라이버 다이어그램>

    이 방식이 성공하여 셀레니움 RC와 웹드라이버 통합이 이루어졌고 2011년 7월에 셀레니움 웹드라이버(또는 셀레니움 2.0)를 릴리즈하게 된다. 즉, 현재 우리가 알고있는 셀레니움은 웹드라이버와 통합한 버전이다.

    그림 2를 보면 알 수 있듯이 웹드라이버는 다양한 브라우저와 환경을 대응해야하는데, 브라우저마다 이를 위한 API가 다를 경우 또 다른 문제가 발생할 수 있기 때문에 현재 표준화를 제정(W3C WebDriver) 중이다.

    현재 셀레니움 웹드라이버는 파이썬, 루비, 자바, C## 그리고 Node.js를 이용해 웹브라우저는 조작할 수 있도록 다양한 API를 제공하고 있다. 하지만 셀레니움 서버와 자바스크립트의 궁합이 좋지 않고, 돔을 조작 하거나 셀렉팅하는데 한계가 있어 셀레니움 웹드라이버와 노드를 바인딩하여 다양한 기능을 제공하는 여러가지 형태의 프로젝트가 생겨났다. 그 중 유명한 프로젝트가 바로 webdriver.io와 나이트왓치 그리고 앵귤러 프로젝트를 위한 프로트랙터다.

    이들 도구는 웹드라이버 API를 사용할 때 생기는 다양한 패턴을 추상화한 API와 신택스 슈가 등을 제공해 셀레니움 2.0 보더 더 편리하고 다양한 경험을 제공한다.

    나이트왓치

    나이트왓치는 노드 기반의 E2E 테스트 프레임워크다. 셀레니움 웹드라이버를 중개하여 각종 브라우저를 조작하고 동작이 기대한 것과 일치하는지 테스트하는데 사용한다. CSS 셀렉터로 엘리먼트를 셀렉팅하여 테스트를 작성할 수 있도록 하는 기능과 신텍스 슈가 그리고 단순하고 간결한 문법을 제공한다. 또한 테스트 러너를 포함하고 있으므로 독자적으로 그룹화한 테스트를 한번에 실행할 수 있으며 지속적인 통합의 파이프 라인과 합칠 수 있다는 특징을 가지고 있다.

    나이트왓치를 알게 된건 나보다 먼저 E2E 테스트를 리서치하고 관련 도구를 찾고있던 훈민이형(개발왕 김코딩, 블로그) 덕분이었다. 미리 삽질을 하고 계셨기 때문에 다른 도구를 선택하기 보다 같이 삽질하는 편이 고민할 시간도 적어서 큰 고민 없이 사용했다.

    설치하기

    나이트왓치 설치는 개발자 가이드 Getting Started 절에 잘 설명돼 있다. 이 문서에는 간단하게 요약해 설치 과정을 설명한다. 우선 NPM을 이용해 설치한다.

    $ npm install --save-dev nightwatch

    웹드라이버로 브라우저와 통신하기 위해서는 셀레니움 서버를 실행시켜야한다. 셀레니움 서버 다운로드 사이트에서 파일을 다운 받고 아래와 같이 서버를 실행한다. 이 글을 작성하는 현재 기준 가장 최신 버전은 2.53.0 이다.

    프로젝트 디렉터리에서 nightwatch.json을 생성하고 다음과 같이 작성한다. 옵션의 자세한 설명은 개발자 가이드 Configuration 절을 참고한다.

    {
      "src_folders" : ["tests"], // 테스트할 디렉터리, 배열로 지정
      "output_folder" : "tests/reports", // JUnit XML 리포트 파일이 저장될 위치
      "custom_commands_path" : "", // 불러올 커스텀 커맨드가 있는 위치
      "custom_assertions_path" : "", // 불러올 커스텀 어썰트가 있는 위치
      "page_objects_path" : "", // 불러올 페이지 객체가 있는 위치
      "globals_path" : "", // 불러올 외부 글로벌 모듈이 있는 위치
      "selenium" : {   // 셀레니움 서버 환경 설정
        "start_process" : true, // 테스트 시작시 셀레니움 프로세스를 자동으로 실행할 것 인지 여부
        "server_path" : "./selenium-server-standalone-2.53.0.jar", // 셀레니움 서버 jar 파일의 경로, start_process가 false면 지정하지 않아도 된다.
        "log_path" : "tests/logs", // 셀레니움의 output.log 파일이 저장될 경로
        "host" : "127.0.0.1", // 셀레니움 서버의 listen ip
        "port" : 4444, // 셀레니움 서버의 listen port
        "cli_args" : { // 셀레니움 프로세스로 넘겨질 cli 인자 목록
          "webdriver.chrome.driver" : "",
          "webdriver.ie.driver" : ""
        }
      },
      "test_settings" : { // 테스트 브라우저 별 환경 설정
        "default" : { // 모든 브라우저에 적용 될 공통 설정
          "launch_url" : "http://localhost",
          "selenium_port"  : 4444,
          "selenium_host"  : "localhost",
          "silent": true, // 셀레니움의 로그를 숨길지 여부
          "screenshots": { // 테스트가 실패 했을 때 촬영 될 스크린샷 설정
            "enabled" : true,
            "on_failure" : true,
            "on_error" : false,
            "path" : "tests/screenshots"
          },
          "desiredCapabilities": { // 셀레니움 웹드라이버로 전달할 브라우저 이름과 기능 지정
            "browserName": "firefox",
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        }
      }
    }

    tests 디렉터리 하위에 demo.js를 생성하고 간단한 테스트 코드를 한다.

    module.exports = {
        '사용자는 검색어를 입력 후 검색어가 포함된 자동 완성 리스트를 볼 수 있다.' : function (browser) {
            browser
                .url('http://www.google.com')
                .waitForElementVisible('body', 1000)
                .setValue('input[type=text]', 'nightwatch')
                .pause(1000)
                .assert.containsText('##sbtc', 'nightwatch')
                .end();
        }
    };

    이어서 아래 명령어로 간단한 E2E 테스트를 실행할 수 있다.

    $ ./node_modules/nightwatch/bin/nightwatch

    하지만 현재 파이어폭스 버전 47에 문제가 있어 테스트가 실행되지 않을것이다. 파이어폭스에서 테스트 하고 싶다면 예전 버전(Install an older version of Firefox)으로 다운그레이드 하거나 GeckoDriver를 사용해야한다(Setting up the Marionette executable). 여기에서는 GeckoDriver를 이용하는 방법을 소개(OSX 기준)하겠다.

    먼저 mozilla/geckodriver에서 GeckoDriver를 다운로드한다.

    $ cd ~/Downloads
    $ wget https://github.com/mozilla/geckodriver/releases/download/v0.8.0/geckodriver-0.8.0-OSX.gz

    다운로드한 파일을 압축 해제하고 적당한 위치로 옮긴 후 실행가능한 파일로 변경한다.

    $ gunzip geckodriver-0.8.0-OSX.gz
    $ mkdir executable && mv geckodriver-0.8.0-OSX executable/wires
    $ chmod 755 executable/wires

    이제 .bash_profile(또는 .zshrc)에서 PATH를 지정한다.

    $ vim ~/.zshrc
    
    	GECKO_DRIVER=$HOME/Downloads/executable
    	export PATH=$HOME/bin:/usr/local/bin:/usr/local/sbin:$GECKO_DRIVER:$PATH
    
    ## rc파일을 다시 불러온다.
    $ source ~/.zshrc

    마지막으로 nightwatch.json파일에서 desiredCapabilities 속성을 다음과 같이 변경한다.

    "desiredCapabilities": {
      "browserName": "firefox",
      "marionette": true, // 추가
      "javascriptEnabled": true,
      "acceptSslCerts": true
    }

    이제 다시 실행해보면 파이어폭스 브라우저에서 정상적으로 테스트가 진행될 것이다.

    데모 테스트 실행 결과
    <그림 3. 데모 테스트 실행 결과>

    여러 브라우저에서 동시에 테스트하기

    현재 작성한 설정 파일로 나이트왓치를 실행하면 파이어폭스에서만 테스트가 진행된다. 이번엔 크롬 브라우저에서도 테스트가 진행되도록 설정을 변경하겠다. 크롬 브라우저는 셀레니움과 통신할 웹드라이버를 별도로 설치해야하는데 웹드라이버 매니저를 사용하면 쉽게 설치할 수 있다. 아래 명령어로 웹드라이버 매니저를 설치한다.

    $ npm install --save-dev webdriver-manager
     
    # 크롬 웹드라이버와 앞 절에서 다운로드 받았던 셀레니움 서버가 함께 설치된다.
    $ ./node_modules/.bin/webdriver-manager update
     
    # 또는 아래 명령어 처럼 인자를 전달해 별도로 설치할 수도 있다.
    # ./node_modules/.bin/webdriver-manager update --chrome

    이제 nightwatch.json에 셀레니움 서버 경로와 크롬 웹드라이버 서버 경로를 수정한다.

    "selenium": {
      "start_process": true,
      "server_path": "./selenium-server-standalone-2.53.0.jar",
      "log_path": "tests/logs",
      "host": "127.0.0.1",
      "port": 4444,
      "cli_args": {
        "webdriver.chrome.driver" : "node_modules/webdriver-manager/selenium/chromedriver_2.21", // 추가
        "webdriver.ie.driver": ""
      }
    },

    다음으로 default 속성에 작성했던 파이어폭스 브라우저 설정을 test_settings 속성 하위로 옮기고 크롬 브라우저 설정도 함께 추가 작성한다.

    {
      // ... 생략 ...
      "test_settings": {
        "default": {
          "launch_url": "http://localhost",
          "selenium_port": 4444,
          "selenium_host": "localhost",
          "silent": true,
          "screenshots": {
            "enabled" : true,
            "on_failure" : true,
            "on_error" : false,
            "path" : "tests/screenshots"
          }
        },
        "firefox": {
          "desiredCapabilities": {
            "browserName": "firefox",
            "marionette": true,
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        },
        "chrome": {
          "desiredCapabilities": {
            "browserName": "chrome",
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        }
      }
    }

    이제 아래 명령어로 실행하면 두 브라우저에서 동시에 테스트가 실행된다.

    $ ./node_modules/nightwatch/bin/nightwatch --env firefox,chrome

    사파리 브라우저에서 테스트하고자 한다면 사파리 웹드라이버를 확장 기능으로 설치해야한다. 자세한 내용은 나이트왓치 위치의 Running tests in Safari 문서를 참고한다.

    사파리의 웹드라이버 확장프로그램
    <그림 4. 사파리의 웹드라이버 확장프로그램>

    모카 사용하기

    이번엔 테스트 코드를 모카 기반으로 작성할 수 있는 환경을 만들어보겠다. 나이트왓치는 어썰트로 챠이(chai)를 내장하고 있지만 모카는 별도로 설정해 사용해야한다. 모카를 설정하는 자세한 내용은 개발자 가이드 Using Mocha 절을 참고한다. 모카를 굳이 사용하려는 이유는 JUnit XML로 리포팅 하는 기본 러너와는 달리 다양하고 보기 쉬운 리포팅을 지원하기 때문이다.

    먼저 nightwatch.json 파일에 다음과 같이 test_runner 속성을 추가한다. 옵션에 관한 자세한 설명은 모카 위키의 Set options 절을 참고한다.

    {
      "test_runner" : {
        "type" : "mocha",
        "options" : {
          "ui": "bdd",
          "reporter": "spec"
        }
      },
      // ... 생략 ...
    }

    테스트 코드를 모카 기반으로 재작성한다.

    describe('구글 메인 페이지', function() {
     
        before(function(client, done) {
            done();
        });
     
        after(function(client, done) {
            done();
        });
     
        describe('##사용자는 검색할 수 있다.', function() {
            it('사용자는 검색어를 입력 후 자동 완성된 리스트를 볼 수 있다.', function(client, done) {
                client
                    .url('http://www.google.com')
                    .waitForElementVisible('body', 1000)
                    .setValue('input[type=text]', 'nightwatch')
                    .pause(1000)
                    .assert.containsText('##sbtc', 'nightwatch')
                    .end(done);
            });
        });
    });

    다시 실행해 보면 모카 기반으로 테스트 코드가 동작하는 것을 볼 수 있다.

    모카 테스트 실행 결과
    <그림 5. 모카 테스트 실행 결과>

    브라우저 스택

    크로스 브라우징 테스트를 할 수 있도록 해주는 웹 서비스인 브라우저 스택은 다양한 플랫폼과 웹 브라우저를 지원한다. 또한, 셀레니움 서버도 제공하고 있는데 이를 이용하면 나이트왓치와 연동해 테스트를 자동화할 수 있다.

    먼저 browserstack.json 파일을 작성한다.

    {
      // ... 생략 ...
      "selenium": {
        "start_process": false
      },
      "test_settings": {
        "default" : {
          "launch_url" : "http://hub.browserstack.com",
          "selenium_host" : "hub.browserstack.com",
          "selenium_port" : 80,
          "silent" : true,
          "screenshots" : {
            "enabled" : true,
            "on_failure" : true,
            "on_error" : false,
            "path" : "tests/screenshots"
          },
          "desiredCapabilities": {
            "platform": "xp",
            "browserName": "firefox",
            "javascriptEnabled": true,
            "acceptSslCerts": true,
            "browserstack.user" : "user_id", // 브라우저 스택 아이디
            "browserstack.key" : "user_key" // 브라우저 스택 키
          }
        }
      }
    }

    platform 속성엔 XP를 browserName 속성엔 파이어폭스를 지정했고 로컬 환경에서 셀레니움 서버를 실행시킬 필요가 없기 때문에 start_process은 false로 지정했다. 이제 브라우저 스택은 윈도우즈 XP 환경의 파이어폭스 브라우저에서 테스트를 진행할 것이다. 브라우저 스택에서 지원하는 플랫폼과 브라우저는 공식 홈페이지의 Capabilities 페이지를 참고하면 알 수 있다.

    아래 명령어를 참고해 실행해본다.

    $ ./node_modules/nightwatch/bin/nightwatch --config browserstack.json

    다양한 플랫폼과 브라우저에서 E2E 테스트를 할 수 있다는 점은 큰 장점이지만 통신이나 테스트를 구동하는 속도가 아주 느리다. 따라서 테스트 배치 혹은 정기 배포 전에만 사용하기 적합해 보인다.

    웹스톰 디버깅

    웹스톰에서 노드 디버깅 도구를 사용해 나이트왓치를 디버깅할 수 있다. 자세한 내용은 Debugging Nightwatch tests in WebStorm을 참고한다. 다만, 파이프라인 방식이다 보니 브레이크 포인트를 활용한 디버깅이 다소 무의미한 느낌은 있다.

    끝으로

    여기까지 다양한 사전 지식을 설명하고 나이트왓치에 관해서 이해해봤다. E2E 테스트 특성 상 프로젝트 저장소에 테스트를 작성하고 유지하기 보단 별도의 E2E 테스트 저장소를 만들어 테스트를 작성하고 유지하는게 더 효율적이지 않을까 생각한다. 또, 나이트왓치에는 페이지 오브젝트, 커스텀 커맨드 등 테스트를 작성할 때 유용한 개념을 제공한다. 이 두 개념을 적절히 잘 사용하면 생각보다 더 관리하기 쉬운 테스트 코드를 작성할 수 있다.

    위에서 진행한 설치 및 설정 과정은 UYEONG/hello-nightwatch에 올려놓았으니 참고하길 바란다.

    참고