파란하늘의 지식창고
article thumbnail
Published 2022. 11. 26. 02:35
Selenium 사용해보기 Study/Java
반응형

Selenium 소개

https://www.selenium.dev/

Selenium은 web application 테스트 및 자동화를 위한 오픈소스 자동화 도구이다.
테스트 뿐만 아니라 아닌 웹 기반의 여러 작업들을 자동화 할 수 있어 여러 용도로 사용할 수 있다.

이런 도구를 단위 테스트(unit test)와 대비해 끝점에서 진행되는 테스트 도구라는 의미로 End-to-End test automation tool이라고 한다.

다양한 E2E 테스트 도구들 (End-to-End testing tool)

selenium외에도 수많은 E2E 테스트 도구들이 있다.

java에서 jquery처럼 사용할 수 있게 제공되는 selenide 도 있고
https://selenide.org/

Microsoft가 만들어서 공개한 Playwright
https://playwright.dev/

nodejs의 selenium 기반 nightwatch
https://nightwatchjs.org/

cypress
https://www.cypress.io/

위에 소개한 것들 외에도 다양한 E2E testing tool들이 있다.
https://theqalead.com/tools/best-end-to-end-testing-tools/

어떤 걸 사용할지는 사용자의 선택이다.

Selenium이 제공하는 도구

Selenium은 크게 3가지 도구를 제공한다.

  • Selenium WebDriver
    WebDriver는 브라우저 공급업체에서 제공하는 browser automation API를 사용하여 브라우저를 제어하고 테스트를 실행한다. Chrome, Edge, Firefox, IE, Safari를 지원한다. 또한 여러 개발 언어를 지원하여 C#, Ruby, Java (Kotlin), Python, Javascript에서 사용할 수 있다.
  • Selenium IDE
    IDE는 Selenium Test Case를 개발하는데 사용하는 도구이다. Chrome과 Firefox, Edge에서 확장 프로그램을 설치하여 사용할 수 있다. 테스트를 만들고 관리할 수 있으며 사용자의 작업을 기록한다.
  • Selenium Grid
    Grid를 사용하면 다양한 플랫폼의 다양한 시스템에서 테스트 케이스를 실행할 수 있다. 원격으로 여러 운영체제 환경에 대한 여러 브라우저 조합을 테스트하고자 하는 경우 사용한다.

Selenium IDE

Selenium IDE는 브라우저에 플러그인으로 제공되며 브라우저에서 취한 행동을 녹화(record)하고 재생할 수 있게 해준다.
또한 이렇게 녹화된 액션을 저장하거나 여러 언어의 test code로 export 할 수 있다.

테스트를 위한 코드를 작성하기 위해 학습해야 할 내용이 많아 부담되는 경우 Selenium IDE의 record 기능을 사용하면 훨씬 쉽게 작성할 테스트 코드를 작성할 수 있다.

Selenuum IDE 설치하기

https://www.selenium.dev/downloads/

Selenium IDE는 Chrome이나 Firefox, Edge에서 확장 프로그램으로 설치하여 사용한다.

Chrome을 주로 사용하기 때문에 Chrome 확장 프로그램을 설치해보았다.

Selenium IDE 사용하기

프로젝트를 만들고 프로젝트에 test case를 N개 등록하고 이렇게 등록된 test case를 test suite로 묶어서 그룹 테스트를 할 수 있다.

프로젝트는 .side 확장자로 저장되며 json 형태이다.

새로운 프로젝트를 만들고 test 만들어보기

설치 후 Selenium IDE 확장 프로그램을 실행하면 다음과 같이 Selenium IDE가 열린다.

Record a new test in new project로 새로 프로젝트를 만든다.

프로젝트 이름을 적당히 입력하고

테스트할 주소를 입력한다.

test case를 테스트 대상 url을 설정하고 START RECORDING을 선택한다.

해당 사이트가 새 브라우저 창으로 로드된다.

