웹 자동화 작업을 진행할 때, 특정 요소를 정확히 찾는 것은 종종 어려운 과제입니다. 안정적으로 동작하던 스크립트가 시간이 지나면서 점점 불안정해지는 경우가 발생합니다. 이러한 문제의 주된 원인은 요소 위치 지정 방식이 너무 고정적이며, XPath에 가변적인 항목이 포함되어 있기 때문입니다. 페이지 구조가 변경되면 원래의 XPath는 더 이상 유효하지 않게 되어 스크립트의 안정성이 저하됩니다.
요소 위치 지정의 핵심 원칙은 변하지 않는 요소를 중심으로 하는 것입니다. XPath는 가능한 한 짧게 작성해야 하며, 짧은 XPath일수록 가변 노드가 포함될 확률이 줄어듭니다. 이 글에서는 요소 위치 지정 시 주로 사용하는 세 가지 기법을 소개합니다.
다음 형제 노드 탐색 - following-sibling::
다음 이미지와 같은 상황을 고려해 보겠습니다:
이미지에 표시된 것처럼, 이는 모달 창입니다. 이러한 모달 창은 웹 페이지에서 흔히 볼 수 있으며, 하나의 페이지에서 여러 종류가 나타나는 경우도 많습니다. 이 모달 창의 확인 버튼을 클릭하려면 단순히 //span[text()="확인"]으로 충분할 수 있습니다. 하지만 이 방법은 어떤 모달 창에도 적용되며, 모달 창 내용이 변경되어도 이를 즉시 감지하기 어렵습니다.
따라서 위치 지정 시 모달 창 내용을 포함해야 합니다. 개발자가 내용을 임의로 변경하면 스크립트가 즉시 오류를 발생시켜 문제를 알려줍니다.
그렇다면 이 버튼을 어떻게 위치 지정하며 경로에 모달 창 내용을 포함할 수 있을까요? 이때 다음 형제 노드를 가져오는 방법인 following-sibling::을 사용해야 합니다.
먼저 모달 창 내용을 위치 지정합니다: //div[text()="암호화된 내용이 비어 있을 수 없습니다!"]. 그런 다음 관찰해 보면, 클릭하려는 노드가 이 div 노드의 다음 형제 노드의 자손 노드 내에 있습니다. 따라서 다음 형제 노드로 먼저 위치 지정합니다: //div[text()="암호화된 내용이 비어 있을 수 없습니다!"]/following-sibling::div. 이렇게 하면 해당 노드의 모든 다음 형제 노드를 위치 지정할 수 있으며, 그 다음 클릭하려는 버튼을 위치 지정합니다. 최종 위치 경로는 //div[text()="암호화된 내용이 비어 있을 수 없습니다!"]/following-sibling::div//span[text()="확인"]이 됩니다.
이렇게 두 요소를 연결하면 어느 쪽이 변경되든 스크립트는 오류를 발생시킵니다.
이전 형제 노드 탐색: preceding-sibling::
다음 이미지와 같은 상황을 고려해 보겠습니다:
이번에 위치 지정해야 할 노드는 드롭다운 상자(이미지에서 노란색으로 표시된 노드)입니다. 이 드롭다운 상자는 클릭 가능한 input 노드입니다. 이미지에 표시된 대로, 일반적인 방법에서는 id가 있는 노드를 위쪽에서 찾게 되며, 이 경우 form 노드가 됩니다. 명백히 이 방식으로 위치 지정하면, 이 노드의 XPath는 //form[@id="saveform"]/div[3]/div[3]/span[2]/input[1]로 작성될 것입니다. 이 방식이 맞는지 틀리는지와 상관없이, 이 방식의 단점은 명확합니다. XPath가 매우 길며, 대괄호로 표시된 노드가 많이 포함되어 있습니다. 이미지에서 볼 수 있듯이, 이 input 노드는 div 아래에 있으며, 이 div 위에는 동일한 모양의 div가 많이 있습니다. 즉, 동일한 모양의 컨트롤이 여러 개 인접해 있으며, 향후 버전에서 이러한 컨트롤을 추가하거나 제거할 가능성이 높습니다. 일단 위쪽에서 변경이 발생하면 이 노드의 위치 지정은 실패합니다.
이 문제는 반드시 해결해야 합니다. 노드를 관찰해 보면, 이 노드와 가까운 input 노드에 name 속성이 있으며, 이 name 값은 명백히 고유한 것으로 보입니다. 따라서 이 input 노드를 중심으로 위치 지정해야 합니다.
다음 형제 노드 위치 지정 방식과 동일하게, 먼저 name이 있는 노드를 위치 지정합니다: //input[@name="sne.sysLnNumAdmin.category2"]. 그런 다음 이 노드의 이전 형제 노드를 위치 지정하며, 최종 노드의 XPath는 //input[@name="sne.sysLnNumAdmin.category2"]/preceding-sibling::input이 됩니다.
이렇게 위치 지정하면 XPath 길이가 크게 단축되며, 대괄호로 표시된 노드가 포함되어 있지 않습니다. 또한 이 두 노드는 매우 가깝게 위치해 있어, 하나가 삭제되면 반드시 함께 삭제됩니다.
부모 노드 탐색 및 혼합 적용: parent::
다음 이미지와 같은 상황을 고려해 보겠습니다:
위 이미지에서 볼 수 있듯이, 이번에 위치 지정해야 할 노드는 트리 구도의 확장 노드입니다. 즉, '山东省' 앞의 더하기 기호(이미지에서 노란색으로 표시된 노드)입니다.
노드를 보면, 이것은 많은 초보자들이 자주 겪는 함정입니다. 이 span 노드에는 id 노드가 있는 것처럼 보이므로 원칙적으로 id로 위치 지정할 수 있습니다. 하지만 자세히 관찰하면 이 노드 뒤의 숫자는 무작위로 생성된다는 것을 알 수 있습니다. 즉, 이번에는 이 id가 다음에는 다를 수 있습니다.
전통적인 방법을 사용하면, id가 있는 노드를 위쪽에서 찾게 됩니다. 즉, 신뢰할 수 있는 노드로 ul 노드가 있거나, 더 안전하게 하려면 id가 있는 div 노드도 있습니다. 하지만 하위 노드가 동적이어서 XPath가 매우 안정적이지 않은 경우가 발생합니다.
따라서 비교적 안정적인 노드를 찾아야 합니다. 이 노드 바로 아래에 두 개의 노드가 있으며, 이들은 독특한 식별자를 가지고 있는 것이 특징입니다. 하나는 title="山东省"를 포함하고 다른 하나는 '山东省'라는 텍스트를 가집니다. 이 두 노드 모두 위치 지정에 사용할 수 있으며, 이번에는 아래 span 노드를 선택합니다.
노드를 결정했으므로, 관찰해 보면 위치 지정해야 하는 노드는 이 span 노드의 부모 노드의 이전 형제 노드, 즉 큰아버지 노드입니다. 따라서 먼저 부모 노드로 위치 지정합니다: //span[text()="山东省"]/parent::a. 그런 다음 위에서 설명한 부모 노드 위치 지정 방식을 결합하면, 최종 XPath는 //span[text()="山东省"]/parent::a/preceding-sibling::span이 됩니다.
이렇게 XPath를 작성하는 장점은 자명합니다. 간결하고 가변 노드가 포함되어 있지 않으며, 스크립트 오류 발생 확률을 줄여줍니다.
요약
이번에 소개한 세 가지 XPath 보조 위치 지정 방식의 핵심 사상은 위치 지정하려는 노드 주변의 변하지 않는 노드를 찾아 이를 사용하여 필요한 노드를 위치 지정하는 것입니다. XPath의 길이를 줄이고 가변 노드의 출현을 방지합니다.
XPath에는 유사한 보조 위치 지정 방식이 더 있으며, /ancestor, /descendant 등의 조상, 손자 노드도 포함됩니다. 이들을 종합적으로 사용하면 XPath의 위치 지정이 더욱 유연하고 정확해집니다.