[Bug]: installInterceptor not working after goto a new page
- Dominant language
- JavaScript
- Stars
- 29.3k
- Forks
- 2.9k
- Avg merge
- 15h 36m
- Merged PRs (30d)
- 70
Description
### Description
尝试为ctrip构建机票查询能力
使用页面:https://flights.ctrip.com/online/list/oneway-bjs-sha?_=1&depdate=2026-03-30&cabin=Y_S_C_F
希望使用拦截器模式来拦截batchSearch接口来获取所有航班信息,但goto到该页面后立刻installInterceptor无法正常拦截到接口内容。
### Steps to Reproduce
1. put this file as flight-tickets.ts at src/clis/ctrip/flight-tickets.ts
2. build: npm run build
3. run: ./dist/main.js ctrip flight-tickets --from bjs --to sha --date 2026-03-30 --limit 5 -v
4. returned no data
[Verbose] Warning: Command returned an empty result.
(no data)
``` flight-tickets.ts by claude code
import { cli, Strategy } from '../../registry.js';
cli({
site: 'ctrip',
name: 'flight-tickets',
description: '查询携程国内机票(单程/往返,含价格、舱位信息)',
domain: 'flights.ctrip.com',
strategy: Strategy.INTERCEPT,
browser: true,
args: [
{ name: 'from', required: true, help: '出发城市代码(如 bjs=北京,sha=上海,ctu=成都,szx=深圳,can=广州)' },
{ name: 'to', required: true, help: '到达城市代码(如 bjs=北京,sha=上海,ctu=成都,szx=深圳,can=广州)' },
{ name: 'date', required: true, help: '出发日期(格式:YYYY-MM-DD)' },
{ name: 'return_date', help: '返程日期(填写则查询往返票,格式:YYYY-MM-DD)' },
{ name: 'cabin', default: 'Y_S_C_F', help: '舱位类型(Y_S_C_F=不限, Y_S=经济/超经, C_F=公务/头等)' },
{ name: 'limit', type: 'int', default: 15, help: '返回结果数量' },
{ name: 'sort', default: 'price', help: '排序方式(price=价格,time=时间)' },
],
columns: ['rank', 'airline', 'flightNo', 'departure', 'arrival', 'duration', 'price', 'cabin'],
func: async (page, args) => {
const { from, to, date, return_date, cabin, limit, sort } = args;
// 1. 构建搜索URL
const tripType = return_date ? 'round' : 'oneway';
const url = tripType === 'round'
? `https://flights.ctrip.com/online/list/round-${from}-${to}?depdate=${date}_${return_date}&cabin=${cabin}`
: `https://flights.ctrip.com/online/list/oneway-${from}-${to}?depdate=${date}&cabin=${cabin}`;
// 2. 导航到搜索页(不等待页面完全加载)
await page.goto(url, { waitUntil: 'none' });
// 3. 立即安装拦截器(在页面开始加载时就挂载)
await page.installInterceptor('batchSearch');
// 4. 等待页面加载和API响应
await page.wait(10);
// 5. 获取拦截的请求
const requests = await page.getInterceptedRequests();
if (!requests || requests.length === 0) {
return [];
}
// 6. 解析航班数据
const results: any[] = [];
for (const req of requests) {
const responseData = req.data;
if (!responseData) continue;
const flightItineraryList = responseData.flightItineraryList || [];
for (const item of flightItineraryList) {
if (results.length >= Number(limit)) break;
const flightSegments = item.flightSegments || [];
if (!flightSegments.length) continue;
// 处理第一段行程(去程)
const firstSegment = flightSegments[0];
const flightList = firstSegment.flightList || [];
if (!flightList.length) continue;
const flight = flightList[0];
const priceInfo = item.priceInfo || {};
// 格式化起飞和到达时间
const departureDate = flight.departureDate || '';
const departureTime = flight.departureTime || '';
const arrivalDate = flight.arrivalDate || '';
const arrivalTime = flight.arrivalTime || '';
// 组装起飞信息
const departureInfo = [
departureTime,
flight.departureAirportName || '',
flight.departureTerminal ? `T${flight.departureTerminal}` : ''
].filter(Boolean).join(' ');
// 组装到达信息
const arrivalInfo = [
arrivalTime,
flight.arrivalAirportName || '',
flight.arrivalTerminal ? `T${flight.arrivalTerminal}` : ''
].filter(Boolean).join(' ');
// 添加日期标识(如果跨天)
const arrivalDateSuffix = departureDate !== arrivalDate ? ' +1' : '';
// 计算飞行时长
const duration = firstSegment.duration ? `${Math.floor(Number(firstSegment.duration) / 60)}h${Number(firstSegment.duration) % 60}m` : '-';
// 获取舱位信息
const cabinInfo = priceInfo.cabinClass || '经济舱';
// 价格信息
const adultPrice = priceInfo.adultPrice;
const priceDisplay = adultPrice ? `¥${adultPrice}` : '-';
// 航司和航班号
const airlineName = flight.airlineName || '';
const flightNo = flight.flightNo || '';
results.push({
rank: results.length + 1,
airline: airlineName,
flightNo: flightNo,
departure: departureInfo,
arrival: arrivalInfo + arrivalDateSuffix,
duration: duration,
price: priceDisplay,
cabin: cabinInfo,
_sortPrice: adultPrice || 999999, // 用于排序
_sortTime: departureTime || '23:59' // 用于排序
});
}
}
// 7. 排序
if (sort === 'price') {
results.sort((a, b) => a._sortPrice - b._sortPrice);
} else if (sort === 'time') {
results.sort((a, b) => a._sortTime.localeCompare(b._sortTime));
}
// 8. 更新排名并移除排序字段
return results.map((item, index) => {
const { _sortPrice, _sortTime, ...rest } = item;
return { ...rest, rank: index + 1 };
});
},
});
```
### Expected Behavior
直接使用CDP可以拦截到接口内容以及航班字段flightItineraryList,希望使用opencli的插件拦截器也能达到该效果
```
### Result
{"found":2,"urls":["https://flights.ctrip.com/international/search/api/search/batchSearch?v=0.48258055969123614","https://flights.ctrip.com/international/search/api/flight/comfort/batchGetComfortTagList?v=0.055198922744281065"],"sampleData":{"keys":["status","msg","data"],"dataPath":["context","flightItineraryList","giftIds","bestChoiceFlightsForceTop","recommendProduct"]}}
### Ran Playwright code
```js
await (async (page) => {
const batchSearchRequests = [];
const client = await page.context().newCDPSession(page);
await client.send('Network.enable');
client.on('Network.responseReceived', (params) => {
const url = params.response.url;
// 查找包含 batchSearch 的请求
if (url.toLowerCase().includes('batchsearch') || url.toLowerCase().includes('batch')) {
batchSearchRequests.push({
url: url,
status: params.response.status,
requestId: params.requestId
});
}
});
await page.goto('https://flights.ctrip.com/online/list/oneway-bjs-sha?depdate=2026-03-30&cabin=Y_S_C_F');
await page.waitForTimeout(10000);
// 尝试获取第一个 batchSearch 响应的内容
let responseBody = null;
if (batchSearchRequests.length > 0) {
try {
const response = await client.send('Network.getResponseBody', {
requestId: batchSearchRequests[0].requestId
});
responseBody = JSON.parse(response.body);
} catch (e) {
responseBody = { error: e.message };
}
}
return {
found: batchSearchRequests.length,
urls: batchSearchRequests.map(r => r.url),
sampleData: responseBody ? {
keys: Object.keys(responseBody),
dataPath: responseBody.data ? Object.keys(responseBody.data).slice(0, 5) : null
} : null
};
})(page);
```
### Page
- Page URL: https://flights.ctrip.com/online/list/oneway-bjs-sha?depdate=2026-03-30&cabin=Y_S_C_F
- Page Title: 北京到上海机票查询预订-北京飞上海特价机票价格-携程飞机票
- Console: 0 errors, 6 warnings
### Events
- New console entries: .playwright-mcp/console-2026-03-29T13-44-46-347Z.log#L16-L17
- [LOG] [echo-sdk] init success,traceType= echo glob...atic/chunks/ctrip-package-async.43fa578eb78a151e.js:0
- [LOG] [echo-sdk] this is test api! @ https://bd-s....atic/chunks/ctrip-package-async.43fa578eb78a151e.js:0
- [LOG] [echo-sdk] echoTraceOfNormal@1.1.11 A0601@PR...atic/chunks/ctrip-package-async.43fa578eb78a151e.js:0
- [WARNING] The PerformanceObserver does not support...n.com/packages/flight/coffeebean-web/5.2.8/main.js:28
- [LOG] [WEB-Core] Init. @ https://bd-s.tripcdn.cn/m.../_next/static/chunks/pages/_app-dfe6772f154fd592.js:0
- [INFO] [Ares] version 2.8.92 @ https://bd-s.tripcd.../_next/static/chunks/pages/_app-dfe6772f154fd592.js:0
- [INFO] [Ares] assetPrefix https://bd-s.tripcdn.cn .../_next/static/chunks/pages/_app-dfe6772f154fd592.js:0
- [INFO] [Ares] modules {group: flight, name: flight.../_next/static/chunks/pages/_app-dfe6772f154fd592.js:0
- [INFO] [Ares] manifest {/online/_next/static/chunk.../_next/static/chunks/pages/_app-dfe6772f154fd592.js:0
- [LOG] %c[logDevTrace] color:#cc2b2b {type: sdkLoad...oginsdkv2/%5E1.5.3/default/pcloginsdk.js?expires=1d:1
- [LOG] %c[logMonitor] color:#e95c0b success {group:...oginsdkv2/%5E1.5.3/default/pcloginsdk.js?expires=1d:1
- [WARNING] An iframe which has both allow-scripts a...attribute can escape its sandboxing. @ about:srcdoc:0
- [LOG] %c[logDevTrace] color:#cc2b2b {type: detectI...oginsdkv2/%5E1.5.3/default/pcloginsdk.js?expires=1d:1
- [LOG] {ResponseStatus: Object, tipsInfo: null, res...om/NFES/mfe_compose/1773049485574/compose.preact.js:2
- [LOG] [echo-sdk] init success,traceType= echo glob...atic/chunks/ctrip-package-async.43fa578eb78a151e.js:0
- [LOG] [echo-sdk] this is test api! @ https://bd-s....atic/chunks/ctrip-package-async.43fa578eb78a151e.js:0
- [LOG] [echo-sdk] echoTraceOfNormal@1.1.11 A0601@PR...atic/chunks/ctrip-package-async.43fa578eb78a151e.js:0
```
### OpenCLI Version
1.5.2
### Node.js Version
22.x
### Operating System
macOS
### Logs / Screenshots
```shell
```
Contributor guide
Research direction
Start with src/clis/ctrip/flight-tickets.ts and trace the installInterceptor and getInterceptedRequests entry points used after page.goto. Reproduce with npm run build and ./dist/main.js ctrip flight-tickets --from bjs --to sha --date 2026-03-30 --limit 5 -v, then compare the interceptor behavior with the provided Playwright CDP example. Done means batchSearch response data is captured after navigation and the command returns flight results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100