이제 이 브라우저에서 하는 행동들이 모두 Selenium IDE에 기록된다.

기록되는 것은 record 버튼이 활성화된 것을 통해 알 수 있다. (가장 오른쪽에 record 중이라는 빨간 표시)

적당히 브라우저에서 동작을 하고 난 후 record 버튼을 클릭해서 기록을 중지한다.

중지를 하면 이제까지 기록한 record를 저장할 테스트를 지정하라는 창이 뜬다.

이렇게 test 하나가 만들어졌다.

Selenium IDE에서 Run test 버튼으로 해당 테스트를 실행할 수 있게된다.

바로 옆의 Step over current command 메뉴로 실행을 단계별로 할 수도 있다.

test Suite 사용하기

이렇게 생성한 test는 test suite로 묶어서 관리할 수 있다.

Tests라고 되어 있는 부분을 선택하면 test, test suites, executing을 선택할 수 있는 메뉴가 뜬다.

길게 사용하는 부분을 소개했지만 사실 일반적으로 테스트 코드 작성을 하는 경우 쓰이는 개념이기 때문에 record를 한 후 몇 번 run을 사용해보면 금방 익히게 된다.

이렇게 생성된 프로젝트에 대해 save project를 하면 .side 확장자로 저장되며 json 형태이다.

이 .side 파일을 사용하면 다른 Selenium IDE에서도 동일한 테스트 프로젝트를 실행할 수 있다.

export 하기

테스트는 export를 통해 다음과 같은 언어 별 테스트 코드를 생성할 수 있다.

  • C# NUnit
  • C# xUnit
  • Java JUnit
  • JavaScript Mocha
  • Python pytest
  • Ruby RSpec

export메뉴는 다음과 같이 tests 메뉴의 테스트 또는 test suites의 suite의 오른쪽에 있는 점 세 개의 메뉴 항목을 선택하여 확인할 수 있다.

Tests의 메뉴 위치
Test suites의 메뉴 위치
메뉴를 선택하여 export 항목을 선택할 수 있다.

여러 언어에 대한 테스트 코드로 export 할 수 있게 된다.

Selenium WebDriver

Selenium IDE가 손쉽게 사용할 수 있고 selector 같은 것들을 잘 모르더라도 돋보기를 사용해 dom element의 target을 확인하기 편하지만 공통화를 통해 효율적으로 관리하기는 아무래도 어렵다.

따라서 Selenium IDE로 기본적인 동작을 테스트 해보았으면 export를 통해 생성된 테스트 코드를 참고하여 제대로 언어 별 테스트 코드를 작성하는 것이 좋을 것 같다.

Selenium WebDriver로 테스트 코드 작성하기

Java Maven Project 설정하기

Selenium IDE에서 export 한 Java JUnit 테스트 코드의 경우 아쉽게도 JUnit 4 기준으로 작성이 되어있지만 method내 동작은 그대로 사용할 수 있기 때문에 Junit 5 기반으로 작성해보았다.

JUnit 5 테스트 코드를 작성하기 위한 Maven 설정은 이곳을 참고하면 된다.

junit이나 assert 설정을 하였다면 아래와 같이 selenium-java를 추가한다.

<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.6.0</version>
</dependency>

Selenium IDE에서 export한 Java JUnit 예제를 살펴보면 대략 다음과 같은 형태이다.

public class DefaultSuiteTest {
    private WebDriver driver;
    private Map<String, Object> vars;
    JavascriptExecutor js;
    @Before
    public void setUp() {
        driver = new ChromeDriver();
        js = (JavascriptExecutor) driver;
        vars = new HashMap<String, Object>();
    }
    @After
    public void tearDown() {
        driver.quit();
    }
    @Test
    public void someTest() {
        driver.get("테스트 주소");
        // 생략...
    }

    @Test
    public void someTest2() {
         driver.get("테스트 주소");
        // 생략...
    }

