JAVA 웹개발 과정 국비 51일차 정리

51일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


오늘 수업의 주요 목표는 웹페이지 자동화 프로그램을 만들어보는 실습이였다.
그 과정에서 새로 배웠던 점들이나 어려웠던 점들을 기록한다.

1. UI 테스트

1) 새로 배운 것들

① alertIsPresent

alert이 존재할 때까지 대기

wait.until(ExpectedConditions.alertIsPresent());

② UnhandledAlertException

기존에 있는 alert을 무시한 상태로 다음 동작을 하려고 할 때 생기는 오류이다. 수업에서 들어준 예는 alert 동작이 있는 버튼2개를 한꺼번에 클릭하려고 할 때 나타났다.
해결 방법은 간단하게 alert 창을 닫아주면 된다.

③ alert 창 닫기

셀레니움을 alert창을 보게해서 accept로 alert창을 닫는 동작을 하게 한다.

driver.switchTo().alert().accept();

④ 옵션 설정

ChromeDriver에 옵션을 설정할 수 있다.

ChromeOptions options = new ChromeOptions();
options.addArguments("옵션1", "옵션2", "...");

addArguments로 옵션을 여러개 줄 수도 있다.
물론 한개를 주는 것도 가능하다.

  • –window-size=1920,1080
    • 셀레니움은 현재 창 바깥쪽 범위에 있는 요소를 볼 수 없다. 따라서 셀레니움 창 크기를 적당히 조절해줘야하는 경우가 생긴다.
  • headless
    • 기본적으로 셀레니움을 실행하면 어떻게 동작하는지 브라우저를 띄워서 보여주는데, 이 브라우저를 실행은 시키되 보고싶지 않을 때 사용하는 옵션이다.
option.addArguments("--window-size=1920,1080");
options.addArguments("headless");
option.addArguments("--window-size=1920,1080", "headless");
// 적용하기
WebDriver driver = new ChromeDriver(options);

⑤ 크롬 창의 주소값

크롬 창이 실행되면 각 크롬 창 마다 고유의 주소가 존재하는데, 팝업이 여러개 뜨는 상황에서 특정 창으로 셀레니움을 보게하고 싶을 때 사용하는 함수로 getWindowHandle이 있다.

getWindowHandles는 모든 창의 주소값들을 가지고 있게 된다. (배열 비슷한 set으로)
이때, 처음 오픈된 크롬 창의 주소값을 변수 parentWindow에 담고. for문을 돌려 parentWindow와 같지 않은 주소를 찾으면 그 주소가 바로 팝업창의 주소가 된다.

String parentWindow = driver.getWindowHandle();

for(String handle : driver.getWindowHandles()) {
	if(!handle.equals(parentWindow)){
		driver.switchTo().window(handle); // 팝업창으로 시선 바꾸기
		break;
	}
}

팝업창에서 볼일을 다 봤다면, 셀레니움을 본래 창으로 돌려줘야 close가 가능하다.

driver.switchTo().window(parentWindow);

⑥ wait.until 의 사용

wait.until의 파라미터는 너무 많고 아직 익숙하지 않지만, 되도록이면 써주는게 안정성에 도움이 된다. 살짝 속도가 느려지긴 하지만, 사용자 각각의 컴퓨터 사양에 따라 로딩 속도가 다르기 때문에 에러를 만나고 싶지 않다면 써주는 것이 좋다.

2) 인터파크 티켓팅 매크로 - 예제

① 들어가자마자 보이는 팝업 알림

인터파크 로그인까지는 별로 어렵지 않았다.
그러나 로그인 후 예매 시작 페이지에 들어가자마자 팝업창으로 알림이 떴는데, wait.until을 써주지 않으면 에러가 났다.
여기서는 presenceOfElementLocated를 써주었다. 요소가 제대로 자리를 잡을 때까지 기다려서 클릭을 하게 했다.

wait.until(ExpectedConditions.presentOfElementLocated(By.className("popupCloseBtn"))).click();

② 팝업창으로 시선 옮기기

셀레니움이 원래는 예매 페이지를 보고 있다가, 예매 시작하기 버튼을 누르면 예매 창이 팝업으로 뜬다.
이때, 셀레니움을 팝업창으로 시선을 옮기기 위해 오늘 배운 getWindowHandles와 for문을 이용했다.

String parentWindow = driver.getWindowHandle();

for(String handle : driver.getWindowHandles()){
	if(!handle.equals(parentWindow)){
		driver.switchTo().window(handle);
		break;
	}
}

③ iframe의 미로…

예매 팝업 창은 iframe이 정말 많았다..
일단 들어가자마자 팝업창에서 iframe으로 시선을 변경시켜줘야 했는데, iframe 로딩이 느려서 요소를 찾지 못하는 것을 iframe을 잘못 찾은 줄 알고 엄청 헤맸다.
마찬가지로 wait.until을 사용하여 해결해줬는데, 상황에 딱 맞는 frameToBeAvailableAndSwitchToIt메소드가 있어서 switchTo.frame을 써주지 않아도 시선을 변경시킬 수 있었다.

wait.until(ExpectedConditions.frameToBoAvailableAndSwitchToIt(By.id("ifrmSeat")));

④ 캡챠의 함정

