<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>IsLiife2</title>
    <link>https://isliife2.tistory.com/</link>
    <description>Email : rkddustn2519@naver.com</description>
    <language>ko</language>
    <pubDate>Thu, 2 Jul 2026 12:53:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>YeonSu02</managingEditor>
    <image>
      <title>IsLiife2</title>
      <url>https://tistory1.daumcdn.net/tistory/6818558/attach/e502febe1bf44a3db2ee2647f78d8593</url>
      <link>https://isliife2.tistory.com</link>
    </image>
    <item>
      <title>  n8n을 활용한 Jira 티켓 생성 자동화</title>
      <link>https://isliife2.tistory.com/138</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  자동화를 하게 된 이유&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QA를 위해 다양한 준비를 한다. 기획/디자인 파악 -&amp;gt; 테스트 케이스 작성 -&amp;gt; 테스트 환경 세팅. 그리고 QA 기간이 돌아오면 본격적으로 테스트를 진행하게 된다. 스프린트 범위에 따라 적게는 200개, 많게는 1000개의 테스트 케이스가 나오는데, 1인 QA다 보니 이걸 혼자 다 돌린다. 빠르게 개발되어야 하는 스타트업 환경에서 FAIL은 생각보다 자주 나오고, 1000개의 TC 중 중복을 걷어내도 최소 100개의 버그 티켓이 나오는 경우가 있다. 이걸 하나하나 Jira에 손으로 만드는 시간은 결코 무시하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 따지고 보면 티켓에 적는 내용 대부분은 TC에 이미 있다. 발생 과정, 기대 결과, 테스트 대상까지 이미 다 적혀있는 내용을 다시 복사해서 붙여넣는 행위가 릴리즈마다 반복됐다. 나는 항상 TC를 돌리고 FAIL 처리가 되면 Jira 티켓을 만든 뒤, 해당 티켓의 제목과 하이퍼링크를 TC의 비고란에 달아두는 방식으로 일해왔다. 이렇게 하면 나중에 이 TC가 왜 FAIL이었는지, 수정은 됐는지, PASS로 바꿔도 되는지 확인할 때 훨씬 편하기 때문이다. 그러다 자연스럽게 이런 생각이 들었다. TC를 FAIL 처리하면, 그 행의 내용을 바탕으로 Jira 티켓을 자동 생성하고 생성된 링크를 비고란에 달아주면 되지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 흐름이 만들어지면 테스트할 때는 화면 녹화를 틀어두고, 성공하면 삭제, 실패하면 저장해두고 넘어가면 된다. 자동 생성된 티켓에 들어가서 첨부파일과 실제 결과만 추가하면 티켓이 완성된다. 그 흐름을 만들고 싶었다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  n8n workflow 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n8n workflow 전체 구조는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVwx7B/dJMcaf05UkV/3Ksh1ccENzgyc42UvTA680/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVwx7B/dJMcaf05UkV/3Ksh1ccENzgyc42UvTA680/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVwx7B/dJMcaf05UkV/3Ksh1ccENzgyc42UvTA680/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVwx7B%2FdJMcaf05UkV%2F3Ksh1ccENzgyc42UvTA680%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1444&quot; height=&quot;333&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Sheets Trigger가 시트 변경을 감지하면 JavaScript로 조건을 걸러내고, Jira REST API로 티켓을 만들고, 생성된 티켓 링크를 시트에 다시 써준다. 단순해 보이지만 이 구조에 도달하기까지 삽질이 꽤 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  삽질 1: Jira 중복 티켓이 계속 생겼다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 조건을 단순하게 잡았다. TC 상태 열이 FAIL이면 티켓을 만드는 것. 그런데 막상 실행해보니 동일한 버그에 대한 티켓이 계속 새로 생성됐다. 원인은 트리거 설정에 있었는데, Google Sheets Trigger를 row added or updated로 설정했다는 게 문제였다. 행에 변경사항이 생길 때마다 발동하는 구조라, FAIL 처리된 행에 이후에 다른 행 업데이트가 생기면 트리거가 다시 발동되고, 그때마다 FAIL 조건을 만족하니 티켓이 또 만들어지고 또 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방향은 &quot;이미 티켓이 만들어진 행인지&quot;를 시트 안에 직접 기록하는 것이었다. TC 시트에 Jira 열을 추가하고 초기값으로 - 문자를 넣어뒀다. 그리고 조건을 하나 더 얹었다. FAIL이면서 Jira 열이 -인 경우에만 티켓을 생성하도록. 티켓이 생성되면 해당 행의 Jira 열에 티켓 번호와 하이퍼링크를 write-back하기 때문에, 이후에 같은 행에서 트리거가 다시 발동돼도 Jira 열에 이미 링크가 있어 조건을 통과하지 못한다. 중복 생성 문제가 사라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  삽질 2: Filter 노드가 조건을 제대로 못 잡았다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2중 조건이 생겼으니 n8n의 Filter 노드로 처리하려 했는데, 막상 써보니 input에 분명히 FAIL 처리된 행이 존재하는데도 output이 전부 Discarded만 됐다. Kept되는 항목이 하나도 없었다. 조건식을 여러 방식으로 바꿔봤다. 필드명을 다시 확인하고, 따옴표를 ''에서 &quot;&quot;로 바꿔보고, 아예 따옴표를 없애보기도 하고, - 대신 빈 값으로 조건을 바꿔보기도 했는데 결과는 똑같이 전부 Discarded였다. 원인을 끝내 특정하지 못한 채 &lt;b&gt;Code in JavaScript 노드&lt;/b&gt;로 바꿨더니 바로 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780464316386&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const items = $input.all();
