Skip to main content
Glama
kay8244

playwright-mcp

by kay8244

playwright-mcp — LLM이 브라우저를 스스로 운전하는 Playwright MCP

Claude Code가 브라우저를 스스로 운전(navigate·키인·클릭·추출)하게 해주는 Playwright MCP 패키지. 의존성은 @playwright/mcp 하나뿐.

작업을 시작하면 둘 중 하나를 반드시 고르게 되어 있다 → 모드 강제

  • 경로 지정 — 사람이 한 번 시연해 저장한 경로를 참고해 수행 (복잡한 사내 화면·반복 작업)

  • LLM 자율 — 스냅샷을 읽고 LLM이 알아서 조작 (처음 보는 페이지·일회성)

사내 LLM이 복잡한 화면에서 요소를 잘 못 고른다면, 사람이 경로를 정해주고 LLM은 그걸 따라가게 하는 쪽이 훨씬 안정적이다. 그래서 자율 모드를 "선택지 중 하나"로 내려놓았다.

이 가이드는 WSL / 리눅스 환경 기준이다. (내부 Claude Code가 WSL 안에서 돌아감) 명령은 전부 bash 문법 — PowerShell/cmd 문법($env:..., set ...)은 WSL에서 command not found 난다. 브라우저도 리눅스 크로미움이 필요하다. 설치·실행 전부 WSL 안에서 한다.

🔄 업데이트는 npm run update 하나로

cd playwright-mcp     # ← 레포 폴더 안에서 실행해야 한다
npm run update

상위 폴더에서 돌리면 npm ERR! enoent Could not read package.json 이 난다. 폴더를 잘못 잡은 것이지 고장이 아니다. git pull 만으로는 부족하다. 전역 등록(~/.claude.json)은 pull 로 바뀌지 않아서, 옛 경로로 계속 붙은 채 겉보기엔 멀쩡한데 새 기능만 빠진 상태가 된다 — 제일 알아채기 어려운 실패다.

npm run update 가 pull → install → 전역 등록 갱신 → 검증까지 한 번에 한다. 전역 등록을 안 쓰면 그 단계는 알아서 건너뛴다. 끝나면 claude 를 껐다 켜야 새 설정이 붙는다.

전제

  • Node.js 18+ (WSL 안에) — 확인: node --version

  • Claude Code (WSL 안에서 구동)


Related MCP server: Playwright MCP

A. 설치 (WSL에서 npm/CDN 접속 가능할 때) — 한 줄이면 끝

git clone https://github.com/kay8244/playwright-mcp.git
cd playwright-mcp
npm run setup     # npm install + 리눅스 크로미움 + 자체검증 2종까지 한 방
claude            # 초록불 뜨면 실행 → playwright MCP approve → 자연어로 조작

아래 초록불 두 묶음이 다 뜨면 준비 완료. 앞쪽은 "브라우저를 조작할 수 있는가", 뒤쪽은 "모드 게이트가 실제로 막는가" 다.

=== [3/4] 브라우저 조작 검증 (verify.mjs) ===
✅ MCP 기동 — Playwright 1.62.0-alpha-...
✅ 툴 노출 — 24 개
✅ navigate — 로컬 페이지 열림 (네트워크 0, node_modules 크로미움 151.0.7922.10)
✅ type + click — 키인/클릭 동작
✅ 결과추출 — "나이키" 매칭 5 건
🎉 오프라인 검증 통과.

=== [4/4] 모드 게이트 검증 (verify-route.mjs) ===
✅ 래퍼 기동 — playwright (route)
✅ 툴 병합 — 게이트 4개 + 자식 23 개 (run_code_unsafe 회수됨)
✅ 하드 차단 — 모드 미선택 시 browser_* 거부, 복구 방법 안내됨
✅ 사용자 확인 강제 — confirmedByUser:false 거부
✅ 자율 모드 — 잠금해제 후 브라우저 조작 통과
✅ 잔소리 방지 — 같은 사이트 안 이동은 재질문 없음
✅ 작업 단위 재무장 — route_done 후 다시 차단, 사유 안내됨
✅ 경로 모드 — 저장된 경로를 읽어 단계·셀렉터를 LLM 에게 전달
🎉 게이트 검증 통과.

🎉 준비 완료.

🩺 "내 PC엔 이미 전역이 켜져 있는데, 이 폴더가 진짜 쓰이고 있나?" — npm run doctor

npm run doctor

전역 등록은 절대경로라서, 다른 폴더를 가리키고 있으면 방금 받은 이 폴더는 claude 에서 아무 상관 없는 상태가 된다. 에러가 안 나기 때문에 여기서 뭘 고쳐도 반영이 안 되는 걸 한참 모른다. doctor 가 그걸 짚어준다.

=== 전역 등록 (~/.claude.json) ===
   가리키는 대상: /home/user/old-playwright-mcp/node_modules/@playwright/mcp/cli.js
❌ 전역 등록이 **다른 폴더**를 가리킵니다 → /home/user/old-playwright-mcp
   즉, 지금 이 폴더는 claude 에서 쓰이지 않습니다. 여기 무엇을 바꿔도 반영되지 않습니다.

=== 결론 — claude 를 어디서 켜면 무엇이 뜨나 ===
⚠️  딴 폴더에서 켜면 다른 폴더의 것이 뜨고, 이 폴더에서 켜면 중복돼 failed 가 날 수 있습니다.
   ① 전역을 이 폴더로 옮긴다 →  npm run install-global   (권장)
   ② 전역을 끄고 이 폴더에서만 쓴다 →  npm run uninstall-global

최종 확인은 claude 안에서: "route_status 호출해줘" 라고 하면 지금 서비스 중인 폴더의 절대경로를 알려준다. 그게 방금 pull 받은 폴더면 확실한 것이다.