시선을 옮기자 마자 매크로 방지를 위한 Capcha가 떴다. 아직 이걸 해결할만한 능력은 없으므로 수기로 작성하고 완료를 누를 때 까지 셀레니움을 대기시켰다.
이때, 캡챠는 첫번째 iframe과 두번째 iframe 사이에 있었는데, 정확한 위치를 몰라서 엄청 헤메다가 코드를 하나씩 뜯어보고나서야 제대로 작동시킬 수 있었다.
캡챠는 div 태그로 이루어져 있었는데, 이 div가 사라질 때까지 셀레니움을 대기시키려고 invisibilityOf 메소드를 써주었다.
이 메소드는 WebElement를 파라미터로 받아서 따로 변수를 설정해주었는데, 지금 다시 찾아보니까 invisibilityOfElementLocated라는 메소드도 있네..

WebElement capcha = driver.findElement(By.id("divRecaptcha"));
wait until(ExpectedConditions.invisibilityOf(capcha));

⑤ 두번째 iframe

캡챠는 첫번째 iframe에 있었으므로, 두번째 iframe에 시선을 옮겨주고 좌석 선택을 했다.
좌석은 같은 class 이름을 가지고 있어서, findElement를 써주고 By.className을 써주면 알아서 가장 첫번째 요소를 선택했다.
그리고 좌석 선택 완료 버튼은 또 첫번째 iframe에 있었기 때문에, parentFrame 메소드를 이용하여 현재 iframe(두번째)의 부모 iframe태그로 이동시켜줬다.

driver.switchTo().frame("ifrmSeatDetail");
WebElement seat = driver.findElement(By.className("stySeat"));
seat.click();

driver.switchTo().parentFrame();

⑥ 꿀팁으로 배운 요소 선택 방법

수업시간에 잠깐 스쳐지나가듯 배운 js 코드를 이용한 요소 선택 방법이다.
내가 실습하면서 쓸 것이란 생각을 안했는데 신기했다.
a 태그에 onclick="fnSelect();" 로 이벤트가 등록되어 있었는데, js코드를 그대로 executeScript메소드에 넣어주면 클릭까지 한꺼번에 가능하다.

script.executeScript("fnSelect()");

⑦ 전체 코드

캡챠도 수동이고, 좌석 선택까지밖에 없는 코드이지만 그래도 매크로처럼 생긴(?) 프로그램을 만들었다는 것에 뿌듯하다.
시간이 된다면 완성까지 시켜보고 싶다.

import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class ticketPlus {
	public static void main(String[] args) throws InterruptedException {
		// 셀레니움 창 크기 키워주기 옵션
		ChromeOptions options = new ChromeOptions();
		options.addArguments("--window-size=1920,1080");
		// 옵션 등록
		WebDriver driver = new ChromeDriver(options);
		// 넉넉히 30초 기다리게 했다
		WebDriverWait wait = new WebDriverWait(driver,Duration.ofSeconds(30));
		// js도 쓸 수 있게 해줌
		JavascriptExecutor script = (JavascriptExecutor)driver;
		// 로그인 창으로 이동
		driver.get("https://ticket.interpark.com/Gate/TPLogOut.asp?From=T&tid1=main_gnb&tid2=right_top&tid3=logout&tid4=logout&GPage=https%3A%2F%2Ftickets.interpark.com%2Fgoods%2F21009146");
		// iframe으로 이동
		WebElement loginFrame = driver.findElement(By.tagName("iframe"));
		driver.switchTo().frame(loginFrame);
		// id, pw (js이용) 입력
		script.executeScript("document.getElementById('userId').value=arguments[0]","ID입력란");
		script.executeScript("document.getElementById('userPwd').value=arguments[0]","PW입력란");
		// 로그인
		WebElement loginBtn = driver.findElement(By.id("btn_login"));
		loginBtn.click();
		// 예매 페이지 이동
		driver.get("https://tickets.interpark.com/goods/21009146");
		// 팝업창 기다려서 끄기
		wait.until(ExpectedConditions.presenceOfElementLocated(By.className("popupCloseBtn"))).click();
         // 귀찮아서 Thread.sleep 해줬나보다.. wait.until로 기다려주기
		Thread.sleep(2000);
         // 날짜 선택하기
		WebElement date = driver.findElement(By.cssSelector("ul[data-view] li:nth-child(21)"));
		date.click();
		// 예매 시작하기 버튼 클릭
		WebElement gogo = driver.findElement(By.cssSelector("div.sideBtnWrap>a.sideBtn"));
		gogo.click();
		// 기존 창 주소 변수에 담기
		String parentWindow = driver.getWindowHandle();
		// 팝업창 주소로 이동
		for(String handle : driver.getWindowHandles()) {
			if(!handle.equals(parentWindow)){
				driver.switchTo().window(handle);
				break;
			}
		}
         // iframe 나타날때까지 대기하고 나오면 이동하기
		wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("ifrmSeat")));
		// 캡챠 없어질때까지 대기
		WebElement capcha = driver.findElement(By.id("divRecaptcha"));
		wait.until(ExpectedConditions.invisibilityOf(capcha));
         // 두번째 iframe으로 이동
		driver.switchTo().frame("ifrmSeatDetail");
         // 좌석 선택하기
		WebElement seat = driver.findElement(By.className("stySeat"));
		seat.click();
		// 이전 frame으로 이동
		driver.switchTo().parentFrame();
		// 다음 단계로 넘어가기
		script.executeScript("fnSelect()");
		
		// 미완성~~
		
		Thread.sleep(5000);
		driver.close();
	}
}

댓글남기기