E2E 테스트 코드를 작성하다 보면 엘리먼트 핸들링에 필요한 로케이터 생성을 Appium Inspector 도구에 의존하는 경우가 많은데요. 로케이터 작성을 도와주는 다른 도구도 존재하겠지만, 안드로이드와 아이폰 모두 커버 가능한 Appium Inspector가 여러모로 편리하고, BrowserStack 또는 Sauce Labs과 같은 클라우드 테스트 서비스와의 활용도가 높기에, 저는 실무에서 Appium Inspector를 활용하여 로케이터를 작성합니다.
이번 주말은 언어 학습을 잠시 뒤로하고 배운 것들을 활용해서 무언갈 만들어보고 싶었습니다. 첫 번째로 나(혹은 동료)의 페인 포인트를 줄여줄 수 있는지? 두 번째로 실무에서 활용 가능한 것인지? 세 번째로 업무 시 생산성을 높이는 데에 기여하는지?를 기준으로 무엇을 만들까 고민하던 중 문득 로케이터 생성이 매번 귀찮은 저의 모습이 떠올랐습니다.
Appium Inspector에서 로케이터를 확인하고 작업하는 방식을 보다 간소화시킬 수 있는 방법엔 무엇이 있을까 고민하던 중 Inspector에 내장된 Copy Attributes to Clipboard 기능을 살펴보게 되었는데요. 호기심에 값을 확인해보니 리스트-딕셔너리 자료가 나타났습니다.
순간 번뜩이는 아이디어가 떠오름과 동시에 Appium Inspector Locator Converter라는 패키지를 만들어 보겠노라 다짐했고, 토요일 15시부터 일요일 20:00까지의 데드라인을 잡고 첫 파이썬 패키지 배포 작업을 시작하게 되었습니다.
아이디어 출현 계기
실무에서 엘리먼트 핸들링에 필요한 로케이터를 설계하고자 위와 같이 Appium Inspector를 활용하는데요. 안드로이드 로케이터 전략은 Attribute-class와 Attribute-text 또는 Attribute-resource-id를 최우선 활용하여 xpath를 생성합니다.
아이폰 같은 경우는 Accessibility ID를 사용하거나 xpath를 활용하는데요. 로케이터 전략은 안드로이드와 아이폰 모두 개발팀 지원하에 Accessibility ID를 사용하는 것이 테스트 코드 성능(실행 속도)과 안정성(엘리먼트 변경에 의한 테스트 실패)이 높습니다.
하지만 3개월 전 Test Automation Workflow 구축 PoC를 담당하며 느낀 점은 우선 QA팀에서 할 수 있는 최선의 방법을 진행한 뒤 개발팀 서포트를 요청하는 것이었습니다. Accessibility ID 추가 작업이 꽤나 단순하지 않다는 것을 알게 되었기 때문이죠.
따라서 아래와 같은 로케이터만 피해서 설계하되(실제로 확인 필요한 element는 EditText인데, EditText에 도달하기 전에 다른 Element의 인덱스가 변경될 경우 실패 발생하고, 성능 면에서도 좋지 않음),
/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.widget.FrameLayout[1]/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup/android.webkit.WebView/android.webkit.WebView/android.view.View [2]/android.view.View/android.view.View/android.view.View [1]/android.widget.EditText
불가피할 경우 hierarchy가 전부 포함된 로케이터를 활용하거나, 여러 상황을 고려해서 로케이터 생성 없이 터치 액션으로 원하는 테스트 요구사항에 도달할 수 있는지를 검토합니다.
아무쪼록 여러 상황에 의해 Attribute-class와 Attribute-text 또는 Attribute-resource-id를 최우선 활용하여 xpath를 생성 필요한데, 해당 작업을 하다 보면 굉장히 귀찮고 번거로울 때가 있습니다.
우선 자동화 프레임워크 구조를 짧게나마 설명드리면 BasePage 클래스에 설계된 엘리먼트 핸들링 함수를 사용해서 각각의 페이지에 필요한 로케이터를 생성합니다.
이때 위와 같이 튜플 자료 구조의 첫 번째 인덱스와 두 번째 인덱스를 로케이터 변수에 담아서 BasePage에 설계된 find_element(self, locator)에 인자 값으로 넘겨줍니다. find_element는 click 또는 input과 같은 함수에서도 활용되는 구조입니다.
위와 같은 구조에서 로케이터 설계가 필요한 상황인데, 여러 방법이 있겠지만 이미 작성된 로케이터를 복사 붙여 넣기 해서 여러 개를 만들고, 필요한 값만 수동으로 바꿔주는 방법으로 그간 실무 진행을 했었는데요.
이 마저도 귀찮아서 누가 대신 좀 알아서 만들어줬으면 좋겠다는 생각이 들기 시작했고 아이디어가 떠오르기 시작했습니다.
아이디어 구현 가능성 검토
Inspector에 내장된 Copy Attributes to Clipboard를 통해 출력된 값을 살펴보면 위처럼 리스트-딕셔너리 자료구조로 나타납니다.
해당 값을 자세히 살펴보면 E2E 프레임워크 설계 구조상, 로케이터 설계에 필요했던 값은 key class의 value와 key text의 value 또는 key resource-id의 value인데요. 해당 값을 가져와서 최종적으로 ('xpath', '//android.widget.TextView [@text="이메일로 로그인"]') 형태로 포맷팅 시켜주면 되겠다는 생각이 들었습니다.
만들고자 하는 결과물의 로직이 크게 복잡하지 않을 거란 판단에 순서도 그리기는 생략하고 바로 코드 작업을 시작했습니다.
아이디어 구체화(1)
(1) Attribute 클래스 값 찾기 함수 설계
(2) Attribute 텍스트 및 리소스 ID 값 찾기 함수 설계
(3) 리스트를 문자열로 변환시키는 함수 설계
안드로이드는 95% 이상의 로직에 xpath가 사용되기 때문에 최종적으로 포맷팅 시킬 값에서 관련 값들은 하드 코딩해서 사용하고자 했습니다. 따라서 Attribute에서 클래스 값을 찾아주는 함수를 우선 설계하기 시작했습니다.
이후 텍스트와 리소스 ID 값 찾기 함수를 설계하고 작업이 끝난 줄 알았는데, 포맷팅 과정에서 텍스트 사이에 공백이 나타나는 바람에 무엇이 문제일까 살펴보기 시작했습니다.
타입 체크 후 다양한 테스트를 해보았는데 결과적으로 로케이터 생성 함수에서 리스트를 문자열로 변환시키는 과정을 추가하니 해결되었습니다.
리팩토링(1)
리스트를 문자열로 변환시키는 함수 제거
의외로 빨리 작업을 끝마쳤다는 생각에 리팩토링 후 패키지 배포하고자 했습니다. 리팩토링 대상은 리스트를 문자열로 변환시키는 함수였는데요. 예전에 파이썬 공부를 했을 때 별도의 내장 함수가 있었던 걸로 기억해서 찾아본 결과 join을 사용하면 해결되었습니다. 따라서 리스트를 문자열로 변환시키는 함수를 제거하고, join 로직을 추가했습니다.
패키지 배포 전 테스트 중 발견한 에러
임의의 테스트 값을 넣고 원하는 출력 값이 나타나서 배포를 앞두고 있던 중 실제 제가 구축한 환경에서 동작하는지 테스트가 필요했습니다. 사실 당연히 될 줄 알았는데 예상치 못한 에러가 발생했습니다. 지금 돌이켜보니 충분히 예상할 수 있었던 이슈였는데 보다 세심하게 작업하지 못했습니다.
BasePage 클래스에 정의된 find_element 함수가 튜플 자료를 인자로 받고 있는 구조인데 실제로 넘겨준 값은 str이었습니다. str을 튜플로 변경하는 로직이 필요하다는 것을 배포 전 테스트를 통해 알게 되었습니다.
리팩토링(2)
str을 튜플로 변환하는 로직 추가
포맷팅 된 locator 값을 최종적으로 튜플로 한번 더 변환하는 로직을 추가했고 그 결과 튜플 자료를 리턴 시킬 수 있게 되었습니다.
마지막 리팩토링
str과 tuple을 반환하는 로직 추가
생각해보니 패키지로 배포하는 용도가 혼자만 사용하는 게 아니라 함께 사용하고 발전시키기 위함인데, 로케이터를 튜플로 반환시키는 함수가 누군가에게는 쓸모없을 수도 있겠다는 생각이 들었습니다. 즉 위와 같은 클래스 간의 의존 관계가 성립되어야만 활용할 수 있는 함수였습니다. 따라서 패키지 배포의 목적을 다시 한번 생각해보게 되었습니다. 테스트 프레임워크 구축 방식에 따라서 str을 반환하는 로케이터도 필요하겠다는 생각이 들었습니다.
str을 반환하는 로케이터도 추가했고 결과적으로 str과 tuple을 반환하는 2개의 함수를 설계하게 되었습니다.
첫 PyPI 패키지 배포 및 최종 코드
드디어 토요일 15시부터 시작한 첫 PyPI 패키지 배포 작업을 끝마치게 되었습니다. 처음으로 실무에 유용하다 느끼는 무언가를 만들어서 배포하게 되었습니다. PyPI 배포를 처음 해보면서 많은 것들을 새롭게 배울 수 있었습니다. pip install이 어떻게 동작하는지에 대한 원리를 배웠고 직접 빌드하고 배포하는 과정을 통해서 이전에는 추상적으로만 다가왔던 개발 환경들이 이제는 보다 익숙함으로 다가왔습니다.
앞으로 해야 할 투두 리스트
현재 안드로이드 기준으로 만들어졌고 그중에서도 Xpath 로케이터 생성을 위해 만들어졌습니다. Attribute-class와 Attribute-text 또는 Attribute-resource-id를 최우선 활용하여 xpath 생성하지 않을 경우 사실상 쓸모없는 패키지가 될 수 있겠지만, 자동화 실무를 담당하는 동안에는 계속 사용하지 않을까 싶습니다. 앞으로 구현해야 할 투두 리스트를 살펴보면 아래와 같습니다.
1. 안드로이드 로케이터 생성 보완
2. 아이폰, 웹 로케이터 추가
3. Appium Inspector에서 추출 가능한 값 중, true 및 false로 나타나는 Attribute에 대한 함수 설계
4. Appium Inspecot 도구 의존성 없이 로케이터 자체 설계 및 변환
5. 예외처리 보완
6. 사용성 보완
현재안드로이드 데일리 테스트 케이스를 구현하는 중인데 직접 사용해보고 유용하다 판단되면 위와 같은 기능들을 앞으로 추가할 계획입니다.
마치며
짧은 시간이었지만 많은 것들을 배우게 되었습니다. 평소 사용했고 비교적 익숙했던 언어인 Javascript와 WebdriverIO 프레임워크에서 python behave 환경으로 실무 진행을 하게 되었는데, 작은 기능이지만 스스로에게 도움 되는 무언갈 직접 만들어보니 새로운 언어와 환경에 보다 빠른 적응이 가능하지 않을까 싶습니다.
언어와 프레임워크에 구애받지 않는 테스트 자동화 엔지니어로 성장하기 위해 이번 첫 패키지 배포를 시작으로 앞으로 E2E 자동화 관련된 도움 되는 패키지를 만들어서 작게나마 QA Engineer 분들께 도움이 되었으면 좋겠습니다.
긴 글 읽어주셔서 감사합니다.