Cypress

Cypress 핵심 개념 - Interacting with Elements

변기원 2022. 10. 28. 12:56

혼자 공부하면서 정리한 내용입니다. 자세한 내용은 공식문서를 봐주세요

Actionability

Cypress의 명령은 DOM과 상호작용 하기 위한 것이다.

예를들면 .click(), .dbclick(), .type(), .clear(), .check(), .trigger(), .selectFile() 같은것들..

 

기본적으로 이 챕터에서 말하고싶은것은 어떤 이벤트가 발생하기 전에

그 이벤트가 발생할 수 있는 상황인지 Cypress가 보이지 않는곳에서 모든것을 체크한다는 개념이다.

당연히 4초동안.

 

무슨말이냐하면 

뭔가 사용자가 이벤트를 발생시키기 전에 적어도 그 버튼이 진짜 눈에 보여야 사용자가 클릭을 하든 드래그를 하든 

뭘 할수 있을테니, 진짜 눈에 보여? 다른 요소에 가려져있지는 않아? 이런것들을 이벤트 발생 전에 체크한다.

Cypress 명령으로 .click()을 실행한다고 마냥 생각없이 클릭이벤트를 발생시켜주지는 않는다는 뜻이다.

위와같은 것들을 체크한다.

Visibility

Cypress는 요소가 진짜 눈에 보이는지 확인하기 위해 많은것을 체크한다. 

 Cypress는 아래와 같은 특징이 있을때 요소가 숨겨져있다고 간주한다.

1. width나 height 가 0 일때

2. CSS 속성 : visibility: hidden 인 경우

3. CSS 속성 : display: none 인 경우 

4. CSS 속성 : position: fixed 이면서 스크린 밖으로 나가거나 다른것에 덮여졌을 경우

5. 조상 요소의 width나 height 가 0 일때

등등 ..

아무튼 중요한건 진짜 명령이 적용된 요소가 사용자의 눈에 보이는지 확인한다는 뜻이다.

CSS 속성중에 Opacity에 대해서는 따로 생각해봐야하는데,

opacity:0은 투명도를 주는 속성으로써

opacity:0속성을 주더라도 눈에는 보이지 않지만 개발자가 그 요소의 위치를 알고있다면 

클릭이벤트 같은 것들을 발생시키는데 문제가 없다. 실제로 없어지거나 다른 요소로 덮여서 사라진게 아니라

투명도가 완전히 투명해져서 눈에만 안보이는 상태이기 때문에..

그래서 Cypress에서도 이 점을 고려하여 위와같은 interaction을 제공한다.

opacity:0 인 요소에 assetion을 적용하면 숨겨진 요소로 확인이 되지만.

actionable한 상태, 즉 이벤트를 줄 수 있는 상태로 간주된다. 는 뜻이다.

 

Detached

대부분의 웹앱은 랜더링되면서 DOM요소를 제거하고 새로운 DOM요소를 넣기도 한다.

Cypress는 요소가 DOM에서 제거되지는 않았는지 체크를 한다.

여전히 document에 남아서 테스트가 가능한 상태인지 알아서 체크해준다.

 

Readonly

readonly 속성이 있으면 당연히 .type()명령이 적용되지 않겠지? .type()명령 전에 이 속성도 체크한다.

 

Animations

Cypress는 요소가 애니메이션 중인지 (움직이고있는지) 체크하고 멈출때까지 기다린다.

근데 이 애니메이션이 이리저리 날아다니는 애니메이션도 있겠지만

작고 미세한 떨림도 있겠지. 유저가 클릭해야하는 버튼임을 알려주려고

일부러 작고 미세한 떨림을 주었는데, Cypress가 이벤트를 발생시키지 않고 애니메이션이니까 끝날때까지 기다리고있다면

뭔가 유저의 움직임과는 맞지 않다.

그래서 Cypress팀은 실험을 통해 유저가 상호작용하기에 너무 빠르다고 '느끼는' 속도를 찾아냈다.

Cypress는 명령실행 전에 애니메이션 속도를 측정해서 이 애니메이션은 유저가 상호작용할 수 있는지, 없는지를 판단한다.

유저가 상호작용하기에 너무빠르다면 실패하는 테스트가 될것이고

상호작용 가능한 속도라면 명령을 실행시키게 될것이다.

이 threshold는 수많은 실험을 통해 도출된 값이므로 이 값을 변경하지 않는게 좋겠지만 

threshold 옵션값은 여기서 확인할 수 있고 변경도 가능하다.

 