return items.filter(item =&amp;gt; 
  item.json['결과'] === 'FAIL' &amp;amp;&amp;amp; 
  item.json['Jira'] === '-'
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직 자체는 Filter 노드에서 걸려던 조건과 동일한데, 코드로 쓰니 바로 동작했다. 이유는 여전히 명확하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  삽질 3: Jira Parent Issue Key가 지정되지 않았다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건 필터 문제를 해결하고 나서, 이번엔 Jira 티켓 생성 단계에서 막혔다. n8n의 공식 Jira Software 노드를 처음에 썼는데, UI가 깔끔하고 필드 매핑도 직관적이어서 편하게 쓸 수 있을 것 같았다. 문제는 &lt;b&gt;Parent Issue Key&lt;/b&gt; 필드였는데, 에픽 하위에 티켓을 생성하려면 parent를 지정해야 하는데 노드 UI에 분명히 해당 필드가 있어서 값을 넣고 실행했더니, 티켓은 생성되는데 에픽 연결이 되지 않았다. 필드명도 바꿔보고, 값 형식도 바꿔봤는데 계속 안 됐고 원인은 끝내 알 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;HTTP Request 노드&lt;/b&gt;로 전환해서 Jira REST API를 직접 호출했더니 바로 됐다.&lt;/p&gt;
&lt;pre id=&quot;code_1780464588783&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;fields&quot;: {
    &quot;project&quot;: { &quot;key&quot;: &quot;PROJECT&quot; },
    &quot;issuetype&quot;: { &quot;name&quot;: &quot;버그&quot; },
    &quot;parent&quot;: { &quot;key&quot;: &quot;KEY-0915&quot; },
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  삽질 4: 티켓 제목을 어떻게 구조화할까&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jira 티켓을 자동으로 만들다 보니 제목 형식이 중요해졌는데, 나중에 티켓 목록을 봤을 때 어떤 영역의 버그인지 한눈에 파악이 돼야 했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 평소 쓰는 형식은 이렇다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[View / Scope] 대분류 &amp;gt; 중분류 &amp;gt; 소분류 &amp;gt; ~현상&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디서 발생한 버그인지 맥락이 제목만 봐도 바로 보이는 구조라, 이걸 자동화하려면 TC 시트의 View, 대/중/소분류 컬럼 값을 그대로 이어 붙이면 됐다. 간단해 보였는데 문제가 있었다. TC 시트의 대/중/소분류 컬럼이 전부 &lt;b&gt;병합 셀&lt;/b&gt;로 되어 있었던 것이다. 시각적으로 깔끔하게 정리하려다 보니 자연스럽게 그렇게 됐는데, Google Sheets API로 읽으면 병합된 셀의 첫 행에만 값이 있고 나머지는 빈 값으로 반환된다. 결과적으로 티켓 제목에 대/중/소분류가 빠지거나 일부만 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &lt;b&gt;Google Apps Script&lt;/b&gt;로 해결했다. 병합 셀을 전부 해제하고 각 행에 상위 값을 채워 넣는 스크립트를 작성해서 전처리 단계로 실행했더니, 모든 행의 대/중/소분류가 정상적으로 읽혔고 티켓 제목이 의도한 형식대로 완성됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/138</guid>
      <comments>https://isliife2.tistory.com/138#entry138comment</comments>
      <pubDate>Wed, 3 Jun 2026 14:47:39 +0900</pubDate>
    </item>
    <item>
      <title>Claude를 업무에 활용하기 위한 기초 개념 정리</title>
      <link>https://isliife2.tistory.com/137</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;CLAUDE.md, Skill, Command, Agent &amp;mdash; 이 네 가지만 알면 Claude가 달라진다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Claude는 왜 매번 까먹을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude를 처음 업무에 쓰려고 하면 이런 경험을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 대화에서 &quot;한국어로 답변해줘, 버그 리포트는 이 형식으로 써줘&quot;라고 했는데, 내일 새 세션을 열면 언제 그랬냐는 듯 아무것도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 버그가 아니다. Claude는 &lt;b&gt;세션이 끝나면 모든 걸 잊는 구조&lt;/b&gt;다. 매 대화가 완전히 빈 상태에서 시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 같은 설명을 매번 반복하게 된다. &quot;이 프로젝트는 이런 거야, 내 역할은 이래, 이 형식으로 써줘...&quot; 하루에도 몇 번씩.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 존재하는 개념이 네 가지 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  CLAUDE.md &amp;mdash; 신입사원 온보딩 문서&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄 정의&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Claude가 세션을 시작할 때 자동으로 읽는 마크다운 파일&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 팀원이 입사했을 때 매번 &quot;우리 팀은 이렇게 일해요&quot;를 설명하는 대신, 온보딩 문서를 한 번 써두면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 한 번 만들어두면, 이후 매 세션마다 Claude가 자동으로 읽고 맥락을 잡는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무엇을 담나?&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;## 기본 규칙&lt;br /&gt;- 모든 답변은 한국어로 &lt;br /&gt;- 코드 수정 전 반드시 확인 먼저 &lt;br /&gt;- 응답은 간결하게, 불필요한 설명 생략 &lt;br /&gt;&lt;br /&gt;## 작업 환경 &lt;br /&gt;- 테스트 환경: staging.example.com &lt;br /&gt;- 운영 환경: prod.example.com &lt;br /&gt;- Prod에서는 절대 데이터 수정 금지 &lt;br /&gt;&lt;br /&gt;## 결과물 형식 &lt;br /&gt;- 버그 리포트는 [환경] 증상 형식으로 제목 작성 &lt;br /&gt;- 코드 리뷰는 심각도 표시 필수&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 개요, 작업 규칙, 출력 형식, 금지 행동 등을 담는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위치에 따라 달라지는 적용 범위&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkFOy0/dJMcajhFEjM/CYAP8tx2MUNuhkW9UN2X10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkFOy0/dJMcajhFEjM/CYAP8tx2MUNuhkW9UN2X10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkFOy0/dJMcajhFEjM/CYAP8tx2MUNuhkW9UN2X10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkFOy0%2FdJMcajhFEjM%2FCYAP8tx2MUNuhkW9UN2X10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;224&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 위치에 동시에 두면 다 적용된다. 충돌이 생기면 더 구체적인 위치(하위 폴더)가 우선된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주의할 점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길수록 좋을 것 같지만, 반대다. &lt;b&gt;짧을수록 더 잘 따른다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude의 컨텍스트 윈도우는 유한하고, 내용이 많아질수록 덜 중요한 내용은 희석된다. 200줄 이하로, 핵심만 담는 게 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Skill &amp;mdash; 사내 업무 규정집&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄 정의&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;특정 작업의 방법론을 패키징한 재사용 가능한 폴더&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLAUDE.md가 &quot;전체적인 상황 설명서&quot;라면, Skill은 &quot;특정 작업의 업무 매뉴얼&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 &quot;버그 리포트는 이 양식으로&quot;, &quot;코드 리뷰는 이 기준으로&quot; 같은 규정집이 있는 것처럼, Claude한테도 작업별로 규정을 만들어둘 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Skill은 &lt;b&gt;단순 파일이 아니라 폴더 구조&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SKILL.md 하나로 시작할 수 있지만, 작업이 복잡해질수록 폴더 안에 참고 자료, 템플릿, 실행 스크립트까지 함께 넣을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdx3mr/dJMcahYuzTv/1nDsfizQG4EKu6lDGSYEf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdx3mr/dJMcahYuzTv/1nDsfizQG4EKu6lDGSYEf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdx3mr/dJMcahYuzTv/1nDsfizQG4EKu6lDGSYEf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdx3mr%2FdJMcahYuzTv%2F1nDsfizQG4EKu6lDGSYEf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;521&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude는 필요한 파일만 그때그때 읽어온다. 파일이 많아도 매번 전부 로드하는 게 아니라, SKILL.md에서 참조한 파일만 필요 시 불러오는 방식이다. 그래서 Skill 폴더가 크다고 컨텍스트를 낭비하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SKILL.md 안에는 YAML frontmatter와 지시사항이 들어간다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;--- &lt;br /&gt;name: bug-report&lt;br /&gt;description: 버그, 이슈, 오류 리포트 작성 요청 시 사용. 재현 절차와 심각도 포함. &lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;버그 리포트는 아래 형식으로 작성해줘. &lt;br /&gt;양식은 templates/report.md를 참고하고, &lt;br /&gt;엣지 케이스는 references/edge-cases.md를 확인해.&lt;br /&gt;&lt;br /&gt;**제목**: [환경] 증상 요약 &lt;br /&gt;**발생 환경**: Dev / Staging / Prod &lt;br /&gt;**재현 절차**: 번호를 매겨서 단계별로 &lt;br /&gt;**기대 결과**: 원래 어떻게 동작해야 하는지 &lt;br /&gt;**실제 결과**: 실제로 어떻게 동작했는지 &lt;br /&gt;**심각도**: Critical / Major / Minor&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 방식이 두 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;자동 실행: Claude가 description을 읽고 관련 요청이라 판단하면 알아서 로드한다. &quot;버그 리포트 써줘&quot;라고 하면 Claude가 스스로 이 Skill을 꺼내 쓴다.&lt;/li&gt;
&lt;li&gt;수동 실행: /bug-report처럼 슬래시 커맨드로 직접 호출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Skill의 진짜 가치는 작업 품질을 높이는 게 아니라, &lt;b&gt;결과물을 안정화&lt;/b&gt;하는 것이다. 같은 &quot;버그 리포트 써줘&quot;를 10번 요청해도, Skill이 없으면 매번 형식이 다를 수 있다. Skill이 있으면 항상 동일한 기준으로 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Command &amp;mdash; 반복 작업 단축키&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄 정의&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;/명령어 하나로 복잡한 작업 흐름을 실행하는 단일 파일&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Skill이 &quot;업무 규정&quot;이라면, Command는 &quot;자주 쓰는 업무 프로세스를 버튼 하나로 만든 것&quot;이다. 예를 들어 E2E 테스트를 생성하는 작업은 여러 단계가 있다. 매번 그 단계를 하나씩 지시하는 대신, 커맨드 하나로 전체 흐름을 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;구조&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nAUi4/dJMcaib3Yfs/hExllQL6qRfyP6G5y7REEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nAUi4/dJMcaib3Yfs/hExllQL6qRfyP6G5y7REEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nAUi4/dJMcaib3Yfs/hExllQL6qRfyP6G5y7REEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnAUi4%2FdJMcaib3Yfs%2FhExllQL6qRfyP6G5y7REEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;86&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 내용 자체가 Claude에게 주는 지시다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;너는 Playwright E2E 테스트 전문가야.&lt;br /&gt;&lt;br /&gt;## 실행 순서 &lt;br /&gt;1. 입력받은 URL을 Playwright MCP로 직접 탐색해 &lt;br /&gt;2. 화면에서 테스트해야 할 요소들을 파악해 &lt;br /&gt;3. 파악한 내용을 바탕으로 테스트 코드 작성 &lt;br /&gt;4. 테스트 실행 후 실패하면 원인 분석 &lt;br /&gt;5. 모든 테스트가 통과할 때까지 반복&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 /create-e2e-test https://staging.example.com을 치면 이 전체 흐름이 자동 실행된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  Command와 Skill, 뭘 써야 하나&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btQce4/dJMb990u1IJ/bkPdf9D6tVwyA3K3q1gqsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btQce4/dJMb990u1IJ/bkPdf9D6tVwyA3K3q1gqsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btQce4/dJMb990u1IJ/bkPdf9D6tVwyA3K3q1gqsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtQce4%2FdJMb990u1IJ%2FbkPdf9D6tVwyA3K3q1gqsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;286&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 작업 흐름은 Command로, 복잡하거나 템플릿/스크립트가 필요하면 Skill로 만드는 게 맞다. 기존 Command 파일도 여전히 동작하긴 하지만, Anthropic은 현재 Skill 방식을 공식 권장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Agent &amp;mdash; 역할이 분리된 부서&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄 정의&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;독립적인 컨텍스트에서 실행되는, 역할이 제한된 별도의 Claude&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 세 가지가 &quot;Claude를 어떻게 쓸지 설정하는 것&quot;이라면, Agent는 구조 자체가 다르다. 하나의 Claude가 모든 걸 하는 게 아니라, &lt;b&gt;역할이 다른 여러 Claude가 분업&lt;/b&gt;한다. 각 Agent는 자신만의 컨텍스트 윈도우를 가지고 독립적으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Skill과 달리 Agent는 &lt;b&gt;단일 .md 파일&lt;/b&gt;이다. 폴더가 아니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4BiGU/dJMcaiwjkuu/oYhkU9Vn2aGfAjazUx58Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4BiGU/dJMcaiwjkuu/oYhkU9Vn2aGfAjazUx58Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4BiGU/dJMcaiwjkuu/oYhkU9Vn2aGfAjazUx58Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4BiGU%2FdJMcaiwjkuu%2FoYhkU9Vn2aGfAjazUx58Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;216&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;위치에 따라 달라지는 적용 범위&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqeIr/dJMcagyuYaT/7yiWrkfWo3owYL2PKomhO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqeIr/dJMcagyuYaT/7yiWrkfWo3owYL2PKomhO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqeIr/dJMcagyuYaT/7yiWrkfWo3owYL2PKomhO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqeIr%2FdJMcagyuYaT%2F7yiWrkfWo3owYL2PKomhO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;118&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 로컬이 글로벌보다 우선순위가 높다. 팀과 공유하고 싶다면 .claude/agents/를 git에 커밋하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;--- &lt;br /&gt;name: code-reviewer &lt;br /&gt;description: 코드 리뷰 전용. PR 확인, 버그 검증, 구현 검토 시 자동 실행. &lt;br /&gt;tools: Read, Grep, Glob &lt;br /&gt;model: sonnet &lt;br /&gt;--- &lt;br /&gt;&lt;br /&gt;너는 시니어 코드 리뷰어야. &lt;br /&gt;&lt;br /&gt;리뷰 기준: &lt;br /&gt;- 버그 가능성이 있는 로직 &lt;br /&gt;- 보안 취약점 &lt;br /&gt;- 성능 이슈 &lt;br /&gt;&lt;br /&gt;출력 형식: &lt;br /&gt;- 심각도: 높음 / 중간 / 낮음 &lt;br /&gt;- 위치: 파일명:라인번호 &lt;br /&gt;- 문제: 무엇이 왜 문제인지 &lt;br /&gt;- 제안: 어떻게 고치면 좋을지 &lt;br /&gt;&lt;br /&gt;중요: 코드를 수정하거나 작성하지 말 것. 리뷰만 수행.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 특징이 있다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;tools&lt;/b&gt;: 이 Agent가 쓸 수 있는 도구를 제한할 수 있다. 리뷰어는 읽기만, 개발자는 쓰기까지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;model&lt;/b&gt;: Agent마다 다른 모델을 지정할 수 있다. 비용/성능을 역할에 맞게 조절 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네이밍&lt;/b&gt;: 파일명은 반드시 &lt;b&gt;하이픈&lt;/b&gt; 사용. code-reviewer.md O, code_reviewer.md X.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 필요한가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수하기 쉬운 지점이 여기에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 Skill과 Agent를 한 번에 만들어놓으면 Claude가 더 잘할 것 같지만, 실제로는 오히려 혼란스러워진다. 어떤 규칙을 따라야 할지 충돌이 생기기 때문이다. &lt;b&gt;중요한 건 많이 설정하는 게 아니라, 역할을 명확하게 분리하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 검토하는 Agent와 코드를 작성하는 Agent가 섞이면, 검토자가 수정까지 해버리거나 작성자가 리뷰 관점을 잃는다. 역할을 나누면 각자는 단순해지고, 전체 시스템은 오히려 더 안정적이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  전체 구조 한눈에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 안의 .claude/ 폴더를 기준으로 보면 이렇게 생겼다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfkr44/dJMcacXaWPk/7a23KCFXSvr2AHzV1la8RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfkr44/dJMcacXaWPk/7a23KCFXSvr2AHzV1la8RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfkr44/dJMcacXaWPk/7a23KCFXSvr2AHzV1la8RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfkr44%2FdJMcacXaWPk%2F7a23KCFXSvr2AHzV1la8RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;575&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정리 &amp;mdash; 뭘 언제 쓰나&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CLAUDE.md: 매 세션마다 같은 설명을 반복하고 싶지 않을 때 &lt;br /&gt;Skill: 같은 작업인데 매번 결과물이 달라질 때 &lt;br /&gt;Command: 반복하는 작업 흐름을 단축키로 만들고 싶을 때 &lt;br /&gt;Agent: 역할을 분리하거나 동시에 여러 작업을 돌려야 할 때&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;처음 시작할 때 이 순서로 하면 된다:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일단 대화로 써본다&lt;/li&gt;
&lt;li&gt;매번 반복하는 설명이 보이면 &amp;rarr; CLAUDE.md에 넣는다&lt;/li&gt;
&lt;li&gt;매번 반복하는 작업 방식이 보이면 &amp;rarr; Skill로 만든다&lt;/li&gt;
&lt;li&gt;매번 반복하는 실행 흐름이 보이면 &amp;rarr; Command로 만든다&lt;/li&gt;
&lt;li&gt;역할 충돌이 느껴지기 시작하면 &amp;rarr; Agent를 고려한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 다 설정하려고 하면 Claude도, 나도 혼란스러워진다. 패턴이 보일 때 하나씩 추가하는 게 실제로 더 잘 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Additional/  AI</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/137</guid>
      <comments>https://isliife2.tistory.com/137#entry137comment</comments>
      <pubDate>Sat, 25 Apr 2026 18:37:40 +0900</pubDate>
    </item>
    <item>
      <title>  1인 QA, 현재의 구조에서 어떻게 버틸 것인가</title>
      <link>https://isliife2.tistory.com/136</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  촉박한 일정일수록, 우선순위가 전부다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정이 촉박해질수록 모든 티켓을 처리하는 건 불가능해진다. 이번 스프린트에서 가장 크게 체감한 지점이었다. 티켓 하나하나를 들여다보면 대부분 기획과 완전히 일치하지 않았고, 사용자 입장에서도 충분히 문제가 될 수 있는 상태였다. 이론적으로는 전부 수정되어야 맞다. 하지만 일정이 계속 밀리고 시간이 줄어들면서, 결국 &quot;모든 걸 다 잡고 갈 수 없는 순간&quot;이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 &lt;b&gt;영향도&lt;/b&gt;, &lt;b&gt;리스크&lt;/b&gt;, 그리고 &lt;b&gt;비즈니스 관점&lt;/b&gt;을 기준으로 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 실제로 인지하는 문제인가? 지금 이 기능이 비즈니스적으로 핵심인가? 이 두 가지를 기준으로 크리티컬한 이슈부터 처리하고, UI나 문구처럼 사용자가 크게 인지하지 못하는 부분은 일단 후순위로 밀었다. 결과적으로 시연 현장에서는 큰 문제 없이 마무리되었고, 후순위로 미뤘던 이슈들은 실제 사용자에게 거의 인지되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어차피 다 고쳐야 하는 이슈들인데, 내가 이 중에서 어떤 걸 더 급하다고 판단하는 게 맞을까?&quot; 그 과정에서 이 고민이 계속 머릿속을 맴돌았다. 지금 돌이켜보면 판단 자체가 잘못됐던 것보다, 그 판단을 QA 혼자 내려야 했던 구조가 더 문제였던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위는 개인의 판단이 아니라, 팀이 함께 합의해야 하는 기준이다. 이 부분이 명확하지 않으면 QA는 계속해서 그 책임을 혼자 떠안게 된다. 이번 스프린트가 그걸 가장 선명하게 남긴 교훈이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  QA를 할수록 API와 DB가 보인다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 스프린트를 거치면서 더 확실해진 건, QA를 깊게 하려면 API와 DB를 이해해야 한다는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인 QA 환경에서는 특정 플로우를 테스트하기 위해 DB를 직접 조작해야 하는 경우가 생각보다 자주 찾아온다. 어떤 값이 있어야 기능이 동작하는지, 어떤 값을 추가하거나 삭제해야 테스트가 가능한지, 참조키는 무엇인지, 어떤 행위를 했을 때 어떤 값이 어떻게 바뀌는지. 이걸 모르면 테스트 자체가 막히는 상황이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API도 마찬가지다. 응답이 200 OK이고 네트워크 탭에 아무 에러도 없는데 화면이 이상하다. 그렇다면 그 다음은 어디를 봐야 하는가? 상태와 데이터 흐름을 봐야 한다. 이 사고 흐름이 자연스럽게 이어지느냐가 문제 해결 속도를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 감각은 개발자와 함께 원인을 추적하면서 계속 질문을 던지는 과정에서 만들어졌다. &quot;왜 이렇게 동작해요?&quot;, &quot;이 값이 여기 들어가는 게 맞나요?&quot;, &quot;이 응답에서 문제가 생길 수 있는 지점이 어디예요?&quot; 처음에는 단순히 모르는 것을 묻는 수준이었지만, 점점 내가 예상한 원인이 실제와 일치하는 경우가 많아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 체계적으로 공부할 시간은 부족하다. 하지만 실전에서 쌓이는 이 감각이 QA의 깊이를 만들어준다는 확신이 점점 커지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Jira 도입, 막막하지만 자산이 될 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 스프린트를 거치며 반복되는 일정 압박과 업무 구조에 대해 다시 한 번 생각해보게 되었다. 이러한 흐름 속에서, 보다 체계적인 협업과 이슈 관리를 위해 Jira가 도입되었다. Jira 자체보다 의미 있었던 건, 개인이 감당하던 문제들이 시스템으로 옮겨가기 시작했다는 점이었다. 혼자 버티는 구조에서, 점점 프로세스로 해결하는 방향으로 바뀌고 있다는 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 처음부터 구조를 설계하고 셋업하는 건 쉽지 않았다. Notion에서 Jira로 마이그레이션하는 작업도 만만치 않았는데, Claude의 Atlassian Rovo 커넥터를 활용하니 포맷만 맞춰주면 자동으로 매핑해서 옮겨줘서 생각보다 훨씬 수월했다. 첨부파일은 자동으로 이전되지 않아서 원본 Notion 링크를 댓글로 자동 추가하는 방식으로 보완했는데, 이 부분은 더 나은 방법이 있는지 계속 찾아보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 직접 구조를 설계하는 건 분명 부담이 크다. 하지만 누군가 만들어놓은 걸 그냥 쓰는 것과, 직접 필요에 맞게 설계해본 경험은 결이 다르다. 단순 사용자에서 벗어나 프로세스를 설계할 수 있는 QA로 성장하는 기회라고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  AI 자동화, 이제는 &quot;도입&quot;이 아니라 &quot;설계&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 만든 Claude 기반 TC 자동화 스킬을 사용해봤지만, 우리 조직의 포맷과 맞지 않아 오히려 수정 비용이 더 크게 들었다. 남이 만든 걸 억지로 맞추려 하다 보니, 1인 QA에게 리소스가 두 배로 드는 상황이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험을 통해 내린 결론은 명확했다. 남이 만든 것을 맞추는 게 아니라, 내 workflow에 맞는 자동화를 직접 설계해야 한다는 것. 지금 회사에서도 AI 활용을 장려하고 있고, 다른 직무에서도 적극적으로 시도하는 분위기다. 그 모습을 보면서 자극도 많이 받았다. 늦었다는 생각이 없지 않지만, 지금부터 제대로 시작해서 내 업무에 맞는 자동화를 만드는 게 더 중요하다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁극적으로는 내가 겪는 반복 작업을 줄이기 위한 도구를 만드는 방향으로 가야 한다. 남이 만든 걸 가져다 쓰는 게 아니라, 내가 겪은 불편함에서 출발한 자동화. 그게 결국 가장 오래 쓸 수 있는 도구가 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Action Item&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 스프린트를 마치고 나서 계속 머릿속에 남는 질문이 하나 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔들리는 일정, 끊임없이 들어오는 요구사항, 그 와중에 혼자 감당해야 하는 QA. 이 구조 안에서 API 테스트, 성능 테스트 같은 깊이 있는 테스트는 사실상 엄두를 내기 어렵다. 시간이 없어서가 아니라, 구조적으로 그 시간이 생길 수가 없는 환경이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금 내가 잡은 방향은 하나다. AI를 활용해서 먼저 시간을 확보하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 반복되는 작업들, 요구사항이 바뀔 때마다 처음부터 다시 해야 하는 것들, 사고보다 손이 더 많이 가는 영역들. 그 부분들을 AI로 덜어내고, 그렇게 확보한 시간으로 조금씩 더 깊은 테스트로 나아가고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 자동화 코드도 마찬가지다. 지금은 바이브 코딩의 시대라고 해도 과언이 아니다. 자동화 코드를 처음부터 직접 작성하는 것보다, AI와 함께 만들어가는 방식이 1인 QA에게는 훨씬 현실적인 선택지다. 완성도보다 속도, 완벽함보다 실제로 돌아가는 것. 그 방향으로 접근해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 자동화는 불가능하다. 판단이 필요한 순간은 결국 사람의 몫이다. 하지만 반복되는 것들을 AI에게 넘기고, 사람이 해야 하는 것에 집중할 수 있는 구조. 그걸 하나씩 만들어보는 게 지금 내가 잡은 다음 방향이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/136</guid>
      <comments>https://isliife2.tistory.com/136#entry136comment</comments>
      <pubDate>Sat, 25 Apr 2026 16:52:26 +0900</pubDate>
    </item>
    <item>
      <title>  Figma와 Claude를 활용한 테스트 자동화 구조 개선 경험</title>
      <link>https://isliife2.tistory.com/135</link>
      <description>&lt;h3 data-end=&quot;245&quot; data-start=&quot;213&quot; data-section-id=&quot;1dj2pka&quot; data-ke-size=&quot;size23&quot;&gt;  자동화는 코드가 아니라 구조라는 걸 느꼈던 경험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업에서는 짧은 스프린트 단위로 기능이 빠르게 변경되다 보니, 정말 다양한 경험과 버그를 계속해서 마주하게 되는 것 같다. 바쁜 일정 속에서도 가끔 일주일 정도 여유가 생기는 시기가 있는데, 그럴 때마다 기존에 어느 정도 구현되어 있던 자동화 코드를 개선해보려고 시도했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;기존 자동화 코드에는 일부 UI 요소에 data-testid가 적용되어 있었다. 전임자께서 자동화 안정성을 고려해 추가해두신 것이었지만, 해당 정보가 별도의 문서로 정리되어 있지 않고 매 스프린트마다 작성되는 기획 문서에만 일부씩 흩어져 있었다.&lt;/p&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 data-testid 관련 정보는 여러 문서에 분산되어 있었고, 어떤 화면에 어떤 식별자가 존재하는지 한 번에 파악하기 어려웠다. 자동화 코드와도 직접적으로 연결되어 있지 않았기 때문에, 실제로는 존재하는 식별자를 제대로 활용하지 못하는 상태였다.&lt;/p&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;새로운 테스트를 작성할 때마다 UI를 다시 분석해야 했고, 기존 테스트를 수정할 때도 locator를 다시 찾아야 했다. 결국 자동화가 누적될수록 효율이 좋아지는 것이 아니라, 오히려 유지보수 비용이 계속 증가하는 구조였다.&lt;/p&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1061&quot; data-start=&quot;921&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 단순히 locator를 바꾸는 것이 아니라, &lt;b&gt;자동화에서 사용할 수 있는 기준 자체를 만들어야 한다고 판단했다.&lt;/b&gt; 그리고 그 기준을 정리하기 위한 방법으로 Figma를 활용해 자동화 대상 UI 구조를 정리하기 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;1061&quot; data-start=&quot;921&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1092&quot; data-start=&quot;1068&quot; data-section-id=&quot;wem3ky&quot; data-ke-size=&quot;size23&quot;&gt;  왜 별도의 기준 파일을 만들었는가&lt;/h3&gt;
&lt;p data-end=&quot;1406&quot; data-start=&quot;1252&quot; data-ke-size=&quot;size16&quot;&gt;기존 자동화 코드는 대부분 UI 요소를 xpath나 텍스트 기반으로 탐색하고 있었다. 이 방식은 빠르게 작성하기에는 편하지만, 다음과 같은 한계를 가지고 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1123&quot; data-start=&quot;1011&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1045&quot; data-start=&quot;1011&quot; data-section-id=&quot;23hy7s&quot;&gt;UI 구조가 조금만 변경되어도 테스트 코드가 쉽게 깨짐&lt;/li&gt;
&lt;li data-end=&quot;1087&quot; data-start=&quot;1046&quot; data-section-id=&quot;1glrtew&quot;&gt;텍스트 변경, 다국어 대응 시 locator를 대량으로 수정해야 함&lt;/li&gt;
&lt;li data-end=&quot;1123&quot; data-start=&quot;1088&quot; data-section-id=&quot;bn0ldj&quot;&gt;기획 및 개발 변경이 자동화 안정성에 직접적인 영향을 줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;1125&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 방식은 장기적으로 유지하기 어려운 구조였고, UI 요소를 보다 구조적이고 의미 있는 기준으로 식별할 필요가 있다고 느꼈다. 그래서 그 기준을 정리하기 위해 별도의 Figma 파일을 생성하게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;1406&quot; data-start=&quot;1252&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1381&quot; data-start=&quot;1353&quot; data-section-id=&quot;hipyfm&quot; data-ke-size=&quot;size23&quot;&gt;  Figma를 자동화 기준으로 사용한 방식&lt;/h3&gt;
&lt;p data-end=&quot;1442&quot; data-start=&quot;1383&quot; data-ke-size=&quot;size16&quot;&gt;이 파일은 단순히 화면을 모아두는 용도가 아니라, 자동화 코드 구조와 직접 연결될 수 있도록 설계했다.&lt;/p&gt;
&lt;p data-end=&quot;1553&quot; data-start=&quot;1444&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1553&quot; data-start=&quot;1444&quot; data-ke-size=&quot;size16&quot;&gt;하나의 화면 단위를 하나의 페이지로 구성하고, 그 페이지는 자동화 코드의 Page Object와 대응되도록 만들었다. 즉, 화면 구조와 코드 구조를 분리하지 않고 동일한 기준으로 맞춘 것이다. 이렇게 구성하면서 UI 구조와 자동화 코드 구조가 자연스럽게 연결되었고, 화면이 변경되었을 때 어떤 코드에 영향을 주는지도 예측할 수 있게 되었다. 또한 Page Object의 책임 범위도 명확해졌다.&lt;/p&gt;
&lt;p data-end=&quot;1553&quot; data-start=&quot;1444&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1553&quot; data-start=&quot;1444&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 Figma는 단순한 디자인 도구가 아니라, &lt;b&gt;자동화 대상 UI를 정의하고 구조를 기록하는 기준 공간&lt;/b&gt;으로 사용되었다.&lt;/p&gt;
&lt;p data-end=&quot;1553&quot; data-start=&quot;1444&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1910&quot; data-start=&quot;1848&quot; data-ke-size=&quot;size16&quot;&gt;각 화면에서 어떤 UI가 어떻게 보이는지, 어떤 요소가 존재하는지를 Figma에 정리하고, 그 화면 안에 있는 실제 요소들 하나하나에 직접 annotation을 달기 시작했다. 그리고 이 annotation에는 설명이 아니라, &lt;b&gt;해당 요소에 부여되어야 할 식별자 값 자체를 기록했다. &lt;/b&gt;즉, 이 요소는 어떤 역할을 하는지 설명하는 것이 아니라, &amp;ldquo;이 요소에는 이 data-testid가 들어간다&amp;rdquo;는 값을 그대로 정의한 것이다.&lt;/p&gt;
&lt;p data-end=&quot;1757&quot; data-start=&quot;1684&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1757&quot; data-start=&quot;1684&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 되면서 Figma 문서는 단순한 UI 정리 문서가 아니라, &lt;b&gt;요소 단위 식별자 정의서&lt;/b&gt; 역할을 하게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;2134&quot; data-start=&quot;2040&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2160&quot; data-start=&quot;2141&quot; data-section-id=&quot;16rnnmb&quot; data-ke-size=&quot;size23&quot;&gt;  식별자 네이밍 기준 정리&lt;/h3&gt;
&lt;blockquote data-end=&quot;2234&quot; data-start=&quot;2162&quot; data-ke-style=&quot;style3&quot;&gt;{page-name}__{section/component}__{element-role}&lt;/blockquote&gt;
&lt;p data-end=&quot;2234&quot; data-start=&quot;2162&quot; data-ke-size=&quot;size16&quot;&gt;식별자는 위와 같은 형태의 규칙으로 정의했다. 이 규칙의 목적은 이름만 보고도 해당 요소의 위치와 역할을 이해할 수 있도록 만드는 것이었다. 예를 들어 특정 버튼이 어떤 화면의 어떤 영역에 속해 있고 어떤 역할을 하는지, 식별자만으로도 바로 파악할 수 있도록 구성했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2484&quot; data-start=&quot;2364&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2811&quot; data-start=&quot;2778&quot; data-section-id=&quot;nooyxt&quot; data-ke-size=&quot;size23&quot;&gt;  가장 중요했던 변화: Claude를 활용한 자동화&lt;/h3&gt;
&lt;p data-end=&quot;3422&quot; data-start=&quot;3302&quot; data-ke-size=&quot;size16&quot;&gt;이 작업을 하면서 가장 크게 느낀 변화는 따로 있었다.&lt;/p&gt;
&lt;p data-end=&quot;3422&quot; data-start=&quot;3302&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2172&quot; data-start=&quot;2065&quot; data-ke-size=&quot;size16&quot;&gt;QA 입장에서 서비스 코드를 깊게 분석하는 것은 현실적으로 부담이 크다. 컴포넌트 구조를 전부 파악해야 하고, 어떤 요소에 어떤 locator를 넣어야 하는지 직접 추적해야 하기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;2172&quot; data-start=&quot;2065&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2212&quot; data-start=&quot;2174&quot; data-ke-size=&quot;size16&quot;&gt;그런데 이 구조를 만든 이후, 접근 방식 자체가 완전히 달라졌다.&lt;/p&gt;
&lt;p data-end=&quot;2212&quot; data-start=&quot;2174&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2342&quot; data-start=&quot;2214&quot; data-ke-size=&quot;size16&quot;&gt;각 요소에는 annotation이 달려 있고, 이 요소들은 하나의 화면 안에서 최상단 레이어에 포함되어 있다. 이 상태에서 해당 화면의 최상단 레이어 선택 항목 링크만 전달하면, AI는 그 레이어 내부 구조를 그대로 탐색한다. 그리고 내부 요소들을 순회하면서 annotation이 달려 있는 요소를 찾아내고, 그 안에 적혀 있는 식별자 값을 그대로 읽는다. 이후 코드에서 해당 UI 요소를 찾아, 그 값을 data-testid 또는 testID로 그대로 적용한다.&lt;/p&gt;
&lt;p data-end=&quot;2342&quot; data-start=&quot;2214&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2564&quot; data-start=&quot;2484&quot; data-ke-size=&quot;size16&quot;&gt;사람이 요소를 하나씩 설명하거나 매핑해줄 필요 없이, 레이어 하나만 전달하면 구조와 식별자를 함께 해석하고 적용하는 흐름이 만들어진 것이다.&lt;/p&gt;
&lt;p data-end=&quot;2584&quot; data-start=&quot;2571&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2584&quot; data-start=&quot;2571&quot; data-ke-size=&quot;size16&quot;&gt;여기서 끝이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;2721&quot; data-start=&quot;2586&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;기존에 xpath 기반으로 작성되어 있던 자동화 코드도, 새롭게 정의된 식별자를 기준으로 자연스럽게 개선할 수 있었다. 코드에서 해당 요소를 찾아 식별자 기반으로 locator를 교체하고, 테스트 코드까지 함께 정리하는 흐름으로 이어졌다.&lt;/p&gt;
&lt;p data-end=&quot;2803&quot; data-start=&quot;2723&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2803&quot; data-start=&quot;2723&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로, 사람이 직접 UI를 보고 locator를 작성하던 방식에서 벗어나, 구조를 정의하면 자동화까지 이어지는 방식으로 바뀌게 되었다결국 내가 한 일은 단순히 자동화 코드를 개선한 것이 아니라, 자동화가 구조를 기반으로 동작하도록 만드는 방식으로 접근을 바꾼 것이었다.&lt;/p&gt;
&lt;p data-end=&quot;3042&quot; data-start=&quot;2957&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2964&quot; data-start=&quot;2954&quot; data-section-id=&quot;1meleq2&quot; data-ke-size=&quot;size23&quot;&gt;  정리하며&lt;/h3&gt;
&lt;p data-end=&quot;3484&quot; data-start=&quot;3391&quot; data-ke-size=&quot;size16&quot;&gt;스타트업 환경에서는 각 팀이 모두 바쁘게 움직이기 때문에, 자동화 코드 개선을 위해 필요한 요소들을 협업으로 정리하는 것 자체도 쉽지 않은 경우가 많다.&lt;/p&gt;
&lt;p data-end=&quot;3484&quot; data-start=&quot;3391&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;354&quot; data-start=&quot;205&quot; data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 느낀 점은, AI의 발전으로 이러한 작업을 QA 혼자서도 어느 정도 해결할 수 있는 환경이 만들어지고 있다는 것이었다. Figma에 구조와 식별자 기준을 정리해두면, 그 정보를 기반으로 실제 코드까지 반영하는 과정이 자연스럽게 이어질 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;354&quot; data-start=&quot;205&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;521&quot; data-start=&quot;356&quot; data-ke-size=&quot;size16&quot;&gt;결국 단순히 자동화 코드를 개선한 것이 아니라, 정보를 기록하고 그 기록이 실제 코드까지 연결되는 구조를 만든 것에 가까웠다. 회사 생활을 할수록 정보 기록과 공유의 중요성을 더 크게 느끼게 되는데, 이번 작업은 그 흐름을 유지하면서도 자동화의 유지보수까지 함께 개선할 수 있었던 경험이었다.&lt;/p&gt;
&lt;p data-end=&quot;521&quot; data-start=&quot;356&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;612&quot; data-start=&quot;523&quot; data-ke-size=&quot;size16&quot;&gt;또한 업데이트 로그나 식별자 기준, 페이지 구성 안내 등을 함께 정리하면서, 이 구조를 지속적으로 관리하고 확장할 수 있도록 만드는 것도 중요한 부분이었다.&lt;/p&gt;
&lt;p data-end=&quot;612&quot; data-start=&quot;523&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;747&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;다만 이 방식이 모든 영역에 항상 효율적인 것은 아니라고 생각한다. 자동화는 서비스의 모든 영역을 커버하기보다는, 변경사항은 적지만 서비스에서 핵심적인 역할을 하고 매 릴리즈마다 반드시 확인이 필요한 영역에 적용할 때 가장 효과적이다.&lt;/p&gt;
&lt;p data-end=&quot;747&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;924&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;반대로, 변화가 잦은 화면이나 구조가 자주 바뀌는 영역에까지 동일한 기준을 적용하면, 오히려 annotation 관리와 구조 유지 비용이 더 커질 수 있다. 그렇기 때문에 이 방식은 전체에 일괄 적용하기보다는, 안정성이 중요하고 반복적으로 검증이 필요한 영역에 선택적으로 적용하는 것이 더 효율적이라고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;3484&quot; data-start=&quot;3391&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3484&quot; data-start=&quot;3391&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/135</guid>
      <comments>https://isliife2.tistory.com/135#entry135comment</comments>
      <pubDate>Fri, 3 Apr 2026 20:27:23 +0900</pubDate>
    </item>
    <item>
      <title>  Explore It! 책을 읽고 깨달은, 내가 놓치고 있던 테스트 관점</title>
      <link>https://isliife2.tistory.com/134</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  탐험적 테스팅을 읽고 나서야 보이기 시작한 것들&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최근 저자 Elizabeth Hendrickson의 &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Explore It! 책을&lt;/span&gt;&amp;nbsp;읽다가 인상 깊은 메시지를 만났다. 애플리케이션을 사용할 때 한 가지 방식에만 고정되지 말고, 순서와 방법을 의도적으로 바꿔가며 탐험하라는 내용이었다. 처음 읽었을 때는 &quot;당연한 이야기 아닌가?&quot;라는 생각이 들었다. 하지만 책을 덮고 나서 최근 내가 진행했던 QA 경험들을 떠올려 보니, 나는 생각보다 훨씬 제한된 방식으로만 테스트하고 있었다는 걸 깨달았다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Hover는 모든 환경에 존재하지 않는다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 체감한 것은 hover 인터랙션이었다. 데스크탑에서는 hover가 너무 자연스럽다. 툴팁이 나타나고, 버튼이 활성화되고, 메뉴가 펼쳐지는 동작들이 모두 hover를 기반으로 한다. 하지만 태블릿 환경에서는 상황이 완전히 달라진다. 테스트를 진행하면서 hover 기반 동작이 기대대로 일어나지 않는 케이스를 발견했는데, 원인을 따라가 보니 단순했다. 터치 환경에서는 hover라는 개념 자체가 성립하기 애매했기 때문이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이 경험을 통해 깨달은 점은 분명했다. 나는 기능이 동작하는지를 확인하고 있었지만, 그 기능이 어떤 입력 환경 위에서 동작하는지는 충분히 탐험하지 않고 있었다. 이 지점에서 탐험의 범위를 내가 너무 좁게 잡고 있었다는 것을 깨달았다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  같은 드래그 앤 드롭이라도 제스처 충돌을 고려해야 한다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;제스처 영역에서도 중요한 깨달음이 있었다.&lt;br&gt;드래그 앤 드롭을 테스트하면서, 단순히 “동작이 되는가”보다 다른 제스처와의 충돌을 어떻게 방지하고 있는가가 더 중요하다는 점을 체감했다.&lt;br&gt;&lt;br&gt;태블릿 환경에서는 다음과 같은 제약이 존재한다.&lt;br&gt; • 스크롤과 드래그가 동일한 터치 기반 제스처를 사용한다.&lt;br&gt; • 필기(펜 입력)와 항목 이동 또한 입력 방식이 유사하다.&lt;br&gt;&lt;br&gt;이 때문에 드래그 앤 드롭은 단순 터치 이동이 아니라, 의도적인 꾹 누르기(long press) 이후 이동으로 설계되어야 스크롤 동작과 명확히 구분될 수 있다. 또한 필기 기능이 있는 환경에서는, 이동 제스처와 필기 입력을 구분하기 위한 툴 기반 모드 분리도 필요하다.&lt;br&gt;&lt;br&gt;이번 경험을 통해 드래그 앤 드롭을 하나의 단일 제스처로 보기보다,&lt;br&gt; • 스크롤과의 충돌&lt;br&gt; • 필기와의 충돌&lt;br&gt; • 입력 도구(손 vs 펜)&lt;br&gt;&lt;br&gt;까지 함께 고려해야 실제 사용자 경험을 제대로 검증할 수 있다는 점을 체감했다.&lt;br&gt;&lt;br&gt;기능 중심 검증만으로는 이런 미묘한 UX 리스크를 발견하기 어렵다는 것도 분명해졌다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  '직접 입력'만 테스트하면 생기는 함정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;책을 읽으며 또 하나 크게 공감했던 부분은 입력 경로의 다양성에 대한 이야기였다. 우리는 보통 입력 필드를 테스트할 때 직접 타이핑을 기준으로 확인하는 경우가 많다. 나 역시 크게 다르지 않았다. 하지만 실제로는 복사/붙여넣기라는 매우 흔한 사용자 행동이 존재한다. 그리고 이 지점에서 실제 이슈가 발생한 경험이 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;특정 기능에 대한 입력 필드에서는 최대 입력 길이 제한이 있었다. 직접 입력할 때는 제한 길이까지만 정상적으로 입력되었기 때문에 기능적으로는 문제가 없어 보였다. 그러나 제한 길이를 초과하는 텍스트를 복사해서 붙여넣기 할 경우, 아무 값도 입력되지 않는 현상이 발생했다. 기술적으로 보면 정책을 위반한 입력을 거부한 것이지만, 사용자 입장에서는 &quot;붙여넣기가 안 된다&quot;는 경험으로 받아들여질 수밖에 없었다. 실제로도 관련 CS 문의가 유입되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;나는 그동안 ‘입력된다’는 사실에만 집중했지, 사용자가 어떤 경로로 입력할 수 있는지까지는 충분히 탐험하지 않았다. 입력 경로의 변주 역시 탐험적 테스팅에서 놓치기 쉬운 영역이라는 것을 이 사례를 통해 실감했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;개인적으로는 다음과 같은 방향이 더 나은 사용자 경험에 가깝다고 생각한다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;최대 길이까지만 자동으로 잘라서 입력하거나&lt;/li&gt;&lt;li&gt;명확한 에러 피드백을 제공하거나&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 동일 문의가 반복된다면, 최대 입력 길이 정책 자체를 재검토하는 것도 하나의 선택지일 것이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마무리&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;탐험적 테스팅은 단순히 자유롭게 이것저것 눌러보는 활동이 아니라, 내가 당연하게 가정하고 있는 사용자 행동을 의심하는 과정에 가깝다는 생각이 들었다. 앞으로는 탐험적 테스팅의 관점에서 기능이 동작하는지만 확인하는 데 그치지 않고, 다음 질문들을 스스로에게 계속 던져보려고 한다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;사용자는 다른 방식으로 조작할 수 있지 않을까?&lt;/li&gt;&lt;li&gt;다른 디바이스에서는 인터랙션이 어떻게 달라질까?&lt;/li&gt;&lt;li&gt;기능은 정상인데 경험은 어색하지 않을까?&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/134</guid>
      <comments>https://isliife2.tistory.com/134#entry134comment</comments>
      <pubDate>Sun, 22 Feb 2026 15:51:07 +0900</pubDate>
    </item>
    <item>
      <title>  다양한 측면에서 성장할 수 있었던 릴리즈 회고</title>
      <link>https://isliife2.tistory.com/133</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  TC는 테스트 스크립트가 아니라 사고를 돕는 프레임이었다&lt;/h3&gt;
&lt;p data-end=&quot;604&quot; data-start=&quot;409&quot; data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서 가장 크게 바뀐 건 기능이 아니라 테스트 방식 자체였다. 버그를 얼마나 잡았는가보다, &lt;b&gt;어떻게 테스트할 것인가&lt;/b&gt;를 다시 정의한 릴리즈였다. 나는 현재 1인 QA로 일하고 있다. 여러 릴리즈를 거치면서 분명해진 사실이 하나 있었다. 완벽한 테스트를 목표로 삼는 방식은 이상적이지만 현실적으로 지속 가능하지 않다는 점이다. 그렇다고 품질을 포기할 수는 없다. 그래서 이번 릴리즈에서는 &quot;더 많이 테스트하는 방법&quot;이 아니라 &quot;더 현실적인 방식으로 테스트하는 방법&quot;을 찾는 데 집중했다.&lt;/p&gt;
&lt;p data-end=&quot;617&quot; data-start=&quot;606&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;617&quot; data-start=&quot;606&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 세 가지였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;617&quot; data-start=&quot;606&quot;&gt;TC 재설계&lt;/li&gt;
&lt;li data-end=&quot;659&quot; data-start=&quot;621&quot;&gt;Risk 기반 테스트&lt;/li&gt;
&lt;li data-end=&quot;659&quot; data-start=&quot;621&quot;&gt;탐색적 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;722&quot; data-start=&quot;661&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지 변화는 테스트 양을 줄이기 위한 선택이 아니라, &lt;b&gt;사고의 밀도를 높이기 위한 선택&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;722&quot; data-start=&quot;661&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;722&quot; data-start=&quot;661&quot; data-ke-size=&quot;size16&quot;&gt;화면 단위, 버튼 단위, 문구 단위로 가능한 모든 행동을 기록했다. 그렇게 해야 놓치지 않는다고 믿었기 때문이다. 문제는 이 방식이 변화에 가장 취약했다는 점이었다. 요구사항이 조금만 바뀌어도 TC는 즉시 낡은 문서가 되었고, 테스트보다 문서 유지가 더 많은 시간을 차지하기 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;989&quot; data-start=&quot;966&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;989&quot; data-start=&quot;966&quot; data-ke-size=&quot;size16&quot;&gt;릴리즈가 반복될수록 이상한 감각이 들었다. '나는 테스트를 하고 있는 걸까, 아니면 테스트를 위한 문서를 유지하고 있는 걸까?' 그 질문을 정면으로 마주한 순간, TC의 목적을 다시 생각하게 됐다. 완전한 기록을 만드는 건 끝이 없다. 하지만 TC의 목적이 &lt;b&gt;리스크를 이해하는 것&lt;/b&gt;이라면 이야기가 달라진다.&lt;/p&gt;
&lt;p data-end=&quot;1150&quot; data-start=&quot;1129&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1150&quot; data-start=&quot;1129&quot; data-ke-size=&quot;size16&quot;&gt;그래서 TC 작성 방식을 바꾸기 시작했다. UI, 문구, 행동을 하나씩 나열하는 대신 그 기능이 어떤 규칙을 가지는지, 어떤 예외가 존재하는지, 어디에서 깨질 가능성이 있는지를 기록했다. 사용자 행동을 따라 쓰는 대신 기능의 &quot;법칙&quot;을 남기기 시작한 것이다.&lt;/p&gt;
&lt;p data-end=&quot;1150&quot; data-start=&quot;1129&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1307&quot; data-start=&quot;1283&quot; data-ke-size=&quot;size23&quot;&gt; &lt;span&gt; &lt;/span&gt;떠오르는 질문을 남기기 시작했다&lt;/h3&gt;
&lt;p data-end=&quot;1369&quot; data-start=&quot;1309&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 자연스럽게 생긴 변화가 하나 있었다. TC의 비고란이 점점 사고 메모 공간이 되기 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;1369&quot; data-start=&quot;1309&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1412&quot; data-start=&quot;1371&quot; data-ke-size=&quot;size16&quot;&gt;테스트 케이스를 적는 과정에서 순간적으로 떠오르는 질문들을 그대로 남겼다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1412&quot; data-start=&quot;1371&quot;&gt;&quot;이 상태에서 뒤로 가면?&quot;&lt;/li&gt;
&lt;li data-end=&quot;1464&quot; data-start=&quot;1416&quot;&gt;&quot;값이 비어 있으면?&quot;&lt;/li&gt;
&lt;li data-end=&quot;1464&quot; data-start=&quot;1416&quot;&gt;&quot;연계 활동이라면?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1728&quot; data-start=&quot;1675&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순한 메모였다. 하지만 실제 테스트 단계에서 이 질문들이 놀라울 정도로 도움이 됐다. 놓칠 수 있었던 케이스들이 다시 눈에 들어왔고, 탐색 테스트를 할 때 사고의 방향을 잡아주는 역할을 했다.&lt;/p&gt;
&lt;p data-end=&quot;1728&quot; data-start=&quot;1675&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1728&quot; data-start=&quot;1675&quot; data-ke-size=&quot;size16&quot;&gt;결국 TC는 체크리스트가 아니라 &lt;b&gt;테스트 과정에서의 사고 기록&lt;/b&gt;이 되었다. 완성된 문서보다 중요한 건 테스트하면서 어떤 질문을 던졌는지였다. 이 방식 덕분에 TC는 정적인 문서가 아니라 릴리즈마다 진화하는 참고 자산이 되기 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;1728&quot; data-start=&quot;1675&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1728&quot; data-start=&quot;1675&quot; data-ke-size=&quot;size23&quot;&gt;  QA는 정책을 가장 잘 알아야 하는 역할이었다&lt;/h3&gt;
&lt;p data-end=&quot;2382&quot; data-start=&quot;2335&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 하면서 점점 분명해진 사실도 하나 있었다. QA는 단순히 기능을 검증하는 역할이 아니라, 그 기능이 어떤 정책 위에서 동작하는지를 회사에서 가장 정확하게 이해해야 하는 역할이라는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;1884&quot; data-start=&quot;1756&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1884&quot; data-start=&quot;1756&quot; data-ke-size=&quot;size16&quot;&gt;기대 결과를 판단하려면 기준을 알아야 하고, 기준을 알려면 정책을 이해해야 한다. 결국 QA는 정책을 가장 많이 검증하고, 가장 자주 질문하게 되는 역할에 가까웠다. 이건 선택이 아니라 구조적인 특성에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;2519&quot; data-start=&quot;2473&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2519&quot; data-start=&quot;2473&quot; data-ke-size=&quot;size16&quot;&gt;그래서 위키 작업은 문서 정리가 아니라 &lt;b&gt;테스트 기반을 만드는 작업&lt;/b&gt;이었다. 정책을 기억이나 개인에게 의존하지 않고 누구나 확인 가능한 형태로 남기는 것, QA 혼자만 알고 있는 정보가 아니라 팀 전체가 공유할 수 있는 기준을 만드는 것이 핵심이었다. 이 과정에서 개발팀의 도움을 받아 Slack 검색봇과 연동했고, 정책을 빠르게 찾을 수 있는 구조를 만들 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2519&quot; data-start=&quot;2473&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2519&quot; data-start=&quot;2473&quot; data-ke-size=&quot;size16&quot;&gt;검색 효율을 높이기 위해 문서 작성 방식도 바뀌었다. 검색봇은 복잡한 들여쓰기, 표 구조, 2열 배치는 등은 인식률이 떨어졌다. 그래서 위키에는 일부러 정책 단위를 완성된 문장 형태로 작성했고, 적용 대상을 명확히 표기했다. 사람에게 읽기 쉬운 구조와 검색 시스템이 읽기 쉬운 문장을 동시에 고려한 문서였다.&lt;/p&gt;
&lt;p data-end=&quot;2706&quot; data-start=&quot;2612&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2706&quot; data-start=&quot;2612&quot; data-ke-size=&quot;size23&quot;&gt; &lt;span&gt; &lt;/span&gt;Figma를 이용한 자동화 구조 설계&lt;/h3&gt;
&lt;p data-end=&quot;2706&quot; data-start=&quot;2612&quot; data-ke-size=&quot;size16&quot;&gt;자동화를 계속 확장하면서 느낀 건 테스트가 깨지는 이유의 대부분은 코드가 아니라 구조라는 점이었다. 현재 자동화 코드의 상당수는 xpath 기반으로 UI 요소를 탐색하고 있다. 초기에는 빠르게 테스트를 구축하는 데에는 효과적이었지만, 릴리즈가 반복될수록 이 방식의 한계가 분명해졌다.&lt;/p&gt;
&lt;p data-end=&quot;2706&quot; data-start=&quot;2612&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;600&quot; data-start=&quot;446&quot; data-ke-size=&quot;size16&quot;&gt;UI 구조가 조금만 바뀌어도 locator가 깨지고, 문구 수정 하나가 테스트 실패로 이어졌다. 자동화가 안정성을 높이는 도구가 아니라 오히려 유지보수 비용으로 돌아오는 순간들이 생겼다. 그래서 단기적인 수정 대신 장기적으로 안정적인 식별 체계를 만들기 위한 준비를 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;자동화 코드 작성 이후 남는 시간을 활용해, 예전부터 생각해왔던 자동화 요소 정리를 Figma로 진행했다. 기존 기획 파일에는 자동화 대상 요소들이 흩어져 있었고, 그 상태로 개발팀에 바로 요청하기는 어려웠다. 그래서 직접 자동화 관점에서 UI를 재구성한 전용 파일을 만들기로 했다.&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;762&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;762&quot; data-ke-size=&quot;size16&quot;&gt;URL 기준으로 페이지를 분리하고, 페이지별 핵심 요소를 정리하고, 향후 data-testid 네이밍 규칙과 연결할 수 있도록 구조를 설계했다. 동시에 POM 구조와 자연스럽게 이어질 수 있도록 요소 단위를 정리했고, 변경 사항을 추적할 수 있도록 업데이트 로그도 함께 관리했다. 이 과정은 단순히 자동화를 준비하는 작업이 아니라, UI를 테스트 가능한 구조로 재해석하는 작업에 가까웠다. 자동화는 QA 혼자 완성할 수 있는 영역이 아니라 제품 구조와 함께 설계해야 하는 영역이라는 걸 분명히 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;1176&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1176&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다 개인적으로 의미 있었던 점은, 예전부터 배우고 싶었던 Figma를 실제 업무 문제 해결에 활용할 수 있었다는 것이다. 도구 학습이 목적이 아니라, 품질 구조를 개선하는 과정에서 자연스럽게 익히게 되었다는 점이 더 크게 남았다.&lt;/p&gt;
&lt;p data-end=&quot;3617&quot; data-start=&quot;3543&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;3836&quot; data-start=&quot;3818&quot; data-ke-size=&quot;size23&quot;&gt;  다시 기본으로 돌아가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취준 시절 읽었던 『테스트 너머의 QA 엔지니어링』을 4개월차 QA 엔지니어가 된 지금 다시 펼쳐봤다. 실무를 겪은 뒤 읽는 문장은 전혀 다르게 다가왔다. Agile 환경, 정적 테스트, 동적 테스트, 테스트 레벨 구조. 예전에는 개념으로만 이해하던 내용들이 이제는 실제 경험과 연결되기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 리뷰에 참여하는 것이 정적 테스트라는 것, 프로세스 초기에 QA가 개입하는 것이 비용 절약과 품질 개선으로 이어진다는 것, 개발자 역시 테스트를 수행한다는 사실. 개발자는 Unit test와 Integration test를 맡고, QA는 System test를 맡는다는 역할 분리가 이제야 명확해졌다. 예전에는 막연히 알고 있던 구조가 실제 팀 안에서 어떻게 작동하는지 이해되기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 다시 읽게 된 이유는 단순한 복습이 아니었다. 지난 몇 달 동안 스스로 성장에 대한 의문이 계속 따라다녔다. 내가 지금 잘 하고 있는 건지, 제대로 성장하고 있는 건지, 눈에 보이는 성과가 없는 것 같다는 생각이 나를 계속 괴롭혔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 돌아보면, 내가 했던 일들은 대부분 눈에 잘 보이지 않는 종류의 일이었다. 테스트 프로세스를 개선하고, 정책을 정리하고, 리뷰에 참여하고, 구조를 바꾸는 일들. 기능 하나를 만드는 것보다 덜 화려하지만, 분명히 품질을 높이는 행동들이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QA 기간은 내가 가장 기다리는 순간이다. 그동안 준비했던 프로세스와 구조를 실제로 적용해볼 수 있는 시간이다. 기획 단계에서 들었던 기능이 개발자의 손을 거쳐 구현되고, 내가 그것을 처음 확인해볼 수 있다는 점도 이 직무의 큰 즐거움이다. 내가 의견을 냈던 부분이 반영되어 실제 제품이 되는 순간에는 책임감과 동시에 묘한 뿌듯함도 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 릴리즈 이후 발견된 이슈는 여전히 아쉽지만, 동시에 다음 릴리즈를 위한 중요한 피드백이 된다. 예전에는 그 감정이 죄책감에 가까웠다. 하지만 지금은 조금 다르게 생각한다. 완벽한 테스트는 존재하지 않는다. 놓친 부분은 실패가 아니라 다음 액션 아이템이다. 기록하고, 정리하고, 다음 릴리즈에 반영할 기회다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 다시 읽으면서 가장 크게 와닿았던 문장은 하나였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;QA 엔지니어는 테스트를 수행하는 직무가 아니라 품질을 설계하는 직무다.&lt;/blockquote&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 해왔던 시도들이 헛된 것이 아니었다는 확신을 얻었다. 동시에 아직 모르는 것이 많다는 사실도 인정하게 됐다. 특히 테스트 프로세스를 체계적으로 경험해본 적이 없다는 점, 1인 QA 체제가 어떤 구조로 굴러가야 하는지에 대한 고민은 여전히 남아 있다.&lt;/p&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1510&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1510&quot; data-ke-size=&quot;size16&quot;&gt;아마 이 고민은 당분간 계속될 것 같다. 하지만 지금은 그 질문 자체가 성장의 일부라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size23&quot;&gt;  Action Item&lt;/h3&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;다음 목표는 꽤 명확해졌다. 테스트 범위를 넓히기 전에, 먼저 테스트 사고를 구조화하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;238&quot; data-ke-size=&quot;size16&quot;&gt;사용자 시나리오 기반 검증을 강화하고, 사이드 이펙트를 놓치지 않기 위한 체크리스트를 만들고, 릴리즈마다 반드시 확인해야 하는 최소 보장 세트를 정리하는 것. 그리고 TC 수행 순서를 더 효율적으로 설계해 한 번의 테스트 흐름 안에서 최대한 많은 영역을 동시에 검증할 수 있도록 개선하는 것.&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;238&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;515&quot; data-start=&quot;403&quot; data-ke-size=&quot;size16&quot;&gt;자동화 역시 같은 방향에 있다. 단순히 테스트 수를 늘리는 것이 아니라, 실패를 줄이기 위한 구조 개선이 먼저다. 안정적인 식별 체계를 만들고, 유지보수 비용을 줄이는 방향으로 자동화를 확장하고 싶다.&lt;/p&gt;
&lt;p data-end=&quot;515&quot; data-start=&quot;403&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;616&quot; data-start=&quot;517&quot; data-ke-size=&quot;size16&quot;&gt;장기적으로는 프로세스를 개선하여 기능 테스트에 쓰는 리소스를 줄이고, API 테스트와 성능 테스트 같은 다른 영역으로 시간을 투자하는 것이 목표다. 테스트의 깊이를 넓히는 쪽으로 방향을 잡고 있다.&lt;/p&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;4034&quot; data-start=&quot;4025&quot; data-ke-size=&quot;size23&quot;&gt;  마무리&lt;/h3&gt;
&lt;p data-end=&quot;4199&quot; data-start=&quot;4142&quot; data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서 가장 크게 바뀐 건 테스트의 양이 아니라 테스트를 바라보는 관점이었다. TC 작성 방식과 테스트 접근을 다시 정리하면서 사고를 늘리고, 기록을 줄이고, 탐색을 늘리고, 기억 대신 구조를 만들었다.&lt;/p&gt;
&lt;p data-end=&quot;4199&quot; data-start=&quot;4142&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;아직 완벽한 QA 엔지니어에 대한 정답은 모르겠다. 하지만 이전보다 덜 지치고, 더 명확하게 테스트하고 있다는 느낌은 분명하다. 그리고 그 방향은 아마 틀리지 않았을 것이다. 완벽한 테스트는 존재하지 않는다. QA는 모든 문제를 막아내는 직무가 아니라, 불완전한 시스템을 조금씩 더 나은 구조로 바꾸는 역할에 가깝다. 적어도 품질이라는 영역 안에서는 그렇다.&lt;/p&gt;
&lt;p data-end=&quot;505&quot; data-start=&quot;403&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;534&quot; data-start=&quot;507&quot; data-ke-size=&quot;size16&quot;&gt;이번 릴리즈는 그 방향을 다시 확인한 시간이었다.&lt;/p&gt;
&lt;p data-end=&quot;4199&quot; data-start=&quot;4142&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4199&quot; data-start=&quot;4142&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/133</guid>
      <comments>https://isliife2.tistory.com/133#entry133comment</comments>
      <pubDate>Sat, 31 Jan 2026 16:23:30 +0900</pubDate>
    </item>
    <item>
      <title>  다사다난했던 릴리즈 회고</title>
      <link>https://isliife2.tistory.com/132</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  흔들리는 일정 속에서, 내가 지켜낸 것들&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;신입이자 1인 QA로서 몇 번의 릴리즈를 겪다 보니, 이제는 단순히 &quot;버그를 얼마나 잡았는가&quot;보다 &quot;이 상황에서 어떤 리스크를 어떻게 판단했는가&quot;가 훨씬 더 크게 남는다. 이번 릴리즈는 그런 감각이 유난히 선명해진 릴리즈였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;일정 변동 가능성은 어느 정도 예상하고 있었지만, 진짜 힘들었던 건 지연 자체보다 &lt;b&gt;지연이 계속 다른 형태로 반복됐다는 점&lt;/b&gt;이었다. 릴리즈 직전까지 일정이 여러 차례 변경되었고, 배포 시점에서도 예기치 못한 이슈로 재조정이 반복되었다. 그 과정에서 장시간 대기와 긴장 상태가 이어졌고, 체력과 멘탈 모두 큰 소모를 느꼈다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 릴리즈를 통해 다시 한 번 체감한 건, QA가 일정의 가장 후단에 위치한 구조상, 일정 변동의 영향을 직접적으로 받을 수밖에 없다는 걸 체감했다. 이 경험 이후로 계속 머릿속에 남은 질문은 하나였다. &quot;일정이 계속 밀릴 때, QA는 어떻게 대응하는 게 최선일까?&quot;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  사소한 디테일은 왜 늘 마지막에 남을까&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서는 디자인 리뷰 단계부터 Shift Left Testing을 의식적으로 더 적극적으로 시도했다. 초기 단계에서 최대한 자세히 듣고, 질문을 많이 하면서 나중에 터질 수 있는 문제를 미리 줄여보려 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;실제로 도움이 된 부분도 분명히 있었다. 그런데 이 과정에서 다시 한 번 강하게 느낀 게 있다. 사실 이전 릴리즈까지는 &lt;b&gt;기획서에 Annotation 자체가 거의 없는 상태&lt;/b&gt;였다. Figma 기획서는 UI 위주로만 정리되어 있었고, 세부 기획은 댓글로 흩어져 있거나 구두로 전달되는 경우가 대부분이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;처음 이 구조를 접했을 때, QA 관점에서 놓치기 쉬운 부분이 있었다. QA 입장에서는 UI만 보고 테스트 케이스를 작성할 수가 없다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;버튼이 언제 활성화되는지&lt;/li&gt;&lt;li&gt;버튼을 누르면 어떤 화면이 노출되는지&lt;/li&gt;&lt;li&gt;어떤 조건에서 Validation이 걸리는지&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이런 것들은 전부 &lt;b&gt;기획의 일부&lt;/b&gt;인데, 그 핵심 정보들이 문서가 아니라 댓글과 구두 설명에 흩어져 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그러다 보니 자연스럽게 문제가 생겼다. 누군가는 이 내용을 알고 있고, 누군가는 모르고 있고, 결과적으로 &lt;b&gt;다들 뭔가를 빠뜨리는 상황&lt;/b&gt;이 반복됐다. &quot;이걸 왜 빠뜨리지?&quot; 싶을 정도의 디테일들이 계속 누락됐다. 그래서 입사 후 첫 릴리즈를 마친 뒤, 이런 방식의 기획 전달에는 분명한 한계가 있다고 느꼈고 QA 입장에서 그 문제를 그대로 전달했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그 결과 이번 릴리즈부터는 기획서에 &lt;b&gt;Annotation이 추가되었고&lt;/b&gt;, 어떤 부분이 수정되었는지를 확인할 수 있도록 &lt;b&gt;업데이트 로그도 함께 도입되었다. &lt;/b&gt;이 덕분에 변경 사항이 댓글에 흩어지지 않고, &quot;언제, 어떤 부분이, 왜 바뀌었는지&quot;를 QA와 개발자가 동일한 기준으로 확인할 수 있게 되었고, 기획 전달 측면에서는 분명히 한 단계 개선되었다고 느꼈다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그럼에도 불구하고, 또 한 번 느끼게 된 게 있다. 아주 사소한 디테일은, Annotation과 업데이트 로그가 있어도 여전히 빠진다는 점이었다. 개발자도, QA도, 기획자도 큰 흐름은 잘 챙기는데, 버튼 하나의 조건, 화면 전환 타이밍, 예외 케이스 같은 정말 디테일한 부분은 여전히 놓치게 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 지금은 이런 고민으로 이어지고 있다. 이건 단순히 &quot;기록이 부족해서&quot; 생기는 문제일까? 아니면&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;구조적인 문제일까,&lt;/li&gt;&lt;li&gt;프로세스의 문제일까,&lt;/li&gt;&lt;li&gt;아니면 이 정도 누락은 어느 조직에서나 발생하는 걸까?&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아직 정답은 모르겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  테스트 케이스 &quot;기능 단위&quot;만으로는 부족했다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;TC는 여전히 어렵다. 다만 이번 릴리즈를 통해 확실하게 체감한 게 하나 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;같은 기능인데도 단일 활동에서는 정상적으로 동작하는데, 연계 활동에서는 깨지는 케이스가 있었다. 이 경험을 하면서 느낀 건, 기능 단위 테스트는 결국 &lt;b&gt;고립된 상태에서의 정상 동작&lt;/b&gt;만 검증할 수 있다는 점이었다. 기능 하나만 떼어 놓고 보면 문제없지만, 이전 활동의 상태나 흐름이 이어지는 순간 예상치 못한 문제가 발생했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 자연스럽게 이런 결론에 도달했다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;기능 단위 테스트만으로는 부족하고, 실제 사용 흐름을 기준으로 한 사용 시나리오 단위 테스트가 반드시 필요하다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;또 하나 크게 느낀 건, TC를 나누는 기준은 &quot;화면이 같은가/다른가&quot;가 아니라 &quot;같은 컴포넌트를 사용하는가&quot;가 훨씬 중요하다는 점이었다. 겉보기엔 같은 화면인데 서로 다른 컴포넌트를 사용해 한쪽만 수정되고 다른 쪽은 그대로 남아 있는 경우도 있었고, 반대로 완전히 다른 화면인데 내부적으로는 같은 컴포넌트를 공유해 한쪽 수정이 다른 화면까지 영향을 주는 경우도 있었다. 그래서 TC를 작성할 때도 &quot;이 화면이 비슷해 보이냐&quot;보다 &quot;같은 구현 단위를 공유하느냐&quot;를 먼저 확인하게 되었고, 그 기준에 따라 TC를 세분화할지, 묶을지를 판단하게 되었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  시간이 부족해지면 우선순위도 무너진다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서는 시간 부족으로 인해 우선순위 판단이 매우 어려워졌던 순간도 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;생성해둔 티켓들을 보면 하나같이 기획대로 구현되지 않았고, 사용자 입장에서 충분히 문제가 될 수 있는 상태였다. 이론적으로만 보면 전부 수정되어야 하는 이슈들이었다. 하지만 일정이 계속 밀리고 시간이 촉박해지면서 현실적으로는 모든 걸 다 잡고 갈 수 없는 상황이 되었고, 결국 정말 급한 것들부터 처리하는 방향으로 이미 생성해둔 티켓들의 우선순위를 다시 조정하게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그 과정에서 계속 이런 고민이 들었다. &quot;어차피 다 고쳐야 하는 이슈들인데, 내가 이 중에서 어떤 걸 더 급하다고 판단하는 게 맞을까?&quot; 지금 돌이켜보면, 이 고민은 판단을 잘못했느냐의 문제가 아니라 &lt;b&gt;우선순위 판단을 QA가 중심적으로 맡게 되는 구조&amp;nbsp;자체의 한계&lt;/b&gt;에 가까웠던 것 같다. 그래서 오히려 이런 생각에 이르게 되었다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;우선순위는 QA 혼자 정하는 ‘정답’이 아니라 팀이 함께 합의해서 만들어가는 '결과'여야 한다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  CS는 &quot;일회성 대응&quot;이 아니라 &quot;재사용 가능한 지식&quot;이었다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서는 CS 이슈도 꽤 인상 깊었다. 특히 외부 링크 리다이렉션 이슈는 내부 로직 오류라기보다는, 외부 네트워크나 보안 정책(프록시/방화벽)과 관련된 이슈일 가능성이 컸고, 실제로 에러 메시지에서도 그 힌트를 명확하게 확인할 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;CS를 처리하다 보면 QA가 단순히 내용을 전달하는 역할에 머무르지 않고, 직접 원인을 파악하고 해결해야 하는 순간이 생각보다 자주 찾아온다. 이번 릴리즈에서는 자주 반복되는 CS에 대해 개발자에게 해결 방법을 전달받고, 그 과정을 상세히 기록해두는 데 특히 신경을 많이 썼다. 처음엔 &quot;이런 것까지 기록해야 하나?&quot; 싶을 정도로 세세하게 남겼지만, 지금 돌이켜보면 그 기록들이 엄청난 자산이 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;유사한 CS가 다시 들어왔을 때는 필요한 정보들을 확인하고, 사전에 정리해 둔 절차에 따라 대응하면서, 개발자에게 추가 요청을 하지 않아도 QA 차원에서 바로 해결 가능한 케이스가 눈에 띄게 늘었다. 이 경험을 통해 느낀 건, CS는 그때그때 처리하고 끝나는 일이 아니라 &lt;b&gt;한 번 정리해두면 계속 재사용할 수 있는 지식&lt;/b&gt;이라는 점이었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  일정 지연이 만든 '붕 뜬 시간', 자동화로 써봤다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서는 TC 작성이 끝난 뒤 개발 완료를 기다리는 동안 비교적 긴 대기 시간이 생겼고, 그 시간을 자동화 구현에 집중해서 사용해볼 수 있었다. 일정 지연이 무조건 손해는 아니고, 그 시간을 어떻게 쓰느냐에 따라 자동화, 테스트 문서 정리, 새로운 기술 학습 등으로 전환할 수 있다는 가능성을 확인했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다음부터는 일정이 밀릴 때 그냥 불안해하며 기다리기보다는, &quot;이 시간을 어떻게 활용할지&quot;를 더 의도적으로 설계해보고 싶다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마무리하며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번 릴리즈에서 가장 크게 느낀 건, &lt;b&gt;&lt;/b&gt;돌아서면 수정되고, 돌아서면 잘 되던 기능이 깨지고, 돌아서면 사이드 이펙트가 발생한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이 모든 변경 이력을 머릿속에 들고 있는 건 생각보다 큰 부담이었다. 그래서 이제는 &quot;내가 더 잘해야지&quot;보다는, 이 구조를 어떻게 팀 차원에서 인식하고 개선할 수 있을지를 더 고민해보려 한다. 물론 내가 발견하지 못한 버그에 대해 내 책임이 전혀 없다고 말할 수는 없다. QA로서 놓친 부분이 분명히 있었을 것이다. 하지만 동시에, 그 모든 누락이 개인의 역량 문제만은 아니라는 생각도 들었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;일정은 계속 흔들리고, 기능이 빠르게 변화하는 상황에서, 사소한 디테일부터 사용성까지 모두 챙기는 건 현실적으로 매우 어려운 상황들이 분명히 있었다. 큰 기능들조차 안정화되지 않은 상태에서, 구현이 끝나지 않은 상황에서 &quot;왜 여기까지 못 봤을까&quot;라고 스스로를 몰아붙이는 건 문제를 해결하는 데 크게 도움이 되지 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 이제는 자책하기보다는, &quot;이 환경에서 왜 이런 누락이 반복될 수밖에 없었을까&quot;, &quot;다음에는 이 부담을 어떻게 줄일 수 있을까&quot;를 계속해서 고민하는 쪽이 더 중요하다고 느낀다. 사소한 디테일을 놓친 건 단순히 개인의 부족함이라기보다, 그 디테일을 놓칠 수밖에 없는 구조가 있었기 때문일 수도 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 이 과정에서 또 하나 느낀 건, 이런 고민을 혼자 끌어안고 계속 스스로를 몰아붙이다 보면 장기적으로 지속 가능성을 고민하게 만드는 구조라는 점이었다. 완벽하게 하고 싶다는 성격이 때로는 나를 성장하게 만들지만, 이 환경에서는 그 완벽함이 오히려 나를 갉아먹을 수도 있겠다는 생각이 들었다. 그래서 앞으로는 쉬어야 할 때는 쉬고, 모든 걸 혼자 책임지려 하지 않고, 천천히, 자책하지 않으면서 이 구조를 어떻게 바꿀 수 있을지를 고민해보려 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;QA가 할 수 있는 역할은 모든 걸 완벽하게 막아내는 것이 아니라, &lt;b&gt;문제가 반복되는 구조를 인식하고, 가시화하고, 조금씩이라도 바꿔보려는 시도를 멈추지 않는 것&lt;/b&gt;이라는 생각이 들었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/132</guid>
      <comments>https://isliife2.tistory.com/132#entry132comment</comments>
      <pubDate>Sat, 3 Jan 2026 13:24:38 +0900</pubDate>
    </item>
    <item>
      <title>  Playwright에서 마이크 기능 자동화하기</title>
      <link>https://isliife2.tistory.com/131</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  음성 입력이 필요한 테스트 화면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 회사 서비스 테스트 자동화 코드를 Playwright + TypeScript로 작성하고 있다.&lt;br /&gt;버튼 클릭, 입력창 검증 같은 기본적인 UI 테스트는 어느 정도 익숙해졌는데,&lt;br /&gt;이번에 처음으로 &lt;b&gt;마이크 입력이 필수인 화면&lt;/b&gt;을 자동화해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;441&quot; data-start=&quot;392&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;실제로 음성 입력이 없는 상태에서는 다음 단계로 넘어갈 수 없도록 막혀 있었고,&lt;br /&gt;매번 사람이 직접 말해야 한다면 회귀 테스트나 반복 테스트에서 자동화의 의미가 크게 줄어든다.&lt;br /&gt;그래서 어떻게든 &lt;b&gt;사람의 목소리를 쓰지 않고 테스트할 방법&lt;/b&gt;을 찾아보기로 했다.&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size23&quot;&gt;  음성 파일을 마이크 입력처럼 쓰는 방식&lt;/h3&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말하면, &lt;b&gt;실제 마이크 대신 미리 준비한 음성 파일을 마이크 입력처럼 사용하는 방식&lt;/b&gt;을 선택했다.&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;958&quot; data-start=&quot;769&quot; data-ke-size=&quot;size16&quot;&gt;먼저&amp;nbsp;테스트에&amp;nbsp;사용할&amp;nbsp;문장을&amp;nbsp;직접&amp;nbsp;작성했다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;실제&amp;nbsp;사용자가&amp;nbsp;말할&amp;nbsp;법한&amp;nbsp;대본을&amp;nbsp;기준으로&amp;nbsp;문장을&amp;nbsp;만들고,&lt;br /&gt;&lt;b&gt;ElevenLabs&lt;/b&gt;를&amp;nbsp;이용해&amp;nbsp;mp3&amp;nbsp;음성&amp;nbsp;파일로&amp;nbsp;생성했다.&lt;br /&gt;&lt;br /&gt;처음엔&amp;nbsp;mp3&amp;nbsp;파일을&amp;nbsp;그대로&amp;nbsp;사용하면&amp;nbsp;될&amp;nbsp;줄&amp;nbsp;알았지만,&lt;br /&gt;Playwright에서&amp;nbsp;가짜&amp;nbsp;마이크&amp;nbsp;입력으로&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;파일&amp;nbsp;형식은&amp;nbsp;&lt;b&gt;wav&amp;nbsp;파일&lt;/b&gt;이었다.&lt;br /&gt;그래서 mp3 -&amp;gt; wav 변환을 진행했고, &lt;b&gt;freeconvert&lt;/b&gt;를&amp;nbsp;사용해&amp;nbsp;간단히&amp;nbsp;해결했다.&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size23&quot;&gt;  마이크 자동화의 핵심은 이 3가지 옵션&lt;/h3&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;여러 시행착오 끝에 알게 된 사실은,&lt;br /&gt;&lt;b&gt;마이크 자동화의 핵심은 테스트 코드보다 브라우저 실행 옵션에 있다&lt;/b&gt;는 것이었다.&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1414&quot; data-start=&quot;1392&quot; data-ke-size=&quot;size16&quot;&gt;아래 3가지 옵션이 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1765618094644&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--use-fake-device-for-media-stream              // 팝업 차단
--use-fake-ui-for-media-stream                  // 장치 대체
--use-file-for-fake-audio-capture=PATH_TO_WAV   // 입력 소리 제공&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1414&quot; data-start=&quot;1392&quot; data-ke-size=&quot;size16&quot;&gt;이 옵션들이 적용되면 테스트 환경에서는 다음과 같은 일이 벌어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1691&quot; data-start=&quot;1576&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1602&quot; data-start=&quot;1576&quot;&gt;마이크 permission 팝업이 뜨지 않고&lt;/li&gt;
&lt;li data-end=&quot;1654&quot; data-start=&quot;1603&quot;&gt;Chromium이 실제 마이크 대신 가짜 마이크 장치(fake mic)를 사용하며&lt;/li&gt;
&lt;li data-end=&quot;1691&quot; data-start=&quot;1655&quot;&gt;지정한 wav 파일을 &lt;b&gt;마이크 입력 스트림으로 흘려보낸다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 가장 중요했던 포인트는 이건 &amp;lsquo;녹음&amp;rsquo;이 아니라 &amp;lsquo;입력&amp;rsquo;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wav 파일을 그냥 재생하는 게 아니다. 브라우저 입장에서는 &lt;b&gt;진짜 입력 장치에서 소리가 들어오고 있다고 믿는 상태&lt;/b&gt;가 된다.&lt;br /&gt;그래서 STT(Speech To Text) 기능도 &quot;지금 누군가 말하고 있다&quot;고 인식하고 정상적으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Playwright 설정 (JavaScript / TypeScript 기준)&lt;/h3&gt;
&lt;pre id=&quot;code_1765618179613&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    browserName: 'chromium',
    launchOptions: {
      args: [
        '--use-fake-device-for-media-stream',
        '--use-fake-ui-for-media-stream',
        '--use-file-for-fake-audio-capture={wav 파일 경로}'
      ],
    },
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  시나리오에서는 이렇게 동작한다&lt;/h3&gt;
&lt;p data-end=&quot;2764&quot; data-start=&quot;2733&quot; data-ke-size=&quot;size16&quot;&gt;이 상태에서 테스트 시나리오에서 마이크 버튼을 클릭하면,&lt;/p&gt;
&lt;pre id=&quot;code_1765618379264&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await page.getByRole('button', { name: '마이크' }).click();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 마이크가 켜지는 대신, 미리 준비해 둔 wav 파일이 &lt;b&gt;마이크 입력으로 들어간다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2914&quot; data-start=&quot;2896&quot; data-ke-size=&quot;size16&quot;&gt;덕분에 내가 겪고 있던 문제였던, &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아무 소리가 없으면 다음 단계로 못 넘어가지 못하는 &lt;/span&gt;문제가 깔끔하게 해결됐다.&lt;br /&gt;사람이 직접 말하지 않아도, 시스템은 &lt;b&gt;음성이 입력되었다고 인식&lt;/b&gt;하고 다음 단계로 넘어간다.&lt;/p&gt;
&lt;p data-end=&quot;2914&quot; data-start=&quot;2896&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2914&quot; data-start=&quot;2896&quot; data-ke-size=&quot;size23&quot;&gt;  마무리하며&lt;/h3&gt;
&lt;p data-end=&quot;2914&quot; data-start=&quot;2896&quot; data-ke-size=&quot;size16&quot;&gt;마이크 입력이 필요한 화면은 자동화하기 어렵다고 막연히 생각했는데, 막상 구조를 이해하고 나니 &lt;b&gt;브라우저가 입력을 어떻게 인식하는지&lt;/b&gt;가 핵심이었다.&lt;/p&gt;
&lt;p data-end=&quot;294&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;다만 이 방식의 목적을 분명히 해둘 필요가 있다. 이 자동화는 &lt;b&gt;마이크 입력 기능 자체의 정확도를 검증하기 위한 용도는 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;393&quot; data-start=&quot;296&quot; data-ke-size=&quot;size16&quot;&gt;항상 동일한 wav 파일이 입력되기 때문에, 음성 인식의 품질이나 STT 결과의 정확성을 테스트하는 데에는 적합하지 않다.&lt;/p&gt;
&lt;p data-end=&quot;393&quot; data-start=&quot;296&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;393&quot; data-start=&quot;296&quot; data-ke-size=&quot;size16&quot;&gt;내가 이 방식을 사용한 이유는 &lt;b&gt;&quot;음성 입력이 있어야만 다음 단계로 넘어갈 수 있는 화면을 자동화하기 위해서&quot; 단 하나였다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;516&quot; data-start=&quot;447&quot; data-ke-size=&quot;size16&quot;&gt;즉, 마이크 기능을 테스트하기 위한 자동화가 아니라 &lt;b&gt;마이크 입력이라는 조건을 안정적으로 통과하기 위한 수단&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;592&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;592&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 접근하니 사람이 직접 말하지 않아도 되고, 회귀 테스트나 반복 테스트에서도 막히지 않는 자동화 흐름을 만들 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;676&quot; data-start=&quot;594&quot; data-ke-size=&quot;size16&quot;&gt;자동화에서 중요한 건 모든 기능을 완벽하게 검증하는 것보다, &lt;b&gt;어디까지를 자동화로 커버할지 명확히 정하는 것&lt;/b&gt;이라는 생각도 함께 들었다.&lt;/p&gt;
&lt;p data-end=&quot;2914&quot; data-start=&quot;2896&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/131</guid>
      <comments>https://isliife2.tistory.com/131#entry131comment</comments>
      <pubDate>Sat, 13 Dec 2025 18:48:20 +0900</pubDate>
    </item>
    <item>
      <title>  about:blank, 버그가 아니었다</title>
      <link>https://isliife2.tistory.com/130</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  QA 업무 중 접수된 CS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정말 오랜만에 QA 업무 중 인상 깊었던 CS를 하나 기록으로 남겨본다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 이슈는 기능 버그가 아니라, &lt;/span&gt;&lt;b&gt;&lt;span&gt;환경을 이해하지 못하면 절대 원인을 찾기 어려운 사례&lt;/span&gt;&lt;/b&gt;&lt;span&gt;였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;우리 서비스에는 &amp;lsquo;활동&amp;rsquo;을 생성하면, 해당 활동에 바로 참여할 수 있는 &lt;/span&gt;&lt;b&gt;&lt;span&gt;링크를 제공하는 기능&lt;/span&gt;&lt;/b&gt;&lt;span&gt;이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;선생님이 이 링크를 학생들에게 공유하면, 학생들은 링크를 클릭해 곧바로 활동을 시작하는 흐름이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 한 학교에서 다음과 같은 CS가 접수되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;569&quot; data-start=&quot;472&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;504&quot; data-start=&quot;472&quot;&gt;선생님이 구글 클래스룸을 통해 활동 링크를 공유했고&lt;/li&gt;
&lt;li data-end=&quot;524&quot; data-start=&quot;505&quot;&gt;학생이 해당 링크를 클릭하면&lt;/li&gt;
&lt;li data-end=&quot;540&quot; data-start=&quot;525&quot;&gt;활동은 시작되지 않고 흰색 화면에서 계속 로딩만 된다는 내용이었다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;조금 더 확인해 보니, 주소창에는 &lt;/span&gt;&lt;span&gt;about:blank&lt;/span&gt;&lt;span&gt;가 표시되어 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 결정적인 조건 하나가 더 있었다. &lt;/span&gt;&lt;span&gt;이 현상은 &lt;/span&gt;&lt;b&gt;&lt;span&gt;학생에게 제공된 크롬북에서만 발생&lt;/span&gt;&lt;/b&gt;&lt;span&gt;하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음엔 당연히 서비스 기능 문제라고 생각했다. 이에 &lt;/span&gt;&lt;span&gt;직접 재현을 시도해 봤지만, 개인 PC나 노트북, 모바일 환경에서는 모두 정상 동작했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이때부터 &amp;lsquo;아, 이건 단순한 기능 버그는 아닐 수도 있겠다&amp;rsquo;는 생각이 들기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  about:blank에 주목하게 된 이유&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용을 개발자분께 공유했고, 개발자분은 기능 흐름보다 &lt;b&gt;URL 자체&lt;/b&gt;를 먼저 확인하셨다.&lt;/p&gt;
&lt;p data-end=&quot;983&quot; data-start=&quot;917&quot; data-ke-size=&quot;size16&quot;&gt;문제가 된 URL은 우리가 직접 제공하는 웹 페이지가 아니라, &lt;b&gt;Branch에서 제공하는 딥링크 URL&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;1044&quot; data-start=&quot;985&quot; data-ke-size=&quot;size16&quot;&gt;그리고 특히 짚어주신 포인트는 &amp;lsquo;흰 화면&amp;rsquo;이 아니라, 주소창에 노출된 &lt;b&gt;about:blank&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;about:blank&lt;/span&gt;&lt;span&gt;는 서버에서 내려주는 에러 페이지가 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;브라우저가 &lt;/span&gt;&lt;b&gt;&lt;span&gt;다음 페이지로 이동하려 했지만, 그 페이지를 정상적으로 열지 못했을 때&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 임시로 표시하는 기본 페이지다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, 이 상태는 다음을 의미했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;사용자의 요청은 발생했지만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;최종 목적지 페이지를 받지 못했고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;브라우저가 대체 화면으로 &lt;/span&gt;&lt;span&gt;about:blank&lt;/span&gt;&lt;span&gt;를 띄운 상태&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 순간부터 프론트엔드 렌더링 오류보다는, &lt;/span&gt;&lt;b&gt;&lt;span&gt;리다이렉션 또는 네트워크 단계에서 무언가 막혔을 가능성&lt;/span&gt;&lt;/b&gt;&lt;span&gt;이 더 크게 보이기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  Branch 딥링크는 어떻게 동작하는가&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Branch 딥링크는 구조적으로 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Branch 서버가 중간에서 요청을 받아 리다이렉션하는 방식&lt;/span&gt;&lt;/b&gt;&lt;span&gt;으로 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자가 딥링크를 클릭하면:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1474&quot; data-start=&quot;1444&quot;&gt;app.link 도메인으로 요청이 전달되고&lt;/li&gt;
&lt;li data-end=&quot;1500&quot; data-start=&quot;1475&quot;&gt;Branch 서버가 요청을 수신한 뒤&lt;/li&gt;
&lt;li data-end=&quot;1531&quot; data-start=&quot;1501&quot;&gt;OS, 디바이스, 앱 설치 여부 등을 분석하고&lt;/li&gt;
&lt;li data-end=&quot;1559&quot; data-start=&quot;1532&quot;&gt;조건에 따라 최종 목적지로 리다이렉션한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 중간 단계에서 외부 서버를 거쳐야 하는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  문제의 원인 &amp;ndash; 크롬북 + 학교 네트워크 환경&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 퍼즐이 맞춰지기 시작했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1766&quot; data-start=&quot;1650&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1680&quot; data-start=&quot;1650&quot;&gt;학생 기기는 학교에서 관리하는 &lt;b&gt;크롬북&lt;/b&gt;이고&lt;/li&gt;
&lt;li data-end=&quot;1711&quot; data-start=&quot;1681&quot;&gt;크롬북에는 관리자 정책이 강하게 적용되어 있으며&lt;/li&gt;
&lt;li data-end=&quot;1766&quot; data-start=&quot;1712&quot;&gt;외부 리다이렉션 도메인이나 트래킹 성격의 URL이 보안 정책에 의해 차단될 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1862&quot; data-start=&quot;1768&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 Branch 딥링크 요청이 &lt;b&gt;리다이렉션 단계에서 차단되었고&lt;/b&gt;, 브라우저는 다음 페이지를 받지 못한 채 about:blank 상태로 멈춰 있었던 것이다.&lt;/p&gt;
&lt;p data-end=&quot;1862&quot; data-start=&quot;1768&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, &lt;/span&gt;&lt;span&gt;서비스 기능 자체의 버그가 아니라 &lt;/span&gt;&lt;span&gt;&lt;b&gt;학교 네트워크 정책에 의한 접근 제한 이슈&lt;/b&gt;였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1948&quot; data-start=&quot;1933&quot; data-ke-size=&quot;size23&quot;&gt;  QA로서 느낀 점&lt;/h3&gt;
&lt;p data-end=&quot;1970&quot; data-start=&quot;1950&quot; data-ke-size=&quot;size16&quot;&gt;이번 CS를 통해 다시 한번 느꼈다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2085&quot; data-start=&quot;1972&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1993&quot; data-start=&quot;1972&quot;&gt;모든 이슈가 코드 문제는 아니다&lt;/li&gt;
&lt;li data-end=&quot;2047&quot; data-start=&quot;1994&quot;&gt;about:blank는 &amp;lsquo;아무것도 없는 화면&amp;rsquo;이 아니라 &lt;b&gt;중요한 힌트&lt;/b&gt;가 될 수 있다&lt;/li&gt;
&lt;li data-end=&quot;2085&quot; data-start=&quot;2048&quot;&gt;QA는 기능뿐 아니라 &lt;b&gt;환경과 맥락까지 함께 봐야 한다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2132&quot; data-start=&quot;2087&quot; data-ke-size=&quot;size16&quot;&gt;만약 주소창을 유심히 보지 않았다면, 이 이슈는 훨씬 오래 헤맸을지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  이 가설이 충분히 설득력 있다고 판단한 이유&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원인이 단순한 추측이 아니라, QA 관점에서 충분히 말할 수 있다고 판단한 근거는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2435&quot; data-start=&quot;2230&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2264&quot; data-start=&quot;2230&quot;&gt;동일한 링크가 개인 PC, 모바일 환경에서는 정상 동작&lt;/li&gt;
&lt;li data-end=&quot;2282&quot; data-start=&quot;2265&quot;&gt;학생용 크롬북에서만 재현&lt;/li&gt;
&lt;li data-end=&quot;2321&quot; data-start=&quot;2283&quot;&gt;주소창에 명확한 에러 페이지가 아닌 about:blank 노출&lt;/li&gt;
&lt;li data-end=&quot;2349&quot; data-start=&quot;2322&quot;&gt;문제 발생 시점이 페이지 렌더링 이전 단계&lt;/li&gt;
&lt;li data-end=&quot;2399&quot; data-start=&quot;2350&quot;&gt;Branch 딥링크(app.link) 특성상 외부 리다이렉션 및 트래킹을 포함&lt;/li&gt;
&lt;li data-end=&quot;2435&quot; data-start=&quot;2400&quot;&gt;학교 네트워크/관리자 정책에서 차단되기 쉬운 URL 유형&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 이 이슈는 서비스 내부 로직보다는, &lt;/span&gt;&lt;b&gt;&lt;span&gt;학교에서 관리하는 크롬북 + 네트워크 정책 환경에서 딥링크 리다이렉션이 차단되었을 가능성이 매우 높다&lt;/span&gt;&lt;/b&gt;&lt;span&gt;고 판단했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Quality Assurance/  Experience &amp;amp; Insight</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/130</guid>
      <comments>https://isliife2.tistory.com/130#entry130comment</comments>
      <pubDate>Sat, 13 Dec 2025 18:14:02 +0900</pubDate>
    </item>
    <item>
      <title>Playwright 구조 뜯어보기</title>
      <link>https://isliife2.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 릴리즈가 끝나면 다음 릴리즈 준비까지 잠시 생긴 틈새 시간에, 회사 서비스 자동화 프로젝트를 진행하고 있다. 회사 스터디에서는 JavaScript를 공부하고, 개인적으로는 TypeScript와 Playwright를 함께 파고들며 &amp;lsquo;자동화 프로세스&amp;rsquo;라는 완전 새로운 영역을 혼자서 부딪히며 구축하고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;QA Manager로서 '테스트 자동화 구축'이라는 큰 과제를 받았지만, 실제 서비스 기반에서의 경험은 거의 전무했다. 그래서 모든 것이 새로웠고, 그래서 더 궁금했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;본인 성격상 항상 '왜?'라는 질문을 끊임없이 던지기에, Playwright 구조를 봤을 때도 마찬가지였다. '이게 왜 이렇게 동작하지?', '왜 이런 문법을 사용하지?', '저 객체는 어디서 생성되는 거지?' 라는 질문들을 끊임없이 던지며 파고들었다. 파고들다 보니 자연스럽게 타입스크립트의 문법도 알게 되었고, 올해 초 부트캠프에서 배웠던 Pytest + Selenium과의 차이점도 자연스럽게 이해할 수 있었다. 이에 대한 부분은 다음 포스팅을 통해 자세히 다룰 예정이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 오늘 글은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Playwright의 구조를 하나씩 뜯어보며 &amp;lsquo;왜 이런 구조를 쓰는지&amp;rsquo; 이해한 과정&lt;/b&gt;을 정리해보려 한다. 누군가는 &quot;이게 왜 궁금하지?&quot;라고 생각할 수도 있지만, 나에게는 정말 궁금했고 파고드는 과정 자체가 큰 도움이 되었다. 표면적인 사용법만 아는 것과, 내부 구조까지 이해하고 사용하는 것의 차이는 실제로 엄청 컸기 때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  시작: 자동 생성된 예제 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1763991084912&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { test, expect } from '@playwright/test';

test('has title', async ({ page }) =&amp;gt; {
  await page.goto('https://playwright.dev/');

  // Expect a title &quot;to contain&quot; a substring.
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) =&amp;gt; {
  await page.goto('https://playwright.dev/');

  // Click the get started link.
  await page.getByRole('link', { name: 'Get started' }).click();

  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Playwright 프로젝트를 생성하면 자동으로 제공되는 tests/example.spec.ts 파일은 위와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 이 코드를 봤을 때 나는 다음과 같은 질문들이 떠올랐다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;1582&quot; data-end=&quot;1618&quot;&gt;@playwright/test는 도대체 뭐지?&lt;/li&gt;
&lt;li data-start=&quot;1619&quot; data-end=&quot;1664&quot;&gt;test() 함수는 어떤 구조를 갖는 거지?&lt;/li&gt;
&lt;li data-start=&quot;1665&quot; data-end=&quot;1723&quot;&gt;page는 뭐고, 왜 함수 인자에서 { page }처럼 구조 분해 할당을 쓰는 거지?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문들을 하나씩 따라가 보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  @playwright/test&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Playwright는 크게 2가지 레이어로 이루어져 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-start=&quot;753&quot; data-end=&quot;920&quot;&gt;
&lt;li data-start=&quot;753&quot; data-end=&quot;812&quot;&gt;&lt;b&gt;Core API&lt;br /&gt;&lt;/b&gt;-&amp;gt; Browser / Page / Locator 같은 '자동화 엔진'&lt;/li&gt;
&lt;li data-start=&quot;813&quot; data-end=&quot;920&quot;&gt;&lt;b&gt;Test Runner (@playwright/test)&lt;br /&gt;&lt;/b&gt;-&amp;gt; test(), expect(), fixtures, 병렬 실행 등 테스트 러너 기능&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;922&quot; data-end=&quot;934&quot;&gt;우리가 보통 사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;'@playwright/test'는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Core API가 아닌&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Playwright가 자체 제공하는 테스트 러너 패키지&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1976&quot; data-end=&quot;2120&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1976&quot; data-end=&quot;2120&quot;&gt;여기에는 다음 기능들이 함께 포함된다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2035&quot; data-start=&quot;1915&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1926&quot; data-start=&quot;1915&quot;&gt;테스트 실행기&lt;/li&gt;
&lt;li data-end=&quot;1953&quot; data-start=&quot;1927&quot;&gt;test / expect 같은 전역 함수&lt;/li&gt;
&lt;li data-end=&quot;1986&quot; data-start=&quot;1954&quot;&gt;테스트마다 독립적인 BrowserContext 관리&lt;/li&gt;
&lt;li data-end=&quot;2009&quot; data-start=&quot;1987&quot;&gt;확장 가능한 Fixture 시스템&lt;/li&gt;
&lt;li data-end=&quot;2035&quot; data-start=&quot;2010&quot;&gt;병렬 실행, 프로젝트 구조, 셰도잉 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2095&quot; data-start=&quot;2037&quot; data-ke-size=&quot;size16&quot;&gt;즉, 'Playwright를 테스트 프레임워크처럼 사용하고 싶다면 반드시 필요한 패키지'가 바로 이에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  test() 함수와 구조 분해 할당&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉보기에는 단순한 함수처럼 보이지만, test()는 사실 여러 타입과 기능이 조합된 복합적인 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 러너의 핵심 기능 대부분이 이 test 객체를 중심으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 따라가 보면, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1763991206231&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const test: TestType = (
  title: string,
  body: TestBody&amp;lt;
    PlaywrightTestArgs &amp;amp;
    PlaywrightTestOptions &amp;amp;
    PlaywrightWorkerArgs &amp;amp;
    PlaywrightWorkerOptions
  &amp;gt;
) =&amp;gt; void&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Playwright의 test 함수는 단일 타입만 넘기지 않고 다음 네 가지 타입을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;전부 합친 하나의 객체&lt;/b&gt;를 넘긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2667&quot; data-start=&quot;2467&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2530&quot; data-start=&quot;2467&quot;&gt;&lt;b&gt;PlaywrightTestArgs&lt;/b&gt;: 테스트마다 새로 만들어지는 자원 (page, context 등)&lt;/li&gt;
&lt;li data-end=&quot;2584&quot; data-start=&quot;2531&quot;&gt;&lt;b&gt;PlaywrightWorkerArgs&lt;/b&gt;: 같은 워커에 속한 테스트들이 공유하는 자원&lt;/li&gt;
&lt;li data-end=&quot;2625&quot; data-start=&quot;2585&quot;&gt;&lt;b&gt;PlaywrightTestOptions&lt;/b&gt;: 테스트 단위 설정&lt;/li&gt;
&lt;li data-end=&quot;2667&quot; data-start=&quot;2626&quot;&gt;&lt;b&gt;PlaywrightWorkerOptions&lt;/b&gt;: 워커 단위 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 네 개의 타입이 합쳐져 최종적으로 하나의 인자 객체(args)가 만들어진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 또 PlaywrightTestArgs의 정의를 따라가 보면:&lt;/p&gt;
&lt;pre id=&quot;code_1763991393550&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface PlaywrightTestArgs {
  page: Page;
  context: BrowserContext;
  request: APIRequestContext;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정의를 보면 &lt;b&gt;page가 단순한 값이 아니라 Playwright가 미리 생성해 전달해주는 Page 객체&lt;/b&gt;라는 점이 명확해진다.&lt;br /&gt;그리고 page뿐만 아니라 context, request 역시 모두 Playwright가 테스트 실행 전에 자동으로 준비해주는 &lt;b&gt;기본 fixture&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, test()는 겉으로 보기에는 하나의 인자(args)만 받지만, 실제로는 다음과 같은 '큰 객체 하나'를 테스트 함수로 전달한다.&lt;/p&gt;
&lt;pre id=&quot;code_1763994160524&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  page: Page,
  context: BrowserContext,
  request: APIRequestContext,
  ...기타 Playwright 기본 fixture들,
  ...내가 extend()로 추가한 custom fixture들
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;args 내부에 수많은 값이 들어 있기 때문에&lt;/b&gt;, 테스트 코드에서는 필요한 값만 골라 쓰기 위해 다음과 같이 구조 분해 할당을 사용하는 것이 기본 패턴이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구조 분해를 사용하지 않으면 모든 fixture에 접근할 때마다 다음처럼 작성해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1763991524519&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test('example', async (args) =&amp;gt; {
  await args.page.goto('/');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;즉, Playwright가 구조 분해 할당을 기본 사용 방식으로 안내하는 이유는 &lt;b&gt;테스트 함수에 전달되는 인자 객체가 매우 크고 복합적이기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;필요한 fixture만 간단하게 꺼내 쓸 수 있도록 구조 분해가 자연스럽게 최적의 선택이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;  더 나아가: Custom Fixture 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1763991612154&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';

// Extend basic test by providing a &quot;todoPage&quot; fixture.
const test = base.extend&amp;lt;{ todoPage: TodoPage }&amp;gt;({
  todoPage: async ({ page }, use) =&amp;gt; {
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await todoPage.addToDo('item1');
    await todoPage.addToDo('item2');
    await use(todoPage);
    await todoPage.removeAll();
  },
});

test('should add an item', async ({ todoPage }) =&amp;gt; {
  await todoPage.addToDo('my item');
  // ...
});

test('should remove an item', async ({ todoPage }) =&amp;gt; {
  await todoPage.remove('item1');
  // ...
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드를 처음 봤을 때, 또 다시 다음과 같은 질문들이 떠올랐다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3787&quot; data-start=&quot;3668&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3686&quot; data-start=&quot;3668&quot;&gt;as는 왜 쓰는 걸까?&lt;/li&gt;
&lt;li data-end=&quot;3707&quot; data-start=&quot;3687&quot;&gt;extend는 무슨 역할?&lt;/li&gt;
&lt;li data-end=&quot;3728&quot; data-start=&quot;3708&quot;&gt;&amp;lt;{ ... }&amp;gt; 구조는 뭐지?&lt;/li&gt;
&lt;li data-start=&quot;3708&quot; data-end=&quot;3728&quot;&gt;({ ... }) 구조는 뭐지?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3812&quot; data-start=&quot;3789&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;다시 해당 질문들을 하나씩 따라가 보기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;  as: 기본 test 이름 변경&lt;/h3&gt;
&lt;pre id=&quot;code_1763991690138&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { test as base } from '@playwright/test';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;as는 단순히 &lt;b&gt;import한 식별자의 이름을 바꾸는 문법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;4028&quot; data-start=&quot;3948&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4028&quot; data-start=&quot;3948&quot; data-ke-size=&quot;size16&quot;&gt;기본 test를 확장해서 새로운 test 객체를 만들 것이기 때문에, 원본과 구분하기 위해 base라는 이름으로 바꿔서 가져오는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4096&quot; data-start=&quot;4030&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4067&quot; data-start=&quot;4030&quot;&gt;base: Playwright가 제공하는 기본 test&lt;/li&gt;
&lt;li data-end=&quot;4096&quot; data-start=&quot;4068&quot;&gt;test: 내가 확장한 커스텀 test&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4122&quot; data-start=&quot;4098&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 이름을 나누지 않으면 혼란이 생긴다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;  Playwright에서 extend()는 어떤 역할을 하는가?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;Playwright는 &lt;b&gt;fixture 기반 테스트 프레임워크&lt;/b&gt;다. test()가 넘겨주는 page, context 역시 기본 fixture다.&lt;/p&gt;
&lt;p data-end=&quot;4328&quot; data-start=&quot;4262&quot; data-ke-size=&quot;size16&quot;&gt;extend()는 여기에 &lt;b&gt;새로운 fixture를 추가하거나 기존 fixture를 재정의&lt;/b&gt;할 수 있는 기능이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763991764940&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const test = base.extend&amp;lt;{ todoPage: TodoPage }&amp;gt;({ ... });&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;911&quot; data-end=&quot;970&quot;&gt;이렇게 작성하면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4484&quot; data-start=&quot;4411&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4435&quot; data-start=&quot;4411&quot;&gt;기존 test 기능은 그대로 유지하고&lt;/li&gt;
&lt;li data-end=&quot;4484&quot; data-start=&quot;4436&quot;&gt;todoPage라는 새 fixture를 테스트 함수에서 사용할 수 있게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4529&quot; data-start=&quot;4486&quot; data-ke-size=&quot;size16&quot;&gt;즉, 테스트 실행 환경을 커스터마이징하는 Playwright의 정식 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;4529&quot; data-start=&quot;4486&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;  왜 &amp;lt;{ todoPage: TodoPage }&amp;gt;처럼 제네릭을 쓰는가?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2389&quot; data-end=&quot;2451&quot;&gt;여기서 &amp;lt;{ todoPage: TodoPage }&amp;gt;는 TypeScript 제네릭이다.&lt;br /&gt;이 제네릭은 '내가 새로 추가할 fixture가 어떤 타입을 가지는지'를 Playwright에게 알려준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;877&quot; data-start=&quot;801&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;831&quot; data-start=&quot;801&quot;&gt;key: fixture 이름 (todoPage)&lt;/li&gt;
&lt;li data-end=&quot;877&quot; data-start=&quot;832&quot;&gt;value: 해당 fixture가 어떤 타입인지 (TodoPage 클래스)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;이 타입 정보를 넘겨두어야 테스트 함수 내부에서 자동 완성 및 타입 추론이 정상적으로 동작한다.&lt;/p&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;295&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;그리고 내가 새로 추가할 fixture는 &lt;b&gt;한 개일 수도 있고, 여러 개일 수도 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;295&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;Playwright는 이런 다양한 경우를 모두 지원하기 위해, &lt;b&gt;&amp;lsquo;fixture 이름: fixture 타입&amp;rsquo;들을 하나의 객체 타입으로 묶어서 전달하는 방식&lt;/b&gt;을 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;327&quot; data-start=&quot;297&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;327&quot; data-start=&quot;297&quot; data-ke-size=&quot;size16&quot;&gt;만약 fixture가 여러 개라면 자연스럽게 아래처럼 확장된다.&lt;/p&gt;
&lt;pre id=&quot;code_1763996069408&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extend&amp;lt;{
  todoPage: TodoPage;
  userPage: UserPage;
  apiClient: ApiClient;
}&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;415&quot; data-ke-size=&quot;size16&quot;&gt;결국 제네릭에 {}를 쓰는 이유는 &lt;b&gt;단일 fixture든 여러 fixture든 동일한 객체 구조로 확장 가능하도록 하기 위해서&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;914&quot; data-start=&quot;879&quot; data-ke-size=&quot;size23&quot;&gt;  extend() 안의 {}는?&lt;/h3&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;extend()에 전달하는 객체는 &lt;b&gt;'추가하거나 재정의하고 싶은 fixture 목록'을 선언하는 영역&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1763993206405&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const test = base.extend&amp;lt;{ todoPage: TodoPage }&amp;gt;({
  todoPage: async ({ page }, use) =&amp;gt; {
    // ...
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 다음 두 가지다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;key: fixture 이름&lt;/li&gt;
&lt;li&gt;value: 해당 fixture가 호출될 때 실행할 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1285&quot; data-start=&quot;1242&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1285&quot; data-start=&quot;1242&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 이런 순서로 동작한다:&lt;/p&gt;
&lt;pre id=&quot;code_1763996756344&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const test = base.extend&amp;lt;{ todoPage: TodoPage }&amp;gt;({
  // 1) fixture 이름: fixture 생성/초기화/정리까지 담당하는 비동기 함수
  todoPage: async ({ page }, use) =&amp;gt; {

    // 2) Playwright가 미리 만들어둔 기본 fixture(page 등)를 받아
    //    우리가 원하는 fixture를 생성한다.
    const todoPage = new TodoPage(page);

    // 3) 테스트 실행 전에 필요한 초기화 작업을 수행한다.
    await todoPage.goto();

    // 4) use()를 호출해 fixture를 테스트에 전달한다.
    //    &amp;rarr; test('...', async ({ todoPage }) =&amp;gt; { ... }) 에서 접근 가능해짐
    await use(todoPage);

    // 5) 테스트 완료 후 cleanup 로직을 실행한다.
    await todoPage.removeAll();
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Additional/  Playwright</category>
      <author>YeonSu02</author>
      <guid isPermaLink="true">https://isliife2.tistory.com/129</guid>
      <comments>https://isliife2.tistory.com/129#entry129comment</comments>
      <pubDate>Mon, 24 Nov 2025 23:34:37 +0900</pubDate>
    </item>
  </channel>
</rss>