현재 모드: (미선택). route_mode 를 먼저 호출해야 브라우저를 쓸 수 있습니다.
서비스 중인 폴더: /home/user/playwright-mcp     ← 이게 맞는지 보면 된다
자율 모드 허용: 예

언제 무엇을 실행하나 — setup 은 매번 하는 게 아니다

상황

실행할 것

걸리는 시간

처음 설치할 때

npm run setup

최초 1회(크로미움 내려받음)

매일 쓸 때

아무것도 안 해도 된다 — claude

새 버전 받을 때

npm run update (pull·설치·전역등록·검증 다 함)

10초

폴더를 옮겼거나 이름을 바꿨을 때

npm run install-global

즉시

뭔가 이상할 때 / 어느 폴더가 쓰이는지 모를 때

npm run doctor

즉시

동작 확인

npm run verify + npm run verify-route

4초

npm run setup몇 번을 돌려도 안전하다(멱등). 이미 설치돼 있으면 1·2단계는 그냥 통과하고 검증만 다시 돈다 — 전부 합쳐 7초. 폐쇄망에서도 이미 받아둔 상태라면 네트워크 없이 통과한다. 그래도 평소에는 굳이 돌릴 필요가 없고, 확인만 하고 싶으면 verify 두 개면 충분하다.

크로미움은 받았는데 실행이 안 되면(리눅스 구동 라이브러리 부족):

sudo npx playwright install-deps chromium
#   또는 한 번에:  sudo npx playwright install --with-deps chromium
npm install
PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium
node verify.mjs         # 브라우저 조작
node verify-route.mjs   # 모드 게이트
claude

B. 폐쇄망 오프라인 (WSL에서 npm/CDN 막힘) — 폴더째 반입

브라우저 바이너리는 OS 종속이라, 인터넷 되는 WSL(같은 리눅스 x64) PC에서 폴더째 만들어 반입한다. npm run setupPLAYWRIGHT_BROWSERS_PATH=0 으로 크로미움을 node_modules에 넣으므로, 이 폴더를 통째로 압축해 옮기면 별도 패커 없이 자체완결 번들이 된다.

# [인터넷 되는 WSL PC]
git clone https://github.com/kay8244/playwright-mcp.git
cd playwright-mcp
npm run setup                       # node_modules 안에 크로미움까지 설치됨
cd ..
tar czf playwright-mcp-offline.tgz --exclude=.git playwright-mcp
#   → 이 .tgz 를 USB / 사내 저장소로 반입

# [폐쇄망 WSL PC]  압축 풀고
tar xzf playwright-mcp-offline.tgz
cd playwright-mcp
npm run verify                      # ① 브라우저 조작 (네트워크 0)
npm run verify-route                # ② 모드 게이트
claude

둘 다 🎉 가 떠야 한다. ①만 통과하고 ②가 실패하면 브라우저는 되는데 게이트가 안 붙은 상태다. ⚠ 준비 PC와 폐쇄망 PC의 OS/CPU가 같아야 한다(리눅스 x64 등). 다른 OS에서 받은 크로미움은 못 쓴다. 폐쇄망 PC(WSL)에 Node 가 없으면 먼저 설치해 둔다.

C. 어느 레포에서든 쓰기 — 전역(user 스코프) 등록

기본 설치(A)는 이 폴더 안에서 claude 를 켤 때만 붙는다. 한 번만 전역 등록하면 어느 레포에서든 playwright MCP가 붙는다.

한 줄로 — npm run install-global (권장)

cd playwright-mcp
npm run setup            # (아직이면) 초록불까지
npm run install-global   # ~/.claude.json 최상위 mcpServers 에 playwright 를 "안전하게" 추가

이 스크립트는 ~/.claude.json읽어서 파싱→추가→다시 저장하므로 손으로 JSON 편집하다 깨질 일이 없다. 이 폴더의 route-mcp.mjs 절대경로도 자동으로 넣는다(래퍼가 내부에서 @playwright/mcp 를 띄운다).

  • 해제: npm run uninstall-global

  • 🖥 브라우저 창을 보고 싶으면(headed): 설치 시점의 DISPLAY 를 감지해 자동으로 넣는다(WSL은 echo $DISPLAY:0 등). 그러면 크롬 창이 화면에 뜬다. DISPLAY 가 없으면(헤드리스 서버) 안 뜨는 게 정상. WSL에서 안 뜨면 wsl --shutdown 후 재접속해 DISPLAY 살린 뒤 npm run install-global 다시.

  • JSON은 최상위 mcpServers 에만 넣는다. ~/.claude.jsonprojects.<경로>.mcpServers 는 프로젝트별이라 전역 아님(이 스크립트는 최상위에 넣어줌).

등록 후 확인 — ⚠ 반드시 "딴 폴더"에서

cd ~            # playwright-mcp 폴더가 아닌 아무 폴더
claude
/mcp            # playwright 가 connected 면 전역 성공 🎉  (approve 뜨면 승인)

그다음 예: "https://example.com 열어서 상품 목록 크롤링해서 표로 정리해줘" → LLM이 브라우저를 스스로 운전한다.

왜 딴 폴더? playwright-mcp 폴더엔 자체 .mcp.json 이 있어서, 그 안에서 켜면 전역 것과 중복→failed 난다. (아래 "중복 주의")

⚠️ playwright 는 "한 곳에만" 등록 (중복 = failed)

playwright두 군데 이상(예: 폴더 .mcp.json + ~/.claude.json)에 정의하면 충돌해 /mcpfailed 로 뜬다. failed 뜨면:

  1. 중복부터 의심 — 한 곳만 남기고 지운다. (제일 흔한 원인)

  2. 그래도면 서버가 못 뜨는 것 → 그 폴더에서 npm run verify 로 원인(브라우저 미설치 등) 확인.