Covering

이벤트를 발생시키려는 요소가 부모요소에 의해 덮여있지 않은지 확인해야한다.

부모 요소에 덮여버리면 실제 유저가 이벤트를 발생시킬 수 없기 때문이다.

이 covering을 체크할때 요소의 중간좌표를 체크한다.

만약 자식요소에 의해 덮여있는 경우는 괜찮다.  이런 경우에는 자식요소에 해당 이벤트를 발생시킨다.

<button>
  <i class="fa fa-check">
  <span>Submit</span>
</button>

위의 경우처럼 종종 <i> 나 <span>에 의해 <button>의 중간이 덮인 경우를 보게된다.(사실 대부분 아닐까)

그러므로 이런 경우에는 자식요소에 이벤트를 발생시켜주고 개발자가 command log를 통해 확인할 수 있게 출력해준다.

 

Scrolling

요소와 상호작용하기 전에, 항상 눈이 보이도록 스크롤한다. 심지어 스크롤 없이 눈에 보이더라도 Cypress는

모든 명령에 같은 행동을 유지하기 위해 scrolling 알고리즘을 실행한다.

이 알고리즘은 요소가 덮여있지 않을때까지 스크롤되어야 한다.

공식문서에 있는 내용이 많은데, 이 스크롤 알고리즘이 동작하는 정확한 모양? 제대로 동작하는 이유 에 대해 설명하고 있다.

알아야하는 내용은 scrolling알고리즘이 항상 동작하기 때문에 DOM을 쿼리할때 당장 눈에 보이지 않는 경우라도

cypress가 저절로 스크롤을 해서 찾게 해준다는 내용이다.

자세한 내용은 이 부분 확인

 

Coordinates

모든 검증이 끝나고 요소가 actionable한 상태라면, 드디어 Cypress가 적절한 이벤트를 발생시킨다.

보통 이런 이벤트들은 요소의 중간에서 발생된다.(클릭이벤트라면 버튼의 정가운데를 클릭한다는 뜻)

그러나 원한다면 이벤트가 발생되는 위치를 변경할 수 있다.

cy.get('button').click({ position: 'topLeft' })

이런식으로 클릭위치를 변경할 수 있으며 Command Log에서 확인할 수 있게 출력된다.

Debugging

요약하자면

Command Log에 마우스를 올리게 되면 해당 요소로 스크롤이 되면서

브라우저에서 그 명령 당시의  snapshot을 가리키게 되는데 이것은 개발자로 하여금 해당명령이 찾은 요소가 뭔지

확인할 수 있게 하기 위한 시각적인 기능일 뿐이지

여기서 보이는 snapshot이 실제 그 시점에 Cypress의 행동을 그대로 반영하지는 않는다는 뜻이다.

예를들어 actionable하지 않다는 테스트 에러메세지가 떴는데 마우스를 올려보면 정상적인 것처럼 보일수도 있다.

하지만 이것은 어떤 요소를 찾은건지 보여줄뿐이지 실제 이벤트가 발생할때의 모습은 아니라는 것이다.

유일하게 정확한 타이밍에 디버깅할수 있는 방법은 .debug()뿐이다.

// break on a debugger before the action command
cy.get('button').debug().click()

Forcing

요약하자면

지금까지 설명한 이런 check과정들이 유저가 활동하는데 방해가 되는 상황을 찾아내는데 매우 유용하다.

하지만 모~든 행동을 유저가 하는것처럼 하나하나 입력해줄 필요가 있을까? 어떤것은 너무 확실하면서도 

너무 중첩된 과정이라 마우스를 직접 움직여 이벤트를 발생시키며 중첩된 과정을 반복하는게 비효율적일 수도 있다.

이 과정을 반복하는 것은 의미가 없기때문에 그런 경우에 forcing을 사용할 수 있다.

// force the click and all subsequent events
// to fire even if this element isn't considered 'actionable'
cy.get('button').click({ force: true })

이렇게 사용하면

default action이 실행되고 이벤트를 강제로 발생시킨다.

하지만 아래와 같은것들은 보장할 수 없다.

1. 스크롤하여 요소를 보이는지 확인

2. disabled한 상태가 아닌지 확인

3. DOM으로부터 분리되지 않았는지 확인

4. readonly인지 확인

5. 애니메이션중인지 확인

6. 가려져있지 않은지 확인

7. 자식요소에서 이벤트 발생시키기

즉, 모든 체크를 건너뛰고 해당요소에서 이벤트를 발생시킨다.