blog

Playwright page.route 핸들러에서 구조 분해가 안 되는 이유

Front-end / Testing#Playwright#E2E#this 바인딩#JavaScript

문제

page.route 핸들러의 첫 번째 인자(Route)에서 request를 구조 분해하면 에러가 난다.

// ❌
await page.route('**/*', ({ request }) => {
  console.log(request.url()); // TypeError: request.url is not a function
});

원인 1: request는 속성이 아니라 메서드다

핸들러의 첫 번째 인자는 Route 객체이고, route.request는 Request를 반환하는 메서드다. 구조 분해로 꺼낸 request는 함수 자체이므로 request.url()은 존재하지 않는 속성을 호출하는 셈이다.

원인 2: 꺼낸 메서드를 호출해도 this가 없다

그렇다면 request()로 호출하면 될까?

await page.route('**/*', ({ request }) => {
  const req = request(); // ❌ this가 Route 인스턴스가 아님
});

JavaScript에서 메서드를 객체에서 떼어 내 호출하면 this가 원래 객체를 가리키지 않는다(엄격 모드에서는 undefined). Playwright의 Route는 클래스 인스턴스이고, 메서드는 내부 상태를 this로 참조하므로 떼어 내서 호출하면 정상 동작을 기대할 수 없다.

class Counter {
  count = 0;
  increase() {
    this.count += 1;
  }
}

const { increase } = new Counter();
increase(); // TypeError: Cannot read properties of undefined (reading 'count')

React props나 일반 객체처럼 데이터 속성은 구조 분해해도 되지만, 클래스 인스턴스의 메서드는 객체에서 직접 호출해야 한다.

올바른 사용법

// ✅ 1. route에서 메서드로 호출
await page.route('**/*', async (route) => {
  const request = route.request();
  console.log(request.url());
  await route.continue();
});

// ✅ 2. 핸들러의 두 번째 인자로 Request 받기
await page.route('**/*', async (route, request) => {
  await route.continue({
    headers: { ...request.headers(), foo: 'foo-value' },
  });
});

route.continue(), route.fulfill(), route.abort() 중 하나를 반드시 호출해야 요청이 멈춰 있지 않는다. 비동기 메서드이므로 await한다.

정리

코드결과
({ request }) => request.url()request는 함수 → url is not a function
({ request }) => request()this 바인딩이 끊김 → 동작 보장 안 됨
(route) => route.request()✅
(route, request) => ...✅ 두 번째 인자로 Request 수신

참고