CLI (일반 환경) — 사내 wrapper 환경에선 sh: Syntax error 로 막힐 수 있음:

claude mcp add playwright --scope user -e PLAYWRIGHT_BROWSERS_PATH=0 -- node "$PWD/route-mcp.mjs" --isolated --allow-unrestricted-file-access --browser chromium

레포별 .mcp.json — 전역이 계속 막히면, 쓰려는 레포 루트에 이 파일 하나만 두기(절대경로):

{
  "mcpServers": {
    "playwright": {
      "command": "node",
      "args": [
        "/home/사용자/playwright-mcp/route-mcp.mjs",
        "--isolated",
        "--allow-unrestricted-file-access",
        "--browser",
        "chromium"
      ],
      "env": { "PLAYWRIGHT_BROWSERS_PATH": "0" }
    }
  }
}

경로는 실제 절대경로로 교체. 한 곳에만~/.claude.json 에도 동시에 넣으면 중복 충돌. ⚠ route-mcp.mjs 가 아니라 node_modules/@playwright/mcp/cli.js 를 직접 가리키면 모드 게이트 없이 붙는다(예전 동작).

  • ⚠ 이 폴더를 지우거나 옮기지 말 것 — 브라우저가 그 안 node_modules 에 있고 등록이 그 절대경로를 가리킨다. 옮겼으면 npm run install-global 다시.

  • 💡 사내 배포판(wrapper)에서 claude 실행 때마다 뜨는 sh: Syntax error 배너는 대개 노이즈다 — claude 채팅 화면이 열리고 /mcp 가 뜨면 정상 동작하는 것.


설치 후 동작 확인 (approve 후 자연어로)

실제 대상은 사내 시스템이지만, 붙었는지 확인만 하려면 동봉된 검증용 페이지로 한 번 돌려본다.

page-agent-shop.html 열어서 검색창에 아무거나 입력하고 결과를 표로 정리해줘

→ LLM이 browser_navigate / browser_snapshot / browser_type / browser_click 을 알아서 호출하면 정상. 그다음부터는 사내 URL을 그대로 시키면 된다.

모드 강제 — 경로 지정 vs LLM 자율

브라우저를 만지기 전에 둘 중 하나를 반드시 고르게 되어 있다. 프롬프트 규칙이 아니라 래퍼(route-mcp.mjs)가 물리적으로 막는다 — 모드를 고르기 전에는 browser_* 호출이 전부 거부된다.

경로 지정 (recorded)

LLM 자율 (auto)

방식

사람이 한 번 시연해 저장한 경로를 참고해 수행

화면 스냅샷을 보고 LLM이 직접 판단

적합

복잡한 사내 레거시 화면, 매일 반복되는 작업

처음 보는 페이지, 일회성 탐색

약한 모델

잘 됨 (판단할 것이 거의 없음)

어려움 (요소 하나 고르는 데 실패)

실제 대화는 이렇게 흘러간다.

사용자: "주문조회 화면에서 목록 뽑아줘"
Claude: (browser_* 호출 → 게이트가 차단하고 두 선택지를 안내)
        "경로 지정과 LLM 자율 중 어느 쪽으로 할까요?"
사용자: "경로로"
Claude: route_mode {"mode":"recorded","route":"주문조회",...}  → 단계·셀렉터를 받아 수행
        끝나면 route_done → 다음 작업은 다시 물어봄

작업 단위로만 다시 묻는다. 한 작업 안에서는 추가 질문이 없다. 같은 사이트 안에서 화면을 옮겨 다녀도 재질문하지 않고, route_done·다른 사이트로 이동·호출 60회/유휴 15분 초과일 때만 다시 잠긴다. (--budget, --idle 로 조정)

검증 — npm run verify-route

npm run verify        # MCP 가 브라우저를 조작할 수 있는가
npm run verify-route  # 게이트가 실제로 막는가

둘 다 초록불이어야 배포 가능한 상태다. verify-route 가 확인하는 것:

✅ 래퍼 기동
✅ 툴 병합 — 게이트 4개 + 자식 23개 (run_code_unsafe 회수됨)
✅ 하드 차단 — 모드 미선택 시 browser_* 거부, 복구 방법 안내됨
✅ 사용자 확인 강제 — confirmedByUser:false 거부
✅ 자율 모드 — 잠금해제 후 브라우저 조작 통과
✅ 잔소리 방지 — 같은 사이트 안 이동은 재질문 없음
✅ 작업 단위 재무장 — route_done 후 다시 차단, 사유 안내됨
✅ 경로 모드 — 저장된 경로를 읽어 단계·셀렉터를 LLM 에게 전달

손으로 확인하려면: 이 폴더에서 claude/mcp 에 playwright connected 확인 → "page-agent-shop.html 열어줘" 라고 시켜본다. Claude가 바로 열지 않고 모드를 묻는다면 게이트가 동작하는 것이다.

사람이 진짜로 고르게 하기 — 2중 장치

LLM 에게 "사용자에게 물어보라"고 시키는 것만으로는 부족하다. confirmedByUser 는 결국 LLM 이 채우는 값이라, 똑똑한 모델은 묻지 않고 그냥 true 를 넣고 진행한다. 실제로 그런다.

사용자: "page-agent-shop.html 열어줘"
Claude: "저 모드를 설정하고 페이지를 엽니다."   ← 안 물어보고 자기가 정함

① elicitation — 사용자에게 다이얼로그를 직접 띄운다 (자동) 클라이언트(Claude Code)가 지원하면, 래퍼가 사용자에게 직접 선택 창을 띄운다. LLM 이 끼어들 여지가 없다.

