webUI 테스트 자동화 (E2E 테스트 end to end)
UI 테스트 자동화 관련된 키워드
phantom.js && casper.js, zombie.js, slimer.js ,node.js, webdriver.io, Cucumber.js, protractor ,nightwatch.js, htmlUnit,Guitar , selenium , webdriver,javascript,Cypress.io,uitest.js,uirecorder,testCafe(*typeScript를 사용) + assertiong lib (chai.js)
1. Headless Browser (헤들리스 브라우저)
정확히는 헤들리스 브라우저 라기보단 헤들리스 옵션으로 사용할수 있는 lib 들을 표현하였습니다.
헤들리스 옵션은 자동화 테스트 진행시에 UI 없이 브라우저가 실행되게 하는 옵션 입니다. PhantomJS의 CapserJS는
PhantomJS 를 조금 더 세밀하게 제어할수 있는 lib 입니다.
장점에 셀레니움 이슈들이 존재 하지 않는다고 되어 있는 부분들은 셀레니엄 IDE(Java)등에서 사용되는 여러 방법이 있는데 해당 에서 발생하는 side-effect 들에 대한 이슈없이 단순히 lib에서 발생하는 이슈만 있기 때문에 조금 더 최적의 상태? 를 사용할수 있다고는 하지만 실제로 다 써본건 아니니 참고만 하려고합니다.
2. 셀레니움 의존
셀레니엄 관련 lib들을 가져와서 사용하는 것으로 Python,Ruby,Java,C#,js (vanilla)
js말고도 다른 언어들이 셀레니엄 lib를 통해서 사용할수 있는 것으로 여러 맞는 언어들을 나에게 맞게 사용할수 있는 장점이 있기 때문에 원하시는 언어로 사용해보시면 될것 같습니다.
저는 JS를 현재 공부중에 있다보니 js기반인 selenium-webdriver npm 에서 받아서 사용 하겠습니다.
기존에 UI 자동화를 위해서 이것저것 사용은 해봤습니다. guitar라든지 관련 자동화 스크립트 등등
하지만 결국 자동화를 위해서 는 코드를 기반으로 짜주어야 하며 위에서와 같이 pyhton,ruby,java,C# 너무많이 있습니다.
사실 js를 사용하기 때문에 했다기보다는 Node.js위에서 npm 을 통한 자동화 테스트 코드를 만드는것이 가장 유연성이
높다고 판단되었기 때문입니다.
실제로 자동화에 접목시키기 위해서 아는 지식선에서는 js가 가장 좋아보였습니다.(정확히는 데이터 전송등 유연하다고 생각되었습니다.)
그래서 일단 간단하게 Naver Login 후에 메일 에 대한 내용을 가져오는 방법에 대해서 작성하려고 합니다.
우선 오늘은 기본 설정 후 네이버 메인에서 Login 버튼을 클릭 하려고 합니다.
3. Total Test Case
1. www.naver.com 으로 접속 (테스트하는 브라우저로)
2. id 란 id 입력
3. pw 란 pw 입력
4. login 버튼 클릭
5. 메일 메뉴 클릭
6. 가장 최상단 메일 클릭
7. contents 가져오기
8. 로그아웃
어떻게 보면 1~4까지가 Login 에 대한 자동화 Test Case 라고 볼수 있다.
4. Environment Set ( 환경 설정 )
우선 Java Script run-time 설치를 위한 https://nodejs.org/en/ 에 들어가서 Node.js를 설치 합니다.
LTS 와 Current 에 대한 설명은 생략 하도록 하겠습니다.
비트에 맞게 설치 후 자동화 테스트 프로젝트로 사용할 폴더를 하나 지정합니다.
저는 바탕화면에 자동화라는 폴더를 만들었습니다.
cmd 에서 해당 경로로 이동 후에 npm init 명령어를 통하여서 기본 설정을 행합니다.
npm init에 대해서는 따로 포스팅하겠습니다.
그리고 일단 크롬테스트를 위하여 설치 하여야 하는 2개의 npm 모듈을 설치합니다.
//셀레니엄 드라이버
npm i selenium-webdriver
// 크롬 드라이버
npm i chromedriver
설치 후 코드를 짜기전에 확인하여야 하는 docs가 있습니다.
http://seleniumhq.github.io/selenium/docs/api/javascript/index.html
셀레니엄 docs 공식 홈페이지인데요 해당 docs만 보고나서도 셀레니엄으로 어지간한 작업은 다 해결이 됩니다.
브라우저 실행 및 기본 동작
const _url = "https://www.naver.com/"; const webdriver = require('selenium-webdriver'); const {By,Util} = webdriver; require('chromedriver'); var driver = new webdriver.Builder() .forBrowser('chrome') .build(); (async () => { try { await driver.get(_url); } finally { await driver.sleep(2000); await driver.quit(); } })();
간단 설명이자면 우선적으로 _url 로 어떤 url 에접속할지 정하게 됩니다.
그리고 테스트를 위한 selenium-webdrvier를 가져오게 되구요.
해당 selenium 에서 사용되어지는 유틸 객체들(By,Util)을 가져옵니다. By의 경우는 DOM 조작에 대한 작업이 많기 때문에 자주 쓸일이 많을 겁니다. 그리고 크롬 드라이버를 사용할 것이기 때문에 chromedriver를 require로 가져옵니다.
테스트용이기 때문에 IIFE 즉시 실행함수로 바로 실행하게 하였습니다.
여기서 주의할 점이 await driver.get(_url); 부분인데요
Selenium-JS 에서 사용되어지는 모든 객체들은 webElement 이며 Promise 객체입니다. JS에서 Promise에 대한 지식이 없다면 사용하기가 애매할수 있기 때문에 비동기 호출과 Promise에 대해서는 꼭 숙지가 되어야 합니다.
또한 Promise가 아니라면 async await(ES8) 도 숙지가 되어야 합니다. Promise then(ES6) 보다는 async await가 조금더 가독성이 높기 때문에 저는 async await 방식을 사용하겠습니다. 자세한 설명빼고 말씀드리자면
async function 으로 선언후 Promise 객체들을 그냥 리턴받게 되면 Promise<pending> 으로 리턴이되어 리턴된 비동기 함수 에대한 보장을 받지 못합니다. 그렇기 때문에
await를 통해서 비동기 함수에 대한 리턴을 보장받게 되는데 해당 await를 사용하기 위해서는 함수를 async로 선언하여야 합니다.
그렇기 때문에 위와같이 선언이되었으며 async await를 사용시에 try block 을 만들지 않게 되면 UnHangle Promise ~ 에러가 나타나기 때문에 해당의형식을 취해주어야 합니다.
이제 네이버에서 입력하거나 클릭을 위해서 태그를 가져오는 방법에 대해서 생각 해보겠습니다.
단순 자동화 클릭만해서는 자동화라고 할수가 없지만 하나하나 차근차근히 진행 하도록 하겠습니다.
순서는 이와같습니다. 주황색 부분에 id를 입력 -> 파란색 박스에 pw를 입력 -> 1과 2과 되었을 경우 빨간색 버튼에 대해서 클릭을 하게 됩니다.
그러면 해당 input 과 button 을 어떤식으로 객체를 가져와서 조작을 해야 할지 고민이 되는 상황에서 네이버 페이지를 직접 들어가 보는게 제일 좋습니다.
사진이 잘 작아서 보이진 않지만 입력 하는 id박스에는
<div class="input-box">로 감싸져 있으며 내부값은
label 과 input으로 이루어져있습니다. 우리가 직접 입력하는 부분은 당연히 input 태그일 거고 아래와 같이
document.querySelector('input#id').value='login 입니다.'; 라고 개발자 도구에 쳤더니 id칸에 입력이 된점이 확인됩니다.
// ID 입력 document.querySelector("input#id").value="test"; // 패스워드 입력 document.querySelector("input#pw").value="test";
위의 DOM (돔) 조작을 위한 방식이 있지만 이 방식이 Selenium_JS에서는 되지않을것 같습니다. 그래서 해당 lib의 코드로 수정을 해보자면
let input_id = await driver.findElement(By.css('input#id')); let input_pw = await driver.findElement(By.css('input#pw'));
와같이 수정이 가능 하다. driver.findElement 현재 드라이버 (현재화면) 에서 By.css( selector ) 의 맞는 객체를 가져옵니다.
xPath 또는 findElement(By.id( id ))방식도 있고 docs를 확인하시면 제어 방법은 여러가지가 있습니다.
입력은 다음에 하도록 하고 login 버튼 까지 클릭을 하도록 하겠습니다.
여기서 로그인 버튼은 <span class="btn_login">
<input type="submit" > 해당 버튼입니다.
여기서 이제 객체를 가져오는 방법은 완전한 개인의 차이 라는 점을 보여드리고자 합니다.
버튼의 위치를 봅니다 ( 물론 가장 확실하게 가져온다는 가정하에 입니다.)
<span class="btn_login"> <input type="submit" title="로그인" value="로그인" data-clk="nmy.login"> </span>
저 가운데 버튼 클릭을 어떤식으로 할수 있을까 라고 말하고 결론부터 말씀드리면 아래의 4가지 방식이 우선 떠 올랐습니다. 해당 방식으로 잘 가져와짐도 확인이 되었으며 그냥 여러 방식이 있다는 점을 보여 드리고 싶었습니다.
<토막 상식 data-clk 는 HTML5에서 제공되어지는 data-* 으로 커스텀 프로퍼티를 사용하기 위함입니다.>
CSS 셀렉터를 정말 무궁무진하기떄문에 어떠한 방식으로도 객체를 가져와서 사용할수 있음을 알면 됩니다.
해당 코드를 node test.js로 실행시 네이버를 열고 -> 바로 login 버튼을 클릭 하기 때문에 id를 입력하라는 tooltip을 확인 할수 있다.
다음에는 id와 pw를 입력해서 로그인하는 방법 을 작성 하겠습니다.
// 네이버 로그인 해서 가장 최근 메일 제목 읽어서 가져오기 // TC 1 네이버 창 렌더링 // TC 2 네이버 로그인 // TC 3 최근 메일 TXT 가져오기 // TC 4 네이버 로그아웃 const _url = "https://www.naver.com/"; const webdriver = require('selenium-webdriver'); const {By,Util} = webdriver; require('chromedriver'); var driver = new webdriver.Builder() .forBrowser('chrome') .build(); // 로그인 // ID 입력 document.querySelector("input#id").value="test"; // 패스워드 입력 document.querySelector("input#pw").value="test"; // Login 버튼 클릭 // document.querySelector("input[type='submit']"); // document.querySelector("input[title='로그인']"); // document.querySelector("span.btn_login > input"); // document.querySelector("span.btn_login").children[0]; // 이외에도 셀렉터를 활용한 방법은 너무나도 많다. 단순한 예로 들었을뿐 var loginSelector = [ "input[type='submit']", "input[title='로그인']", "span.btn_login > input" ]; (async () => { try { await driver.get(_url); let loginBtn = await driver.findElement(By.css("input[type='submit']")); loginBtn.click(); } finally { await driver.sleep(2000); await driver.quit(); } })();