기존 커스텀 키워드 함수
GridCellTextByColumnValue(dataTestValue, targetColumn, targetText, expectColumn, expectText)
: data-test가 dataTestValue인 그리드의 targetColumn열에 targetText가 있는 행에서, expectColumn열의 내용이 expectText인지 확인하는 verify 용도의 커스텀 키워드 함수이다.
예시를 들어 다시 설명해보자면
아래는 출자 정보를 담은 그리드 중 일부분이다.
GridCellTextByColumnValue("fd0003-grid-invsDstrb", "약정 좌수", "2,600", "출자 납입액", "2,600,000,000")을 이용해
해당 그리드의 row값이 제대로 들어가있는지 verify하는 것이다.
기능을 실행하는 유즈 케이스에서 실행한 결과에 따라
의도한 값이 나왔는지 감지해내기 위한 용도이다.
success가 뜨면 검증된 것이고,
fail이 뜨면 의도했던 값과 다르기에 로직의 문제 확인이 필요하다.
단순히 기능이 동작하느냐를 확인하는 것도 TA의 목적이지만
그 기능을 수행 후 맞게 실행이 되었는지 검증도 필수다.
이 함수는 이전 ta 키워드 함수 제작자 분이 만들어두신 키워드 함수였는데,
테스트 케이스에 적용할 때 몇 가지 이슈가 있었어서
키워드 수정 요청을 받고 실제 작업을 했던 내용이다.
<기존 키워드 함수>
@Keyword
synchronized def GridCellTextByColumnValue(String dataTestValue, String targetColumn, String targetText, String expectColumn, String expectText) {
String targetColIndex = Grid.GetAriaColIndex(dataTestValue, targetColumn)
String expectColIndex = Grid.GetAriaColIndex(dataTestValue, expectColumn)
String xpath = "//*[@data-test='${dataTestValue}']//*[@role='gridcell' and @aria-colindex='${targetColIndex}' and (text() = '${targetText}')]/ancestor::div[@role='row']//*[@role='gridcell' and @aria-colindex='${expectColIndex}']"
TestObject gridCellTestObject = new TestObject()
gridCellTestObject.addProperty("xpath", ConditionType.EQUALS, xpath)
try {
WebElement gridCellElement = WebUiCommonHelper.findWebElement(gridCellTestObject, 30)
if (gridCellElement != null) {
String cellText = gridCellElement.getText()
if (cellText.contains(expectText)) {
KeywordUtil.markPassed("Expected value '${expectText}' is exactly present in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}'.")
} else {
KeywordUtil.markFailed("Expected value '${expectText}' is not exactly present in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}'. Found: '${cellText}'.")
}
} else {
KeywordUtil.markFailed("Grid cell in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}' not found.")
}
} catch (Exception e) {
KeywordUtil.markFailed("Error verifying cell value in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}': " + e.getMessage())
}
}
이는 Verify.groovy에 정의된 함수인데
Grid.groovy에 컬럼 index를 추출해내는 함수를 정의해서
각각 타겟 컬럼과 기대 컬럼의 인덱스를 추출한다. (인덱스 추출 함수는 아래에서 언급하겠다.)
그 인덱스를 통해 xpath로 잡고자 하는 오브젝트의 값을 찾고 기대 값과 비교한다.
근데 기존의 함수에는 문제가 있다!
기존의 인덱스 추출 함수와 검증 함수의 로직은 텍스트를 포함만 하면 되도록 해서
만약 기대 컬럼값 텍스트가 "잔액"인데 "이월 잔액"이라는 값으로 있어도 문제가 없다고 판단하게 된다.
따라서 그리드의 첫 번째 컬럼부터 순차적으로 뒤로 가면서 타겟 컬럼 값을 찾는 것인데,
"잔액"이라는 텍스트보다 "이월 잔액"이라는 텍스트가 먼저 나와서 그걸 타겟 컬럼으로 잡아버리면
유즈케이스의 기대값과 다른 값을 비교하게 되기에 테스트 실패로 판정된다.
또한 기대값 자체가 만약 2,000,000이라고 치면 12,000,000도 맞다고 치는 것이다.
이것이 이 함수의 첫 번째 문제였다.
커스텀 키워드 함수 개선 - 텍스트 매치
따라서 텍스트 값이 정확히 매치되어야지만 맞다고 판단하도록 수정을 했다.
Verify.GridCellTextByColumnValue 함수에서의 수정사항은
cellText.contains(expectText) ▶️ cellText == expectText
Grid.GetAriaColIndex 함수에서의 수정사항은
(contains(text(), '${searchText}') or contains(., '${searchText}'))
▶️ normalize-space() = normalize-space('${searchText}')
▶️ /ancestor::div[@role='columnheader']
normalize-space() 함수는 텍스트의 앞뒤 공백을 제거하고, 연속된 공백을 하나의 공백으로 처리하는 함수이다.
공백을 처리하여 정확히 일치하는 텍스트를 찾는다.
@role attribute가 'columnheader'인 오브젝트를 잡던 함수이기 때문에
텍스트가 매치되는 것을 확인한 후 다시 부모 attribute인 @role='columnheader'의 위치로 xpath를 설정 해줘야한다.
<기존 컬럼 인덱스 추출 함수>
@Keyword
synchronized def GetAriaColIndex(String dataTestValue, String searchText) {
String xpath = "//*[@data-test='${dataTestValue}']//*[@role = 'columnheader' and (contains(text(), '${searchText}') or contains(., '${searchText}'))]"
TestObject testObject = new TestObject()
testObject.addProperty("xpath", ConditionType.EQUALS, xpath)
try {
WebElement element = WebUiCommonHelper.findWebElement(testObject, 30)
if (element != null) {
String ariaColIndex = element.getAttribute("aria-colindex")
KeywordUtil.logInfo("aria-colindex value: ${ariaColIndex}")
return ariaColIndex
} else {
KeywordUtil.markFailed("Element with text '${searchText}' not found.")
return null
}
} catch (Exception e) {
KeywordUtil.markFailed("Error getting aria-colindex value: " + e.getMessage())
return null
}
}
<xpath 수정>
String xpath = "//*[@data-test='${dataTestValue}']//*[@role='columnheader']//div[@class='text' and normalize-space() = normalize-space('${searchText}')]/ancestor::div[@role='columnheader']"
이렇게 첫 번째 문제를 해결했다.
커스텀 키워드 함수 개선 - 다중 컬럼
두 번째 문제는 위에 처럼 컬럼이 한 줄로 있는 것은 다 커버가 가능한데,
컬럼이 다중값이면 해당 커스텀 키워드 함수로 대응이 되지 않는다.
예를 들면 전기의 2열 컬럼 아래있는 값을 검증하고 싶은데
기존의 함수로 "2열"을 타겟 컬럼으로 잡으면 의도대로 실행이 되지 않는다.
왜냐면 2열이라는 텍스트는 당기 아래에도 있기 때문이다.
그리고 다중 컬럼이기에 구조도 달라서 기존 방식으로 대응이 안된다.
따라서 새로운 커스텀 키워드 함수가 필요했다.
GridCellTextByMultiColumnValue(dataTestValue, targetColumn, targetText, expectParentColumn, expectColumn, expectText)
data-test가 dataTestValue인 그리드의 targetColumn열에 targetText가 있는 행에서, expectParentColumn열의 하위 열인 expectColumn열의 내용이 expectText인지 확인하는 verify 용도의 커스텀 키워드 함수이다.
expectParentColumn을 설정해서 "전기" 텍스트를 잡고
그 아래에 expectColumn으로 "2열"이라는 텍스트를 잡는 방식으로 설정했다.
<새로 추가한 커스텀 키워드 함수>
@Keyword
synchronized def GridCellTextByMultiColumnValue(String dataTestValue, String targetColumn, String targetText, String expectParentColumn, String expectColumn, String expectText) {
String targetColIndex = Grid.GetAriaColIndex(dataTestValue, targetColumn)
String expectParnentColIndex = Grid.GetAriaColIndex(dataTestValue, expectParentColumn)
String expectColIndex = Grid.GetAriaMultiColIndex(dataTestValue, expectColumn, expectParnentColIndex)
String xpath = "//*[@data-test='${dataTestValue}']//*[@role='gridcell' and @aria-colindex='${targetColIndex}' and (text() = '${targetText}')]/ancestor::div[@role='row']//*[@role='gridcell' and @aria-colindex='${expectColIndex}']"
TestObject gridCellTestObject = new TestObject()
gridCellTestObject.addProperty("xpath", ConditionType.EQUALS, xpath)
try {
WebElement gridCellElement = WebUiCommonHelper.findWebElement(gridCellTestObject, 30)
if (gridCellElement != null) {
String cellText = gridCellElement.getText()
if (cellText.contains(expectText)) {
KeywordUtil.markPassed("Expected value '${expectText}' is exactly present in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}'.")
} else {
KeywordUtil.markFailed("Expected value '${expectText}' is not exactly present in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}'. Found: '${cellText}'.")
}
} else {
KeywordUtil.markFailed("Grid cell in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}' not found.")
}
} catch (Exception e) {
KeywordUtil.markFailed("Error verifying cell value in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}': " + e.getMessage())
}
}
다중 컬럼 값을 잡기 위해 GetAriaMultiColIndex 함수도 추가를 했는데
현재 element의 ari-colindex가 상위 컬럼에 대해 유효한지를 확인하는 방식으로 인덱스를 구했다.
<새로 추가한 인덱스 추출 함수>
@Keyword
synchronized def GetAriaMultiColIndex(String dataTestValue, String searchText, String parentColIndex) {
String xpath = "//*[@data-test='${dataTestValue}']//*[@role = 'columnheader' and (contains(text(), '${searchText}') or contains(., '${searchText}'))]"
TestObject testObject = new TestObject()
testObject.addProperty("xpath", ConditionType.EQUALS, xpath)
try {
// xpath와 일치하는 모든 요소 찾기
List<WebElement> elements = WebUiCommonHelper.findWebElements(testObject, 30)
if (elements.size() > 0) {
int parentIndex = Integer.parseInt(parentColIndex);
int matchingIndex = -1;
for (int i = 0; i < elements.size(); i++) {
WebElement element = elements[i];
String ariaColIndex = element.getAttribute("aria-colindex");
int currentIndex = Integer.parseInt(ariaColIndex);
// 현재 element의 ari-colindex가 상위 컬럼에 대해 유효한지 확인 (상위 컬럼에 대한 하위 컬럼이 맞는지 확인)
if (currentIndex >= parentIndex) {
// searchText가 일치하는 경우 인덱스 반환
if (element.getText().contains(searchText) || element.getAttribute("innerText").contains(searchText)) {
KeywordUtil.logInfo("aria-colindex value: ${ariaColIndex}")
return ariaColIndex
}
// 못찾았으면 첫 번째 일치 element의 인덱스를 폴백
if (matchingIndex == -1) {
matchingIndex = i;
}
}
}
// 정확한 일치 항목을 찾을 수 없으면 첫 번째 일치 요소의 인덱스를 반환
if (matchingIndex != -1) {
String ariaColIndex = elements[matchingIndex].getAttribute("aria-colindex");
KeywordUtil.logInfo("aria-colindex value: ${ariaColIndex}")
return ariaColIndex
} else {
KeywordUtil.markFailed("Element with text '${searchText}' not found with valid aria-colindex.")
return null
}
} else {
KeywordUtil.markFailed("No elements found with xpath '${xpath}'.")
return null
}
} catch (Exception e) {
KeywordUtil.markFailed("Error getting aria-colindex value: " + e.getMessage())
return null
}
}
이제 두 번째 문제 상황에서도 대응이 되는 커스텀 키워드 함수가 완성되었다.
커스텀 키워드 함수 개선 - 다중 컬럼 + 공백 컬럼 값
그런데... 이후에 세 번째 문제가 생겼다...
Voc 반영으로 저 1열, 2열과 같은 컬럼값이 삭제되고 공백으로 대체 되었다.
따라서 텍스트 값이 아닌 위치 인덱스 값으로 테스트를 해야하는 상황이 되어서
새로운 함수를 또 작성하였다.
GridCellTextByMultiColumnNum(dataTestValue, targetColumn, targetText, expectParentColumn, expectColumn, expectText)
data-test가 dataTestValue인 그리드의 targetColumn열에 targetText가 있는 행에서, expectParentColumn열의 하위 열인 expectColumn 인덱스 열의 내용이 expectText인지 확인하는 verify 용도의 커스텀 키워드 함수이다.
expectParentColumn을 설정해서 "전기" 텍스트를 잡고
그 아래에 expectColumn으로 1이라는 index로 잡는 방식이다.
<새로 추가한 커스텀 키워드 함수>
@Keyword
synchronized def GridCellTextByMultiColumnNum(String dataTestValue, String targetColumn, String targetText, String expectParentColumn, Integer expectColumn, String expectText) {
String targetColIndex = Grid.GetAriaColIndex(dataTestValue, targetColumn)
String expectParnentColIndex = Grid.GetAriaColIndex(dataTestValue, expectParentColumn)
String xpath = "//*[@data-test='${dataTestValue}']//*[@role='gridcell' and @aria-colindex='${targetColIndex}' and (text() = '${targetText}')]/ancestor::div[@role='row']//*[@role='gridcell' and @aria-colindex='${expectParnentColIndex.toInteger() + expectColumn}']"
TestObject gridCellTestObject = new TestObject()
gridCellTestObject.addProperty("xpath", ConditionType.EQUALS, xpath)
try {
WebElement gridCellElement = WebUiCommonHelper.findWebElement(gridCellTestObject, 30)
if (gridCellElement != null) {
String cellText = gridCellElement.getText()
if (cellText.contains(expectText)) {
KeywordUtil.markPassed("Expected value '${expectText}' is exactly present in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}'.")
} else {
KeywordUtil.markFailed("Expected value '${expectText}' is not exactly present in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}'. Found: '${cellText}'.")
}
} else {
KeywordUtil.markFailed("Grid cell in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}' not found.")
}
} catch (Exception e) {
KeywordUtil.markFailed("Error verifying cell value in column '${expectColumn}' for the row with '${targetColumn}' = '${targetText}': " + e.getMessage())
}
}
두 번째 문제 상황에선 index를 직접 구해야했는데
(+ 애초에 진작에 왜 index값으로 입력값을 받지 않았냐면,
기본적인 우리 웹에서의 그리드는 컬럼의 이동이 가능해서 사용자가 직접 옮기는 것도 가능하고,
기획 쪽이 변경 되면 디폴트 값으로 지정된 인덱스가 바뀔 수 있다.
그럼 인덱스로 해두었다가 수정때마다 바꿔야하는데 그런 자잘한 수정까지
qa 조직 알려서 수정사항을 반영하기엔 모두가 번거로워진다.
그래도 텍스트 값 자체가 바뀌는 경우는 거의 없기 때문에 직접 인덱스를 구하는 식으로 작성했던 것이다.)
세 번째 상황은 index로 밖에 입력을 받을 수 없는 상황이라서
그 부분을 바로 입력 받은 인덱스 값으로 채워넣기만 하면 됐다!
이렇게 세 번째 문제 상황도 해결했다!!
MultiColumn말고 TwoColumn 관련 함수도 새로 작성했었는데,
이는 하나의 타겟 값으로 특정이 안되는 케이스의 경우 두 개의 타겟 값으로 기대값을 검증하기 위해 만든 함수이다.
이건 다음 포스팅에서 다루려고 한다.
나는 카탈론 스튜디오를 통한 qa를 진행하면서 groovy언어를 처음 써봤음에도
java 기반이라서 그런지 금방 익숙해졌다.
하지만... 이정도는 web element를 순조롭게 사용하는 희망편인 것 같고
왜 이게 안되는거야 하는 상황도 많았다ㅜㅋㅋㅋㅋ
그런 것들도 시간 여유가 되면 하나씩 다뤄보려고 한다!
'TA' 카테고리의 다른 글
[QA] 테스트 자동화 (14) | 2024.11.05 |
---|