    // 생략

}

webDriver에 대한 자세한 사용은 selenium documentation을 참고하면 된다.

https://www.selenium.dev/documentation/webdriver/

개인적으로 사용한 java 테스트 코드 설정들

junit 설정 부분

테스트 순서 지정

테스트를 단계별로 진행하면서 순서대로 진행이 되어야 했다.

  1. 로그인
  2. 페이지 접근하여 로그인한 경우의 동작 수행

이와 같이 순서대로 테스트를 진행해야 하기 때문에 다음 설정을 선언하였다.

대상 테스트 클래스에 아래와 같은 설정을 하여 순서대로 진행하는 것을 선언하였고

각 테스트 method에는 아래와 같이 순서를 지정해주었다.

@TestMethodOrder(OrderAnnotation.class)
class SomeTests {

    @Test
    @Order(1)
    void loginTest() {
        // ....
    }

    @Test
    @Order(2)
    void loginUserActionTest() {
        // ....
    }
}

TestInstance Lifecycle class 단위 지정 및 Cookie 값 전달 유지

순서대로 테스트를 수행하면서 로그인한 유저의 쿠키 정보를 유지하고 다음 테스트에서 사용하려고 하였다.

기본적으로 JUnit의 테스트는 method별로 test instance를 사용하기 때문에 @TestInstance annotation을 사용해 class 단위로 TestIntance lifecycle을 설정하지 않으면 아래의 예시에서 처럼 다음 테스트 method로 쿠키를 전달하려고 해도 유지가 되지 않는다.

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(OrderAnnotation.class)
class SomeTests {

    private Set<Cookie> cookies = new HashSet<>();

    @BeforeEach
    void beforeEach() {
        // 로그인에서 얻은 Cookie 사용
        cookies..forEach(driver.manage()::addCookie);
    }

    @Test
    @Order(1)
    void loginTest() {
        // .... 로그인 처리 수행

      // 로그인 후 얻은 Cookie를 사용하기 위해 저장
        cookies = driver.manage().getCookies();
    }

    @Test
    @Order(2)
    void loginUserActionTest() {
        // ....
    }
}

N개 class 단위 테스트에 대해 동일 webDriver 사용하도록 처리

위에서 class 단위 테스트 설정을 하고 나서 여러 개의 class를 실행하면 각각의 class에 대해 개별 webDriver를 사용한다.

이로 인해 매번 class 마다 webDriver를 로드하는 부하가 커서 이를 줄이기 위해 공유해서 테스트하도록 처리하였다.

Selenium WebDriver 설정 부분

browser loading 관련 wait 처리

아쉽게도 페이지 이동이나 링크를 클릭한 이후 response status code를 확인하는 것은 제공되지 않는다.

극단적으로 로그를 남기고 로그의 응답 결과를 찾아 확인할 수 있지만 추천하지 않는 방법이기 때문에 사용하지 않았다.

https://stackoverflow.com/questions/6509628/how-to-get-http-response-code-using-selenium-webdriver

따라서 페이지를 이동하거나 dom의 요소를 찾아 이벤트를 실행하기 전/후에 적절히 대기 상태 처리를 해주어야 한다.

https://www.selenium.dev/documentation/webdriver/waits/

예를 들어 로그인 버튼을 눌러 로그인 페이지로 진입한 후 특정 버튼을 클릭하는 다음의 경우가 있다면

driver.findElement(By.linkText("로그인")).click();
driver.findElement(By.cssSelector(".btn-primary > span")).click();

클릭하기 전에 해당 버튼이 로드되는 것을 대기해야 한다.

관련해서 selenium은 WebDriverWait 를 제공한다.

다음과 같이 처리한다.

driver.findElement(By.linkText("로그인")).click();
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.presenceOfElementLocated(".btn-primary > span"));
driver.findElement(By.cssSelector(".btn-primary > span")).click();

