하위 페이지
  • 페이스북 테스트 계정 및 Selenium을 활용한 로그아웃 구현
메타 데이터의 끝으로 건너뛰기
메타 데이터의 시작으로 이동

지금 서비스하고 있는 slipp.net은 가능한 모든 부분을 자동화하려고 노력하고 있다. slipp.net  개발에 많은 시간을 투자할 수 있는 상황도 아니기 때문에 자동화하지 않으면 기능을 개선하거나 리팩토링을 진행했을 때 발빠르게 대응하기 힘들다는 생각 때문이다.

이 같은 생각으로 기능이 추가될 때마다 가능하면 Selenium을 활용해 Acceptance Test를 만들면서 기능 개발을 하고 있다. 그런데 테스트하기 힘든 부분이 계정 관련된 부분이다. slipp.net은 계정을 자체적으로 가지고 있는 것이 아니라 페이스북, 트위터, 구글 서비스의 OAuth로 연동해서 사용하고 있다. 따라서 여러 명의 계정으로 연동 테스트하기 힘들었다. 그런데 얼마 전 페이스북은 개발자들을 위해 테스트 계정을 제공한다는 것을 알게 되었다. 이 얼마나 개발자를 배려하는 마음인가? 감동의 눈물이... 역시 페이스북은 달라도 뭐가 다르다. 먼저 페이스북의 개발용 앱에서 5명의 테스트 계정을 만들었다.

테스트 계정을 만드는 과정은 다음과 같다.

  • https://developers.facebook.com/apps 에서 자신의 앱으로 접근한다.
  • 역할 우측의 역할 편집을 선택하면 다음 그림과 같이 테스트 계정을 추가할 수 있다.

위와 같이 테스트 계정을 추가한 후에 Selenium을 통해 서로 다른 계정으로 로그인해 다양한 테스트를 할 수 있으리라 생각했다. 그런데 왠 걸... 역시 한방에 성공하는 일은 없다는 것을 다시 한번 느낀다. Selenium을 통해 현재 브라우저의 Cookie를 모두 삭제하더라도 페이스북 Cookie가 삭제되지 않아 직전에 로그인했던 계정으로 계속해서 자동 로그인 되는 상황이 발생했다. 페이스북 Cookie를 삭제해야 되는 이슈가 발생했다. 이 상황이 닥치니까 갑자기 하기 싫어졌다. 뭐 굳이 이렇게까지 해야 되나라는 생각... 일단 이 단계까지만 진행하고 포기하자. 내가 항상 취하는 태도이다.

몇 일 동안 이 이슈와 관련해서는 잊고 지낸다. 그렇게 몇 일이 지났다. 오기가 발동한다. 분명 무슨 방법이 있을거야를 생각하다 페이스북의 로그아웃 URL을 호출하면 로그아웃 되지 않을까라는 생각을 하고 찾아보니 http://www.facebook.com/logout.php 이다. 이 URL을 GET, POST 방식으로 호출해도 예상과는 달리 로그아웃이 되지 않는다. 페이스북 놈들 좀 똑똑한가보다. 분명히 뭔가 다른 부분을 확인하고 있는 듯하다. 이 방법을 포기하고 다른 방법을 찾던 중 Facebook에서 제공하는 JavaScript API 중에 로그아웃 API가 있다는 것을 알게 되었다. 오 다시 도전해 보고 싶은 마음이 생긴다.

위 2개의 URL 참고해서 페이스북 로그아웃을 시도했더니 잘 된다. 페이스북 로그아웃은 slipp.net에서 기본으로 제공하는 기능이 아니기 때문에 다음과 같이 별도의 페이지를 만들었다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%><%@ include file="/WEB-INF/jsp/include/tags.jspf"%>
<html>
<head>
<title>로그인 :: SLiPP</title>
<link href="${url:resource('/stylesheets/main.css')}" rel="stylesheet">
</head>
<body>
  <div class="jumbotron">
    <div class="container">
      <input id="fbLogoutBtn" class="btn btn-primary btn-large" type="button" value="페이스북 계정으로 로그아웃" />			
    </div>
  </div>
  <script src="//connect.facebook.net/en_US/all.js"></script>
  <script>
    window.fbAsyncInit = function() {
      // init the FB JS SDK
      FB.init({
        appId      : 'YOUR_APP_ID', // App ID from the App Dashboard
        status     : true, // check the login status upon init?
        cookie     : true, // set sessions cookies to allow your server to access the session?
        xfbml      : true  // parse XFBML tags on this page?
      });
    };
    
    $(document).ready(function() {
  	  $("#fbLogoutBtn").click(function() {
  		  FB.logout(function(response) {});  
  	  });
    });
  </script>	
</body>
</html>