📋 브라우저 작업을 어떻게 진행할까요?
   [1] 경로 지정 — 주문조회 (9단계, 위험 1)
   [2] 🔴 지금 녹화하기 — 브라우저가 뜨면 직접 시연하세요 (권장)
   [3] LLM 자율 — 화면을 보고 직접 판단 (처음 보는 페이지용)
   [4] 취소 — 브라우저를 쓰지 않는다

저장된 경로가 없거나 원하는 게 없으면 [2] 지금 녹화하기를 고르면 된다. 터미널로 나갈 필요 없이 주소와 이름을 물어본 뒤 브라우저가 뜨고, 시연을 마치고 창을 닫으면 경로로 저장돼 바로 그 경로로 진행한다. 고른 대로 즉시 진행되고, 취소를 고르면 브라우저가 안 열린다. 지원 여부는 자동 감지하며, 안 되는 버전이면 조용히 안내문 방식으로 폴백한다. (시작 시 stderr 에 클라이언트 elicitation 지원: 예/아니오 로 찍힌다)

지원하는지 확인하는 법claude 에서 route_status 를 물어보면 마지막 줄에 나온다.

사용자 직접 확인(elicitation): 미지원 — LLM 이 대신 고를 수 있습니다 ⚠ 막으려면 --no-auto 를 켜세요

미지원 이면 아래 ②로 간다.

--no-auto — 자율 모드를 아예 봉쇄한다 (사내 배포 권장)

npm run no-auto          # 켜기 (.mcp.json + 전역 등록 둘 다)
npm run no-auto -- --off # 되돌리기
# claude 껐다 켜기
"args": ["route-mcp.mjs", "--isolated", "--allow-unrestricted-file-access",
         "--browser", "chromium", "--no-auto"]

