jackwener / jackwener/OpenCLI

[Bug]: installInterceptor not working after goto a new page

Open
#584 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.