대부분의 이벤트 동작이나 페이지 이동 동작 전/후 대기 처리를 하지 않으면 테스트 코드를 실행할 때마다 dom의 element를 찾을 수 없다는 에러를 잦은 확률로 보게 된다.

제공되는 ExpectedConditions에서 원하는 경우를 찾아서 적절히 사용하면 된다.

presenceOfElementLocated보다 elementToBeClickable가 좀 더 대기하는 것 같고 아예 javascript의 document.readState를 체크하는게 더 대기를 하는 것 같다.

new WebDriverWait(driver, Duration.ofSeconds(30)).until(ExpectedConditions.jsReturnsValue("return document.readyState==\"complete\""));

그렇다고 해도 완전히 대기를 처리하는건 아니기 때문에 특정 액션 이후 의도한 url인지까지 체크하는게 명확한거 같다.

new WebDriverWait(driver, Duration.ofSeconds(6)).until(ExpectedConditions.urlContains("someUrl..."));

제공되는 ExpectedConditions에서 원하는 경우를 찾아서 적절히 사용하면 된다.

headless 설정

로컬에서 테스트 진행 시에는 브라우저를 띄우고 실시간을 확인하였지만 jenkins에 job으로 등록하여 반복 수행하는 경우(mvn -B -U test) 브라우저를 띄울 필요가 없다.

이 경우 아래처럼 headless 설정을 추가해준다.

ChromeOptions options = new ChromeOptions();
options.setHeadless(true);
driver = new ChromeDriver(options);

image load 비활성화

테스트시엔 이미지 로딩을 굳이 하지 않아도 되어 아래와 같이 설정한다.

var prefs = new HashMap<String, Object>();
prefs.put("profile.managed_default_content_settings.images", 2);
options.setExperimentalOption("prefs", prefs);

사용해보면서 느낀 점

Selenium IDE는 Selenium WebDriver만 사용했을 경우 일일이 작성해야할 테스트 단계들을 record 기능을 통해 손쉽게 구현할 수 있어 좋다.
또한 대상 target을 지정하기 위한 여러 명령어를 숙지하지 못한 초보자여도 target 을 지정하여 확인할 수 있고 동일 target에 대해 css, xpath 전환을 편하게 할 수 있어 편리했다.
(target 지정과 관련한 방법들은 https://www.selenium.dev/documentation/legacy/selenium_ide/#locating-elements 문서를 참고하면 된다.)

그러나 사용자의 동작은 기록하지만 브라우저에 동적으로 DOM element가 로드되길 기다리는 행동은 기록되지 않는다.
내가 클릭한 DOM element가 동적으로 생성된 경우 생성을 기다리는 대기 행동에 대해서도 자동으로 생성해서 바로 앞에 Insert new command 해주는 기능이 있었으면 좋겠다 싶었다.
해당 기능이 없어서 테스트 코드를 여러 번 돌려보면서 DOM element가 로드되지 않는 경우 발생한 에러 케이스를 감지하고 코드를 보완해가는 과정을 거쳐야 했다.

페이지를 호출한 후 응답 http status code를 기준으로 처리를 하고자 하는 경우에 지원이 아쉬웠다.
https://stackoverflow.com/questions/6509628/how-to-get-http-response-code-using-selenium-webdriver

기본적으로 응답 확인을 제공하지 않으며 로그 파일에 기록된 기준으로 확인할 수 있지만 이런 방법을 권장하지는 않는다.
wait를 활용하여 해당 페이지에 내가 원하는 DOM element가 있는지를 체크해서 대기하는 형태로 작성해야 하는 것 같다.

사이트 모니터링을 목적으로 사용해보았지만 많이 다양한 용도로 쓰일 수 있고 그 중엔 티켓팅 매크로나 웹 크롤링, 댓글 봇 같은 형태의 활용도 있다.

반응형
profile

파란하늘의 지식창고

@Bluesky_

내용이 유익했다면 광고 배너를 클릭 해주세요