그러면 LLM 이 auto 를 우겨넣어도 거부되고, 사람이 직접 시연해 만든 경로(routes/*.json)로만 브라우저가 열린다. 원하는 경로가 없으면 LLM 은 "터미널에서 npm run record 를 실행해 주세요" 라고 사용자에게 요청하는 수밖에 없다.

이게 실질적으로 유효한 이유는, 검증 불가능한 질문("사람에게 물어봤는가?")을 검증 가능한 사실("사람이 만든 경로 파일이 있는가?")로 바꾸기 때문이다. 경로 파일은 LLM 이 만들 수 없다.

기본(자율 허용)

--no-auto

LLM 이 혼자 진행

가능 (묻지 않고 auto)

불가

새 화면 대응

즉시

사람이 한 번 시연해야 함

권장

탐색·개인 사용

사내 배포

  • browser_run_code_unsafe(임의 코드 실행)는 래퍼가 회수해 LLM 에게 노출하지 않는다. 래퍼 도입으로 LLM 권한은 줄어든다.

  • 되돌리려면 .mcp.json"route-mcp.mjs""node_modules/@playwright/mcp/cli.js" 로 바꾸면 끝(전역 등록은 npm run install-global 재실행).

  • ⛔ 경로 지정만 허용하려면 args 에 "--no-auto" 추가.

사내망 — 로그인 화면이 스타일 없이 뜰 때

SSO 로그인 페이지가 CSS 도 JS 도 없는 맨 HTML 로 뜨고 "Cookies are required" / "page has timed out" 같은 문구가 보이면, 쿠키 문제가 아니라 하위 리소스(CSS·JS)가 로딩에 실패한 것이다. 사내 프록시가 HTTPS 를 가로채는데(SSL inspection) 이 브라우저가 사내 루트 인증서를 모르기 때문인 경우가 대부분이다.

npm run corp                                  # 인증서 무시 + 프록시 자동 감지
npm run corp -- --proxy http://주소:포트        # 프록시 직접 지정
npm run corp -- --user 사번 --pass 비밀번호      # 인증이 필요한 프록시
npm run corp -- --check auth.sk.com           # 프록시가 정말 필요한지 판별
npm run corp -- --off                         # 해제
# claude 껐다 켜기

프록시 주소는 HTTPS_PROXY 환경변수 → 윈도우 설정(WSL 에서 reg.exe/netsh.exe 로 조회)npm config 순으로 자동으로 찾는다. 사내 프록시가 윈도우에만 잡혀 있고 WSL 환경변수에는 없는 경우가 흔하다. PAC(자동구성 스크립트)만 있으면 --proxy-pac-url 로 넘긴다.

프록시가 정말 필요한지 모르겠으면 먼저 확인한다.

npm run corp -- --check auth.sk.com
# ✅ 직접 연결됨  → 프록시 불필요. 브라우저만 안 되면 인증서 문제
# ❌ 직접 연결 실패 → 프록시 필요

환경변수는 HTTPS_PROXY/HTTP_PROXY 에서 찾는다(NO_PROXY 도 예외로 반영). 셸의 curl 은 되는데 브라우저만 안 되는 이유가 이것이다 — 크로미움은 이 환경변수를 자동으로 쓰지 않는다.

인증이 필요한 프록시(407)--proxy-server 플래그로는 안 된다. 크로미움이 URL 안의 아이디:비번@ 를 무시하기 때문이다. 그래서 Playwright 의 launchOptions.proxy 로 넘긴다 — npm run corp 가 알아서 처리한다.

비밀번호를 명령줄에 치면 셸 기록에 남으므로 .env 를 권한다.

PROXY_SERVER=http://proxy.corp:8080
PROXY_USER=k12345
PROXY_PASS=●●●●●●

설정은 .corp-config.json 에 저장되고 .mcp.json 은 건드리지 않는다 — 그건 커밋되는 파일이라 프록시 계정이 올라가면 안 되기 때문이다. .corp-config.json.env 는 둘 다 gitignore 된다.

원인 확인은 claude 에서 route_probe 로 한다 — 실패한 요청과 콘솔 에러를 그대로 보여준다.

🌐 리소스 로딩 실패 — 화면이 맨 HTML 로 보이는 원인일 수 있습니다
   [GET] https://.../style.css => [FAILED] net::ERR_CERT_AUTHORITY_INVALID
   → 인증서 문제입니다. MCP args 에 "--ignore-https-errors" 를 추가하세요.

증상의 전형적인 신호는 net::ERR_CERT_AUTHORITY_INVALID 다. 자체 서명 인증서로 재현해 확인했다 — 끄기 전에는 이 오류로 실패하고, npm run corp 후에는 CSS 까지 정상 로딩된다.

--ignore-https-errors 는 인증서 검증을 끈다. 사내 시스템 접속용으로만 쓸 것. npm run corp 만 하고 claude 를 껐다 켜지 않으면 반영되지 않는다 — 옛 브라우저 설정이 그대로 살아있다. 녹화기(npm run record)도 이 설정을 읽는다. claude 에서는 잘 뜨는데 녹화할 때만 맨 HTML 이면 옛 버전이니 npm run update 부터 하면 된다.

아이디·비밀번호 자동 입력 (.env)

로그인 단계를 자동화하려면 값을 래퍼만 알게 하고 LLM 에게는 이름만 보이게 한다.

cp .env.example .env      # 그리고 값을 채운다
ROUTE_SECRET_사번=k12345
ROUTE_SECRET_비밀번호=●●●●●●

시스템마다 계정이 다르면 녹화 중에 계정 이름을 붙인다. 한 경로에서 SSO 포털과 업무 시스템에 각각 로그인하는 경우가 흔한데, 둘 다 "아이디" 로 저장하면 구분할 수 없다.

[3/9] fill  #email
      🔑 로그인 정보로 감지됨 (아이디) — 값은 저장하지 않고 .env 에서 가져옵니다
      어느 계정인가요? 이름을 붙이세요 [없음(공용)]: 포털
      → .env 이름: ROUTE_SECRET_포털_아이디

터미널이 아닌 곳에서 녹화하면(질문 불가) 겹치는 이름에 자동으로 번호를 붙인다(아이디2). 녹화가 끝나면 .env 에 넣어야 할 이름을 그대로 출력하므로 복사해 채우면 된다.

녹화할 때 타이핑한 값은 기본적으로 저장하지 않는다. routes/*.json 은 커밋되는 파일이라 사번·이메일·검색어까지 그대로 올라가기 때문이다. 아이디·비밀번호는 valueFrom:"secret"(.env 에서), 나머지 입력은 valueFrom:"param"(실행 시 지정)이 된다. 민감하지 않아 값을 남기고 싶으면 --keep-values. routes/*.json 은 커밋되는 파일이라 사번·사내 이메일이 그대로 올라가면 안 되기 때문이다.

ROUTE_SECRET_아이디=k12345@sk.com
ROUTE_SECRET_비밀번호=●●●●●●
# 시스템마다 계정이 다르면 경로별로 덮어쓴다
# ROUTE_SECRET_회의실예약_아이디=...

경로 단계가 {"valueFrom":"secret","param":"비밀번호"} 면 래퍼가 그 값을 브라우저에 직접 넣는다. 값은 툴 응답·대화·로그 어디에도 나오지 않는다(혹시 섞여도 •••••• 로 가린다). 녹화할 때 비밀번호 칸은 자동으로 값 대신 valueFrom:"secret" 으로 저장된다.

알고 쓰셔야 할 것

  • .env.gitignore 되지만 디스크에는 평문으로 남는다. 사내 계정 정책상 곤란하면 쓰지 말 것.

  • Claude Code 는 파일 읽기 도구로 .env 를 열 수 있다. MCP 경계 밖이라 래퍼가 막지 못한다. 그게 걸리면 레포 밖 ~/.playwright-mcp.env 에 두고(이것도 자동으로 읽는다), .claude/settings.json 에 읽기 거부 규칙을 넣는다.

  • 더 안전한 대안: 세션 재사용. 비밀번호를 아예 저장하지 않는다.

    npm run record -- --name 주문조회 --url ... --auth auth.json

    한 번 사람이 로그인하면 세션이 auth.json(gitignore 됨)에 저장돼 다음부터 로그인 단계 자체가 사라진다. SSO 환경에서는 이쪽이 대개 유일하게 현실적인 방법이다.

테스트는 npm run play 로 — LLM 없이 직접 돌린다

경로와 도구가 제대로 도는지 확인하는 데 LLM 을 끼울 필요가 없다. play 는 래퍼를 그대로 띄워 claude 에서 도는 것과 완전히 같은 경로를 탄다.

npm run play -- --route 회의실예약진입                    # 경로만 재생
npm run play -- --route 회의실예약진입 --headed --repl    # 브라우저 띄운 채 이어서 시험 (권장)
npm run play -- --route 회의실예약진입 --headed           # 브라우저를 보며 한 번만

# 경로 재생 후 이어서 도구 실행
npm run play -- --route 회의실예약진입 \
  --pick-time '{"target":".jqs-day","watch":"#use","time":"09:00"}'
npm run play -- --route 회의실예약진입 \
  --row '{"rowText":"1704호","cellText":"예약","action":"click"}'

# 위젯이 무슨 조작에 반응하는지 시험
npm run play -- --route 회의실예약진입 \
  --try '{"target":".jqs-day","watch":"#use","x":150,"y":25,"toX":300}'
=== 경로 재생 (route_run) ===
✅ [1/1] 예약 화면 열기
=== 시각 선택 (route_pick_time) ===
✅ 09:00 를 선택했습니다 — 이용시간: 09:00 ~
🎉 완료.

--repl 이 실전에서 제일 쓸모 있다. 경로를 재생한 그 브라우저 그대로 도구를 계속 시험할 수 있다. 셀렉터를 맞춰가는 일은 시행착오가 필수인데, 매번 처음부터 다시 열면 시간이 다 간다.

play> try {"target":".jqs-day","watch":"#use","x":150,"y":25,"toX":300}
→ 클릭 위치로 값이 정해집니다 (한가운데=13:00 vs x=150=09:00)

play> pick {"target":".jqs-day","watch":"#use","time":"15:30"}
✅ 15:30 를 선택했습니다

play> sel 이용시간           ← 그 텍스트 요소의 셀렉터를 알아낸다
play> find 이용시간          ← 화면에서 텍스트 찾기
play> probe                  ← 화면 구조·프레임워크 진단
play> run                    ← 경로 처음부터 다시
play> quit

--keep 은 Ctrl+C 로 나가면 브라우저도 닫힌다(이 프로세스가 브라우저의 주인이라서). 이어서 시험하려면 --repl 을 쓸 것.

작업 순서를 이렇게 잡으면 된다.--headed --try 로 위젯 성격 파악 → ② --pick-time 등으로 인자를 확정 → ③ 그 인자를 claude 에서 그대로 쓰게 한다. LLM 은 마지막에만 등장하고, 그때는 이미 검증된 도구 호출을 옮기기만 하면 된다.

모델이 약할 때 — 어려운 앞부분만 녹화하기

사내 LLM 이 화면에서 요소를 잘 못 찾는다면, 전부 녹화할 필요는 없다. 어려운 앞부분(로그인·메뉴 여러 단계·프레임 진입)만 녹화해 두고, 화면이 목적지에 도착한 뒤부터 LLM 에게 맡기는 방식이 잘 통한다.

핵심은 녹화된 단계에서 LLM 은 아무 판단도 하지 않는다는 점이다. "참고" 하는 게 아니라 래퍼가 저장된 셀렉터로 직접 실행한다. 그래서 그 구간에서는 모델 성능이 아예 변수가 되지 않는다.

route_run
  ✅ [1/3] 로그인               ← 래퍼가 실행 (LLM 판단 0)
  ✅ [2/3] 회의실 예약 메뉴       ← 래퍼가 실행
  ✅ [3/3] 예약 화면 진입         ← 래퍼가 실행
  🎉 경로를 끝까지 실행했습니다. 화면은 지금 경로가 끝난 지점에 있습니다.
     · 이어서 직접 조작해야 하면:  route_assist

route_assist               ← 여기서부터 LLM 이 조작 (화면은 이미 목적지)
  browser_click ...        ← 남은 것만 하면 되니 훨씬 쉬움
route_done

LLM 이 헤매기 시작하면 그때 그 부분을 추가로 녹화해 경로를 늘리면 된다. 쓸수록 LLM 이 판단할 구간이 줄어든다.

시간표·타임라인 위젯 (클릭 위치로 값이 정해지는 화면)

회의실 예약의 시간대 그리드처럼, 어느 요소를 눌렀는지가 아니라 요소 안 어디를 눌렀는지로 값이 정해지는 위젯이 있다. 녹화 로그에 클릭이 전부 같은 셀렉터(.jqs-day >> nth=0 등)로 찍히면 이 경우다.

⚠ 녹화로는 좌표가 남지 않는다. Playwright 레코더는 <canvas> 가 아니면 클릭 좌표를 기록하지 않는다(positionForEvent 가 CANVAS 외에는 undefined 를 돌려준다). 그래서 div 로 만든 시간표 위젯은 재녹화해도 소용없고, 재생하면 늘 요소 한가운데가 눌린다(820시 타임라인이면 13:3014:00).

해결은 좌표를 사람이 한 번 찾아 굳히는 것이다.

1) route_run          → "늘 한가운데를 누릅니다" 경고가 뜬다
2) route_try {"target":".jqs-day","watch":"#이용시간","x":150,"y":30}
                      → 한가운데=14:00 vs x=150=9:00  (위치가 값을 정한다는 확인)
3) route_set_position {"step":5,"x":150}
                      → 실제로 눌러보고 성공하면 경로에 저장

이후 그 단계는 항상 그 지점을 누른다. 실제로 대조 확인한 동작이다 — 좌표 없이 14:00, 저장 후 9:00.

가장 쉬운 방법 — route_pick_time 으로 시각을 말로 고른다. 좌표를 몰라도 되고, 저장할 필요도 없다. 위젯의 몇 지점을 눌러보며 "x ↔ 시각" 대응을 그 자리에서 재고, 목표 시각의 지점을 계산해 누른 뒤 결과를 읽어 확인한다. watch 는 셀렉터 대신 화면에 보이는 텍스트를 그대로 줘도 된다. 사내 화면의 셀렉터를 알아내는 게 제일 번거로운데, 눈에 보이는 말은 바로 쓸 수 있다. 라벨("이용시간")만 잡혀 값이 옆 칸에 있어도 시각이 들어있는 가장 가까운 상위까지 올라가 읽는다.

route_pick_time {"target":".jqs-day","watch":"이용시간","time":"09:00"}

✅ 09:00 를 선택했습니다 — 이용시간: 09:00 ~
   재본 대응: x=216→09:30, x=1224→18:00  (시간당 약 119px)
   시도: x=157→09:00

매번 다시 재기 때문에 창 크기가 바뀌어도 맞는다. 폭 1440px→760px 로 줄여도 같은 9시를 찾는 것을 확인했다(시간당 119px→63px 로 스스로 보정). 저장된 좌표(route_set_position)보다 이쪽이 튼튼하다.

무엇이 통하는지 모르겠으면 route_try 로 시험한다. 클릭·좌표클릭·더블클릭·드래그를 차례로 해보고 결과가 어떻게 갈리는지 알려준다.

route_try {"target":".jqs-day","watch":"#이용시간표시","x":150,"y":30,"toX":250}

   ✅ 클릭(한가운데) → 14:00
   ✅ 좌표클릭(x=150) → 9:00
   ✅ 드래그(x 150→250) → 9:00 ~ 10:00
→ 클릭 위치로 값이 정해집니다 (한가운데=14:00 vs x=150=9:00)

셀렉터만 바꿔가며 재시도하지 말고 이걸로 먼저 성격을 파악한다.

⚠ 좌표 재생은 창 크기·행 개수가 바뀌면 어긋난다. 시간처럼 매번 달라지는 값은 녹화하지 말고 왼쪽 패널의 이용시간 같은 드롭다운을 쓰거나, 진입까지만 녹화하고 나머지는 route_assist 로 맡기는 편이 안전하다.

사내 그리드(가상 스크롤) 다루기

사내 시스템의 표는 대개 보이는 몇 줄만 실제로 존재하고 나머지는 스크롤해야 만들어진다(597행짜리 표인데 DOM 에는 12행만 있는 식). 그래서 셀렉터를 아무리 잘 잡아도 "없는 요소" 를 찾게 된다.

도구

쓰임

route_scroll_to {text}

그 텍스트가 나올 때까지 iframe 안까지 스크롤해 화면에 올린다

route_row {rowText, cellText, action, value}

내용으로 행을 찾아 그 행 안의 칸만 조작한다

route_row {"rowText":"07/24","cellText":"퇴근","action":"fill","value":"16:00"}
→ ✅ 행: 07/24 출근 퇴근 / 칸: 퇴근 (행 안 칸 3개) / 입력값: 16:00

위치로 행을 잡으면 위험하다. 녹화가 만들어내는 div:nth-child(12) 는 "화면상 12번째" 라서, 스크롤 위치가 달라지면 다른 날짜 행을 건드린다. 근태·결재 데이터에서는 조용한 오작동이라 최악이다. 특정 행을 노린다면 반드시 route_row 를 쓴다.

반복 작업 빠르게 — 결정적으로 "굳히기" (crawl.mjs)

에이전트형 MCP는 매번 LLM이 화면을 새로 분석해서 느리다. 매일/반복되는 똑같은 작업이면 분석을 아예 없애고 고정 셀렉터 스크립트로 재생하면 수 초 만에 끝난다(분석 0, 재현·감사 가능).

에이전트형 (MCP)

결정적 (crawl.mjs)

방식

LLM이 매번 보고 판단

셀렉터 고정, 그대로 재생

속도

느림(단계마다 분석)

수 초, 분석 0

적합

처음 보는 페이지·탐색·일회성

매일/반복·감사 필요

실전 흐름: MCP로 찾고 → 결정적으로 굳히기

  1. 흐름은 MCP로 한 번만 탐색(어떤 입력칸/버튼을 거치는지)

  2. 그 셀렉터를 crawl.mjscrawl(page) 함수에 박아 이후엔 재생만:

npm run crawl                                   # 기본: 동봉 검증용 픽스처 → crawl-result.json
node crawl.mjs --url "https://..." --out out.json
node crawl.mjs --headed                          # 화면 보며 디버깅(WSL은 GUI 필요)
node crawl.mjs --auth auth.json                  # 로그인 세션 재사용
  • 다른 사이트로 바꾸기: crawl.mjscrawl(page) 함수만 대상에 맞게 수정한다.

  • 셀렉터 찾기: MCP가 조작할 때 쓴 셀렉터를 받거나, npx playwright codegen <URL> (클릭할 때마다 코드 자동 생성 — WSL은 GUI 필요).

  • 결과는 crawl-result.json 에 JSON 으로 저장(.gitignore 처리됨).

옵션 (.mcp.json 의 args)

목적

방법

화면 보며 실행(headed)

기본 headed. 무인이면 "--headless" 추가

사내 로그인 유지(SSO/키인)

자동이다 — 폴더에 auth.json 이 있으면 래퍼가 알아서 넘긴다(--isolated 와 함께 써도 된다)

특정 사내 도메인만 허용

"--allowed-origins","https://사내앱.도메인" 추가

시스템에 깔린 Chrome 을 쓰고 싶다

"--browser","chrome" 로 변경 (기본은 번들 chromium — 폐쇄망 안전)

자율 모드 금지(경로 지정만)

"--no-auto" 추가

한 작업당 호출 상한 조정

"--budget","100" (기본 60)

재질문까지의 유휴 시간

"--idle","30" (분, 기본 15)

앞의 3개는 자식(@playwright/mcp)에게 그대로 전달되고, 뒤의 3개는 래퍼가 소비한다.

auth.json(세션 재사용): 로그인 가능한 환경에서 한 번 npx playwright codegen --save-storage=auth.json <URL> → 생성 파일을 이 폴더에 두고 위 옵션 켜기. (auth.json 은 로그인 토큰이라 .gitignore 처리됨 — git 에 올라가지 않는다.)

파일

  • .mcp.json — Claude Code용 MCP 설정. 절대경로 없음, envPLAYWRIGHT_BROWSERS_PATH=0. 진입점은 route-mcp.mjs(래퍼)

  • route-mcp.mjs — @playwright/mcp 앞에 서는 래퍼. 모드 게이트가 여기 있다. 진입점이 이 파일이다

  • verify-route.mjs — 게이트 검증(차단→모드선택→통과→재무장). 전역 등록이 옛 경로면 경고한다. npm run verify-route

  • update.mjs — 업데이트 원커맨드(pull→install→전역등록 갱신→검증). npm run update

  • record.mjs경로 녹화: 사람이 한 번 시연 → 의도 라벨링 → 후보 보강·재생검증 → routes/<이름>.json. npm run record

  • corp.mjs — 사내망 설정(인증서 무시·프록시·프록시 인증) → .corp-config.json. npm run corp

  • play.mjsLLM 없이 경로·도구를 직접 실행(테스트용). npm run play

  • no-auto.mjs — 자율 모드 봉쇄 토글. npm run no-auto

  • doctor.mjs — 진단: 전역/로컬 등록이 어느 폴더를 가리키는지, 중복은 없는지. npm run doctor

  • route-store.mjsroutes/<이름>.json 로드·검증·원자적 저장. 셀렉터/동작을 화이트리스트로 검증한다

  • routes/ — 저장된 경로. 팀 자산이라 커밋한다(사내 URL·입력값이 들어가므로 커밋 전 확인)

  • setup.mjs — 원커맨드 부트스트랩(install→크로미움→verify). npm run setup 으로 호출

  • verify.mjs — 자체검증(navigate→키인→클릭→결과추출, 네트워크 0). 조작이 실제로 먹혔는지까지 확인한다

  • mcp-client.mjs — MCP stdio(NDJSON) 클라이언트 공용 모듈. 툴 실패(isError)를 놓치지 않는다

  • crawl.mjs — 결정적 크롤러(반복 작업 굳히기). npm run crawl 으로 호출

  • install-global.mjs — 전역 등록(~/.claude.json 최상위 mcpServers에 안전 추가). npm run install-global

  • page-agent-shop.html — 검증용 로컬 픽스처(네트워크 0). verify.mjs 초록불이 이걸 대상으로 돈다

  • package.json@playwright/mcp@0.0.78 고정

트러블슈팅

증상

원인 / 해결

$env:... / set ... 에서 command not found

WSL(리눅스)인데 Windows 셸 문법을 씀. 이 가이드의 bash 명령을 쓸 것

node: command not found

WSL에 Node 18+ 미설치 → 설치 후 재시도

npm run setup 이 크로미움 다운로드에서 멈춤

CDN 차단(폐쇄망). 위 B 경로로 반입

크로미움은 받았는데 브라우저 실행 실패

리눅스 구동 라이브러리 부족 → sudo npx playwright install-deps chromium

verify 초록불이 안 뜨고 [mcp] 에러

node_modules/@playwright/mcp 손상 → npm install 다시. 로그의 [mcp] 줄 확인

claude 시작 시 playwright MCP 가 안 보임

이 폴더 안에서 claude 를 실행했는지 확인(.mcp.json 이 여기 있음)

approve 했는데 "브라우저 없음"

Windows에서 설치한 브라우저는 WSL이 못 찾음. 설치·실행 전부 WSL에서, npm run verify 통과 확인

녹화 때는 로그인 화면이 안 뜨는데 재생하면 로그인부터 나옴

저장된 세션(auth.json)이 재생에 안 실려서다. npm run update 로 최신을 받으면 래퍼가 자동으로 넘긴다. 세션이 만료됐으면 --auth auth.json 으로 다시 녹화

route_try/route_pick_time 이 요소를 못 찾음 (화면엔 보이는데)

iframe 안에 있는 경우다. 이제 모든 프레임을 뒤지며, 못 찾으면 프레임 개수와 함께 알려준다. route_probe 로 프레임 구조를 먼저 보라

녹화 중 페이지 클릭이 안 먹음 (요소만 빨갛게 표시되고 툴팁에 getByText(...) 가 뜸)

Inspector 가 로케이터 선택 모드다. 클릭이 "요소 고르기" 로 쓰여 페이지가 반응하지 않는다. 툴바의 ⊹ 를 다시 눌러 끄거나 Esc, 그리고 ●(녹화)가 켜져 있는지 확인

녹화 중 브라우저에 한글이 입력되지 않음(숫자는 됨)

WSL 브라우저에 한글 IME 가 안 붙어서다. ① Windows 쪽에서 복사 후 브라우저에 Ctrl+V 붙여넣기 ② 또는 아무 값이나 넣고 녹화한 뒤 --redact 로 값을 파라미터로 승격해 실행 시 지정

SSO 로그인 화면이 스타일 없이 뜨고 "Cookies are required"

쿠키가 아니라 CSS/JS 로딩 실패다. 사내 프록시의 HTTPS 가로채기 → npm run corp (원인 확인은 route_probe)

cp: cannot stat .env.example / 새 파일이 안 보임

git pull 이 실패했는데 지나간 것. 로컬 변경 때문이다 → git stashnpm run update 다시. (이제는 update 가 크게 알리고 실패로 끝난다)

npm ERR! enoent Could not read package.json

레포 폴더 밖에서 npm 을 돌린 것. cd playwright-mcp 후 다시

Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome

MCP 가 시스템 Google Chrome 을 찾고 있다. 인자에 --browser chromium 이 빠진 것 — git pullnpm run update. 수동 등록이면 args 에 "--browser","chromium" 추가

모드가 선택되지 않아… 가 계속 뜸

정상 동작이다. 사용자에게 경로/자율을 물어보고 route_mode 를 부르면 된다

게이트 없이 예전처럼 쓰고 싶음

.mcp.jsonroute-mcp.mjsnode_modules/@playwright/mcp/cli.js (전역은 npm run install-global 재실행)

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kay8244/playwright-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server