위와 같이 구현하고 웹 페이지에서 직접 테스트했더니 잘 된다. 역시 해결 안되는 문제는 없다는 생각을 하면서 Selenium 테스트를 돌린다. 앗, 근데 안된다. 뭐 이런 짜증나는 경우가 있나? 웹 페이지에서 사람이 직접 테스트하면 잘 되는데 Selenium으로 돌리면 안되다니. 분명 페이지 전환이 너무 빨라서 이런 상황이 발생할거라 생각하고 로그아웃 버튼 클릭 전에 Thread.sleep(500)와 같이 구현해 0.5초 멈춘 후에 로그아웃 버튼을 클릭하도록 변경했다. 역시 예상대로 잘 된다. 넘 기분 좋다. 몇 개의 Acceptance Test를 추가하고 있는데 잘 동작하던 테스트가 실패한다. 원인을 찾아보니 0.5초가 너무 작아 모든 경우에 로그아웃이 정상적으로 되지 않는다. 에라 모르겠다. 시간은 Thread.sleep(1000)와 같이 1초로 늘려 버렸다. 테스트 잘 된다. 근데 뭔가 찜찜하다. 이 테스트는 100%의 성공을 보장하지 못하는 테스트라는 찜찜함. 하지만 그래도 어디야라는 만족감을 느끼며 하루를 마무리했다.

다음 날 아침. 그래도 뭔가 찜찜하다. 이 같은 원인이 왜 발생했는지 찾아보니 FB.init() fuction이 Async란다. Async니까 뭔가 Callback function이 있을 듯하다. 그런데 특별히 그런 Callback function은 없단다. 그래서 꼼수로 FB.getLoginStatus()를 Callback function처럼 사용할 수 있단다. 이 사실을 알고 다음과 같이 코드를 개선했다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%><%@ include file="/WEB-INF/jsp/include/tags.jspf"%>
<html>
<head>
<title>로그인 :: SLiPP</title>
<link href="${url:resource('/stylesheets/main.css')}" rel="stylesheet">
</head>
<body>
  <div class="jumbotron">
    <div class="container">
      <input id="fbLogoutBtn" class="btn btn-primary btn-large" style="display:none" type="button" value="페이스북 계정으로 로그아웃" />
    </div>
  </div>
  <div id="fb-root"></div>
  <script src="//connect.facebook.net/en_US/all.js"></script>
  <script>
    $(window).load(function() {
      // init the FB JS SDK
      FB.init({
        appId      : 'YOUR_APP_ID', // App ID from the App Dashboard
        status     : true, // check the login status upon init?
        cookie     : true, // set sessions cookies to allow your server to access the session?
        xfbml      : false  // parse XFBML tags on this page?
      });
      
      FB.getLoginStatus(function(response){
        $("#fbLogoutBtn").show();
      });
      
      $("#fbLogoutBtn").click(function() {
        FB.logout(function(response) {});  
    });
    });
  </script> 
</body>
</html>

최초 페이지가 로딩되었을 경우에는 로그아웃 버튼을 보이지 않도록 설정했다. 그리고 FB.init() function Async로 초기화 작업을 완료한 후에 Callback으로 FB.getLoginStatus() fuction을 호출하면 이 시점에 로그아웃 버튼을 보이도록 구현했다.

위와 같이 웹 페이지를 구현한 후 Selenium 소스는 다음과 같이 구현함으로써 문제를 해결했다.

package net.slipp.qna;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class FBLogoutPage {
    private WebDriver driver;
    public FBLogoutPage(WebDriver driver) {
        this.driver = driver;
        driver.get("http://localhost:8080/fblogout");
    }
    public FBLogoutPage goToLogoutPage() {
        driver.get("http://localhost:8080/fblogout");
        return new FBLogoutPage(driver);
    }
    public IndexPage logout() {
        new WebDriverWait(driver, 1000).until(ExpectedConditions.visibilityOfElementLocated(By.id("fbLogoutBtn")));
        driver.findElement(By.id("fbLogoutBtn")).click();
        List<WebElement> logoutLinks = driver.findElements(By.linkText("로그아웃"));
        if (!logoutLinks.isEmpty()) {
            logoutLinks.get(0).click();
        }
        return new IndexPage(driver);
    }
}

위와 같이 구현했더니 정상적으로 동작하는 것을 확인할 수 있었다. 위 단계를 마치고 나니 페이스북 테스트 계정을 활용해 대부분의 기능을 테스트할 수 있는 단계가 되었다. 앞으로도 기능을 추가하고 변경할 때마다 Acceptance Test 추가하는 작업을 해야겠다. 방법을 찾기까지는 다소 힘들지만 해결 방법을 찾게 되면 이후 작업은 한결 수월하고 그로 인해 더 많은 시간을 벌 수 있겠다.