@lmcc-dev/mult-fetch-mcp-server 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -15
- package/README.zh.md +31 -0
- package/dist/src/client.js +118 -91
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +25 -10
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +67 -64
- package/dist/src/lib/fetchers/common/BaseFetcher.js +106 -10
- package/dist/src/lib/fetchers/common/types.js +10 -1
- package/dist/src/lib/fetchers/common/utils.js +1 -1
- package/dist/src/lib/fetchers/node/HttpClient.js +41 -21
- package/dist/src/lib/fetchers/node/NodeFetcher.js +24 -8
- package/dist/src/lib/i18n/index.js +2 -2
- package/dist/src/lib/i18n/keys/client.js +1 -0
- package/dist/src/lib/i18n/keys/extractor.js +26 -0
- package/dist/src/lib/i18n/keys/fetcher.js +12 -1
- package/dist/src/lib/i18n/keys/index.js +1 -0
- package/dist/src/lib/i18n/keys/node.js +2 -0
- package/dist/src/lib/i18n/locales/en/client.js +1 -0
- package/dist/src/lib/i18n/locales/en/extractor.js +24 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +3 -1
- package/dist/src/lib/i18n/locales/en/index.js +2 -0
- package/dist/src/lib/i18n/locales/en/node.js +2 -0
- package/dist/src/lib/i18n/locales/zh/client.js +1 -0
- package/dist/src/lib/i18n/locales/zh/extractor.js +22 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +13 -3
- package/dist/src/lib/i18n/locales/zh/index.js +2 -0
- package/dist/src/lib/i18n/locales/zh/node.js +2 -0
- package/dist/src/lib/i18n/logger.js +2 -2
- package/dist/src/lib/logger.js +38 -17
- package/dist/src/lib/server/browser.js +2 -2
- package/dist/src/lib/server/fetcher.js +0 -3
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +4 -4
- package/dist/src/lib/server/tools.js +127 -354
- package/dist/src/lib/utils/ChunkManager.js +2 -2
- package/dist/src/lib/utils/ContentExtractor.js +141 -0
- package/dist/src/lib/utils/ContentProcessor.js +5 -11
- package/dist/src/lib/utils/ContentSizeManager.js +2 -2
- package/dist/src/lib/utils/ErrorHandler.js +1 -0
- package/dist/src/lib/utils/TemplateUtils.js +6 -2
- package/dist/tests/client.test.js +1 -1
- package/dist/tests/test-extract-single.js +389 -0
- package/dist/tests/utils/ContentExtractor.test.js +173 -0
- package/dist/tests/utils/ContentProcessor.test.js +136 -0
- package/dist/tests/utils/TemplateUtils.test.js +118 -0
- package/package.json +13 -3
- package/dist/i18n-test-report.json +0 -8
- package/dist/i18n-unused-keys-report.json +0 -8
- package/dist/src/lib/BrowserFetcher.js +0 -787
- package/dist/src/lib/NodeFetcher.js +0 -492
- package/dist/src/lib/i18n/keys.js +0 -529
- package/dist/src/test-i18n.js +0 -139
- package/dist/tests/BrowserFetcher.test.js +0 -951
- package/dist/tests/NodeFetcher.test.js +0 -263
- package/dist/tests/i18n-remove-unused-keys.js +0 -236
- package/dist/tests/i18n-test-report.json +0 -2004
- package/dist/tests/src/lib/i18n/index.js +0 -108
- package/dist/tests/src/lib/i18n/keys/base.js +0 -47
- package/dist/tests/src/lib/i18n/keys/browser.js +0 -93
- package/dist/tests/src/lib/i18n/keys/client.js +0 -70
- package/dist/tests/src/lib/i18n/keys/errors.js +0 -34
- package/dist/tests/src/lib/i18n/keys/fetcher.js +0 -84
- package/dist/tests/src/lib/i18n/keys/index.js +0 -31
- package/dist/tests/src/lib/i18n/keys/node.js +0 -56
- package/dist/tests/src/lib/i18n/keys/prompts.js +0 -82
- package/dist/tests/src/lib/i18n/keys/resources.js +0 -50
- package/dist/tests/src/lib/i18n/keys/server.js +0 -64
- package/dist/tests/src/lib/i18n/keys/tools.js +0 -34
- package/dist/tests/src/lib/i18n/locales/en/browser.js +0 -88
- package/dist/tests/src/lib/i18n/locales/en/client.js +0 -66
- package/dist/tests/src/lib/i18n/locales/en/errors.js +0 -28
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +0 -71
- package/dist/tests/src/lib/i18n/locales/en/index.js +0 -29
- package/dist/tests/src/lib/i18n/locales/en/node.js +0 -51
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +0 -52
- package/dist/tests/src/lib/i18n/locales/en/resources.js +0 -50
- package/dist/tests/src/lib/i18n/locales/en/server.js +0 -56
- package/dist/tests/src/lib/i18n/locales/en/tools.js +0 -28
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +0 -87
- package/dist/tests/src/lib/i18n/locales/zh/client.js +0 -66
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +0 -28
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +0 -71
- package/dist/tests/src/lib/i18n/locales/zh/index.js +0 -29
- package/dist/tests/src/lib/i18n/locales/zh/node.js +0 -51
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +0 -53
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +0 -50
- package/dist/tests/src/lib/i18n/locales/zh/server.js +0 -57
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +0 -28
- package/dist/tests/src/lib/i18n/logger.js +0 -114
- package/dist/tests/src/lib/logger.js +0 -181
- package/dist/tests/tests/test-i18n.js +0 -588
- package/dist/vitest.config.js +0 -29
|
@@ -1,951 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
-
* Co-Author: AI Assistant (Claude)
|
|
4
|
-
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
-
*/
|
|
6
|
-
import { BrowserFetcher } from '../src/lib/BrowserFetcher.js';
|
|
7
|
-
import { log, COMPONENTS } from '../src/lib/logger.js';
|
|
8
|
-
// Mock Puppeteer
|
|
9
|
-
jest.mock('puppeteer', () => ({
|
|
10
|
-
__esModule: true,
|
|
11
|
-
default: {
|
|
12
|
-
launch: jest.fn()
|
|
13
|
-
}
|
|
14
|
-
}));
|
|
15
|
-
// Mock puppeteer-extra
|
|
16
|
-
jest.mock('puppeteer-extra', () => {
|
|
17
|
-
const mockPuppeteerExtra = {
|
|
18
|
-
use: jest.fn(),
|
|
19
|
-
launch: jest.fn()
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
__esModule: true,
|
|
23
|
-
default: mockPuppeteerExtra
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
// Mock puppeteer-extra-plugin-stealth
|
|
27
|
-
jest.mock('puppeteer-extra-plugin-stealth', () => {
|
|
28
|
-
const mockStealthPlugin = jest.fn();
|
|
29
|
-
return {
|
|
30
|
-
__esModule: true,
|
|
31
|
-
default: mockStealthPlugin
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
// Mock child_process
|
|
35
|
-
jest.mock('child_process', () => ({
|
|
36
|
-
execSync: jest.fn()
|
|
37
|
-
}));
|
|
38
|
-
// Mock JSDOM
|
|
39
|
-
jest.mock('jsdom', () => ({
|
|
40
|
-
JSDOM: jest.fn().mockImplementation(() => ({
|
|
41
|
-
window: {
|
|
42
|
-
document: {
|
|
43
|
-
body: {
|
|
44
|
-
textContent: '测试文本内容'
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}))
|
|
49
|
-
}));
|
|
50
|
-
// Mock TurndownService
|
|
51
|
-
jest.mock('turndown', () => jest.fn().mockImplementation(() => ({
|
|
52
|
-
addRule: jest.fn(),
|
|
53
|
-
turndown: jest.fn().mockReturnValue('# 转换后的Markdown内容')
|
|
54
|
-
})));
|
|
55
|
-
// Mock i18n
|
|
56
|
-
jest.mock('../src/lib/i18n/index.js', () => ({
|
|
57
|
-
t: (key, options) => key
|
|
58
|
-
}));
|
|
59
|
-
// Mock logger
|
|
60
|
-
jest.mock('../src/lib/logger.js', () => ({
|
|
61
|
-
log: jest.fn(),
|
|
62
|
-
COMPONENTS: {
|
|
63
|
-
BROWSER_FETCH: 'BROWSER_FETCH'
|
|
64
|
-
}
|
|
65
|
-
}));
|
|
66
|
-
describe('BrowserFetcher 类测试 (BrowserFetcher Class Tests)', () => {
|
|
67
|
-
let mockPuppeteerExtra;
|
|
68
|
-
let mockBrowser;
|
|
69
|
-
let mockPage;
|
|
70
|
-
// 模拟 process.memoryUsage 返回值
|
|
71
|
-
const mockMemoryUsage = {
|
|
72
|
-
heapUsed: 100 * 1024 * 1024, // 100MB
|
|
73
|
-
heapTotal: 200 * 1024 * 1024, // 200MB
|
|
74
|
-
rss: 300 * 1024 * 1024, // 300MB
|
|
75
|
-
external: 50 * 1024 * 1024, // 50MB
|
|
76
|
-
arrayBuffers: 10 * 1024 * 1024 // 10MB
|
|
77
|
-
};
|
|
78
|
-
beforeAll(() => {
|
|
79
|
-
// 启用 Jest 的假计时器
|
|
80
|
-
jest.useFakeTimers();
|
|
81
|
-
});
|
|
82
|
-
afterAll(() => {
|
|
83
|
-
// 恢复真实计时器
|
|
84
|
-
jest.useRealTimers();
|
|
85
|
-
});
|
|
86
|
-
beforeEach(() => {
|
|
87
|
-
// 重置所有 mock
|
|
88
|
-
jest.clearAllMocks();
|
|
89
|
-
jest.clearAllTimers();
|
|
90
|
-
// 模拟 process.memoryUsage
|
|
91
|
-
jest.spyOn(process, 'memoryUsage').mockReturnValue(mockMemoryUsage);
|
|
92
|
-
// 创建 mock 页面
|
|
93
|
-
mockPage = {
|
|
94
|
-
setDefaultTimeout: jest.fn(),
|
|
95
|
-
setUserAgent: jest.fn(),
|
|
96
|
-
setExtraHTTPHeaders: jest.fn(),
|
|
97
|
-
goto: jest.fn(),
|
|
98
|
-
waitForSelector: jest.fn(),
|
|
99
|
-
content: jest.fn(),
|
|
100
|
-
evaluate: jest.fn(),
|
|
101
|
-
close: jest.fn(),
|
|
102
|
-
cookies: jest.fn(),
|
|
103
|
-
setCookie: jest.fn(),
|
|
104
|
-
url: jest.fn()
|
|
105
|
-
};
|
|
106
|
-
// 创建 mock 浏览器
|
|
107
|
-
mockBrowser = {
|
|
108
|
-
newPage: jest.fn().mockResolvedValue(mockPage),
|
|
109
|
-
close: jest.fn(),
|
|
110
|
-
on: jest.fn()
|
|
111
|
-
};
|
|
112
|
-
// 重置 BrowserFetcher 的静态属性
|
|
113
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
114
|
-
BrowserFetcher.browser = null;
|
|
115
|
-
// @ts-ignore
|
|
116
|
-
BrowserFetcher.browserStarting = false;
|
|
117
|
-
// @ts-ignore
|
|
118
|
-
BrowserFetcher.browserStartPromise = null;
|
|
119
|
-
// @ts-ignore
|
|
120
|
-
BrowserFetcher.lastMemoryCheck = 0;
|
|
121
|
-
// @ts-ignore
|
|
122
|
-
BrowserFetcher.cookieStore = {};
|
|
123
|
-
// 获取 puppeteer-extra 的模拟实例
|
|
124
|
-
mockPuppeteerExtra = require('puppeteer-extra').default;
|
|
125
|
-
mockPuppeteerExtra.launch.mockReset();
|
|
126
|
-
});
|
|
127
|
-
describe('内存管理测试 (Memory Management Tests)', () => {
|
|
128
|
-
test('应该正确检查内存使用情况 (Should check memory usage correctly)', async () => {
|
|
129
|
-
// 准备测试数据
|
|
130
|
-
const testUrl = 'https://example.com';
|
|
131
|
-
const testPayload = {
|
|
132
|
-
url: testUrl,
|
|
133
|
-
debug: true
|
|
134
|
-
};
|
|
135
|
-
// 调用方法
|
|
136
|
-
await BrowserFetcher.html(testPayload);
|
|
137
|
-
// 验证 process.memoryUsage 被调用
|
|
138
|
-
expect(process.memoryUsage).toHaveBeenCalled();
|
|
139
|
-
});
|
|
140
|
-
test('当内存使用过高时应该记录警告 (Should log warning when memory usage is high)', async () => {
|
|
141
|
-
// 模拟高内存使用情况
|
|
142
|
-
const highMemoryUsage = {
|
|
143
|
-
heapUsed: 900 * 1024 * 1024, // 900MB
|
|
144
|
-
heapTotal: 1024 * 1024 * 1024, // 1GB
|
|
145
|
-
rss: 1.5 * 1024 * 1024 * 1024, // 1.5GB
|
|
146
|
-
external: 200 * 1024 * 1024, // 200MB
|
|
147
|
-
arrayBuffers: 100 * 1024 * 1024 // 100MB
|
|
148
|
-
};
|
|
149
|
-
jest.spyOn(process, 'memoryUsage').mockReturnValue(highMemoryUsage);
|
|
150
|
-
// 设置浏览器实例
|
|
151
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
152
|
-
BrowserFetcher.browser = mockBrowser;
|
|
153
|
-
// 准备测试数据
|
|
154
|
-
const testUrl = 'https://example.com';
|
|
155
|
-
const testPayload = {
|
|
156
|
-
url: testUrl,
|
|
157
|
-
debug: true
|
|
158
|
-
};
|
|
159
|
-
// 调用方法
|
|
160
|
-
await BrowserFetcher.html(testPayload);
|
|
161
|
-
// 验证警告日志被记录
|
|
162
|
-
expect(log).toHaveBeenCalledWith('browser.memoryTooHigh', true, expect.objectContaining({
|
|
163
|
-
heapUsed: expect.any(Number),
|
|
164
|
-
heapTotal: expect.any(Number),
|
|
165
|
-
rss: expect.any(Number)
|
|
166
|
-
}), COMPONENTS.BROWSER_FETCH);
|
|
167
|
-
});
|
|
168
|
-
test('应该在检查间隔内只检查一次内存 (Should check memory only once within interval)', async () => {
|
|
169
|
-
// 准备测试数据
|
|
170
|
-
const testUrl = 'https://example.com';
|
|
171
|
-
const testPayload = {
|
|
172
|
-
url: testUrl,
|
|
173
|
-
debug: true
|
|
174
|
-
};
|
|
175
|
-
// 设置浏览器实例
|
|
176
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
177
|
-
BrowserFetcher.browser = mockBrowser;
|
|
178
|
-
// 连续调用两次
|
|
179
|
-
await BrowserFetcher.html(testPayload);
|
|
180
|
-
await BrowserFetcher.html(testPayload);
|
|
181
|
-
// 验证 process.memoryUsage 只被调用一次
|
|
182
|
-
expect(process.memoryUsage).toHaveBeenCalledTimes(1);
|
|
183
|
-
});
|
|
184
|
-
test('应该在超过检查间隔后重新检查内存 (Should check memory again after interval)', async () => {
|
|
185
|
-
// 准备测试数据
|
|
186
|
-
const testUrl = 'https://example.com';
|
|
187
|
-
const testPayload = {
|
|
188
|
-
url: testUrl,
|
|
189
|
-
debug: true
|
|
190
|
-
};
|
|
191
|
-
// 设置浏览器实例
|
|
192
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
193
|
-
BrowserFetcher.browser = mockBrowser;
|
|
194
|
-
// 第一次调用
|
|
195
|
-
await BrowserFetcher.html(testPayload);
|
|
196
|
-
// 模拟时间经过
|
|
197
|
-
jest.advanceTimersByTime(60000); // 60秒后
|
|
198
|
-
// 第二次调用
|
|
199
|
-
await BrowserFetcher.html(testPayload);
|
|
200
|
-
// 验证 process.memoryUsage 被调用两次
|
|
201
|
-
expect(process.memoryUsage).toHaveBeenCalledTimes(2);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
describe('浏览器实例管理测试 (Browser Instance Management Tests)', () => {
|
|
205
|
-
test('应该正确创建浏览器实例 (Should create browser instance correctly)', async () => {
|
|
206
|
-
// 准备测试数据
|
|
207
|
-
const testUrl = 'https://example.com';
|
|
208
|
-
const testPayload = {
|
|
209
|
-
url: testUrl,
|
|
210
|
-
debug: true
|
|
211
|
-
};
|
|
212
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
213
|
-
const mockLaunch = require('puppeteer-extra').default.launch;
|
|
214
|
-
mockLaunch.mockResolvedValue(mockBrowser);
|
|
215
|
-
// 调用方法
|
|
216
|
-
await BrowserFetcher.html(testPayload);
|
|
217
|
-
// 验证浏览器实例被创建
|
|
218
|
-
expect(mockLaunch).toHaveBeenCalled();
|
|
219
|
-
// 验证事件监听器被设置
|
|
220
|
-
expect(mockBrowser.on).toHaveBeenCalledWith('disconnected', expect.any(Function));
|
|
221
|
-
});
|
|
222
|
-
test('应该复用已存在的浏览器实例 (Should reuse existing browser instance)', async () => {
|
|
223
|
-
// 设置已存在的浏览器实例
|
|
224
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
225
|
-
BrowserFetcher.browser = mockBrowser;
|
|
226
|
-
// 准备测试数据
|
|
227
|
-
const testUrl = 'https://example.com';
|
|
228
|
-
const testPayload = {
|
|
229
|
-
url: testUrl,
|
|
230
|
-
debug: true
|
|
231
|
-
};
|
|
232
|
-
// 调用方法
|
|
233
|
-
await BrowserFetcher.html(testPayload);
|
|
234
|
-
// 验证没有创建新的浏览器实例
|
|
235
|
-
expect(require('puppeteer-extra').default.launch).not.toHaveBeenCalled();
|
|
236
|
-
});
|
|
237
|
-
test('应该正确处理浏览器启动中的状态 (Should handle browser starting state correctly)', async () => {
|
|
238
|
-
// 模拟浏览器正在启动
|
|
239
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
240
|
-
BrowserFetcher.browserStarting = true;
|
|
241
|
-
// @ts-ignore
|
|
242
|
-
BrowserFetcher.browserStartPromise = Promise.resolve(mockBrowser);
|
|
243
|
-
// 准备测试数据
|
|
244
|
-
const testUrl = 'https://example.com';
|
|
245
|
-
const testPayload = {
|
|
246
|
-
url: testUrl,
|
|
247
|
-
debug: true
|
|
248
|
-
};
|
|
249
|
-
// 调用方法
|
|
250
|
-
await BrowserFetcher.html(testPayload);
|
|
251
|
-
// 验证使用了现有的启动 Promise
|
|
252
|
-
expect(require('puppeteer-extra').default.launch).not.toHaveBeenCalled();
|
|
253
|
-
});
|
|
254
|
-
test('应该在浏览器断开连接时清理实例 (Should clean up instance on browser disconnect)', async () => {
|
|
255
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
256
|
-
const mockLaunch = require('puppeteer-extra').default.launch;
|
|
257
|
-
mockLaunch.mockResolvedValue(mockBrowser);
|
|
258
|
-
// 准备测试数据
|
|
259
|
-
const testUrl = 'https://example.com';
|
|
260
|
-
const testPayload = {
|
|
261
|
-
url: testUrl,
|
|
262
|
-
debug: true
|
|
263
|
-
};
|
|
264
|
-
// 调用方法
|
|
265
|
-
await BrowserFetcher.html(testPayload);
|
|
266
|
-
// 获取断开连接的处理函数
|
|
267
|
-
const disconnectHandler = mockBrowser.on.mock.calls.find(call => call[0] === 'disconnected')?.[1];
|
|
268
|
-
// 确保找到了处理函数
|
|
269
|
-
expect(disconnectHandler).toBeDefined();
|
|
270
|
-
// 调用断开连接的处理函数
|
|
271
|
-
if (disconnectHandler) {
|
|
272
|
-
disconnectHandler({ targetId: 'test-browser' });
|
|
273
|
-
}
|
|
274
|
-
// 验证浏览器实例被清理
|
|
275
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
276
|
-
expect(BrowserFetcher.browser).toBeNull();
|
|
277
|
-
// @ts-ignore
|
|
278
|
-
expect(BrowserFetcher.browserStarting).toBeFalsy();
|
|
279
|
-
// @ts-ignore
|
|
280
|
-
expect(BrowserFetcher.browserStartPromise).toBeNull();
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
describe('Cookie 管理测试 (Cookie Management Tests)', () => {
|
|
284
|
-
beforeEach(() => {
|
|
285
|
-
// 重置 BrowserFetcher 的静态属性
|
|
286
|
-
BrowserFetcher.cookieStore = {};
|
|
287
|
-
BrowserFetcher.browser = null;
|
|
288
|
-
BrowserFetcher.browserStarting = false;
|
|
289
|
-
BrowserFetcher.browserStartPromise = null;
|
|
290
|
-
BrowserFetcher.lastMemoryCheck = 0;
|
|
291
|
-
// 重置 mock
|
|
292
|
-
mockPage.evaluate.mockClear();
|
|
293
|
-
mockPage.setExtraHTTPHeaders.mockClear();
|
|
294
|
-
mockPage.goto.mockClear();
|
|
295
|
-
mockPage.waitForSelector.mockClear();
|
|
296
|
-
mockPage.content.mockClear();
|
|
297
|
-
mockPage.close.mockClear();
|
|
298
|
-
mockPage.url.mockClear();
|
|
299
|
-
// 设置基本的 mock 返回值
|
|
300
|
-
mockPage.content.mockResolvedValue('<html><body>Test content</body></html>');
|
|
301
|
-
mockPage.waitForSelector.mockResolvedValue(null);
|
|
302
|
-
// 模拟 puppeteerExtra.launch 返回浏览器实例
|
|
303
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
304
|
-
});
|
|
305
|
-
it('应该正确保存和获取 Cookie (Should save and retrieve cookies correctly)', async () => {
|
|
306
|
-
// 设置测试数据
|
|
307
|
-
const testUrl = 'https://example.com';
|
|
308
|
-
const testDomain = 'example.com';
|
|
309
|
-
const testCookie = 'test=value; path=/';
|
|
310
|
-
// 模拟 getDomain 方法
|
|
311
|
-
jest.spyOn(BrowserFetcher, 'getDomain')
|
|
312
|
-
.mockReturnValue(testDomain);
|
|
313
|
-
// 直接模拟 saveCookies 方法
|
|
314
|
-
const originalSaveCookies = BrowserFetcher['saveCookies'];
|
|
315
|
-
BrowserFetcher['saveCookies'] = jest.fn().mockImplementation((url, cookies) => {
|
|
316
|
-
const domain = BrowserFetcher['getDomain'](url);
|
|
317
|
-
if (domain) {
|
|
318
|
-
BrowserFetcher['cookieStore'][domain] = cookies;
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
// 模拟 evaluate 方法返回 cookie
|
|
322
|
-
mockPage.evaluate.mockImplementation((fn) => {
|
|
323
|
-
// 检查是否是获取 document.cookie 的调用
|
|
324
|
-
if (fn && fn.toString && fn.toString().includes('document.cookie')) {
|
|
325
|
-
return Promise.resolve(testCookie);
|
|
326
|
-
}
|
|
327
|
-
return Promise.resolve('');
|
|
328
|
-
});
|
|
329
|
-
// 模拟 fetch 方法
|
|
330
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
331
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async (params) => {
|
|
332
|
-
// 模拟 fetch 方法的行为
|
|
333
|
-
const { url, saveCookies } = params;
|
|
334
|
-
// 如果有存储的 cookies,使用它们
|
|
335
|
-
const storedCookies = BrowserFetcher['getCookies'](url);
|
|
336
|
-
if (storedCookies && saveCookies) {
|
|
337
|
-
await mockPage.setExtraHTTPHeaders({
|
|
338
|
-
'Cookie': storedCookies
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
// 模拟获取页面内容
|
|
342
|
-
await mockPage.goto(url, { waitUntil: 'networkidle2' });
|
|
343
|
-
// 如果需要保存 cookies
|
|
344
|
-
if (saveCookies) {
|
|
345
|
-
const cookies = await mockPage.evaluate(() => document.cookie);
|
|
346
|
-
if (cookies) {
|
|
347
|
-
BrowserFetcher['saveCookies'](url, cookies);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return {
|
|
351
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
352
|
-
isError: false
|
|
353
|
-
};
|
|
354
|
-
});
|
|
355
|
-
// 第一次请求
|
|
356
|
-
await BrowserFetcher.fetch({
|
|
357
|
-
url: testUrl,
|
|
358
|
-
useBrowser: true,
|
|
359
|
-
saveCookies: true
|
|
360
|
-
});
|
|
361
|
-
// 验证 saveCookies 是否被调用
|
|
362
|
-
expect(BrowserFetcher['saveCookies']).toHaveBeenCalledWith(testUrl, testCookie);
|
|
363
|
-
// 验证 Cookie 是否被保存
|
|
364
|
-
expect(BrowserFetcher['cookieStore'][testDomain]).toBe(testCookie);
|
|
365
|
-
// 重置 mock 以便于验证
|
|
366
|
-
mockPage.setExtraHTTPHeaders.mockClear();
|
|
367
|
-
// 第二次请求
|
|
368
|
-
await BrowserFetcher.fetch({
|
|
369
|
-
url: testUrl,
|
|
370
|
-
useBrowser: true,
|
|
371
|
-
saveCookies: true
|
|
372
|
-
});
|
|
373
|
-
// 验证是否使用了保存的 Cookie
|
|
374
|
-
expect(mockPage.setExtraHTTPHeaders).toHaveBeenCalledWith(expect.objectContaining({
|
|
375
|
-
'Cookie': testCookie
|
|
376
|
-
}));
|
|
377
|
-
// 恢复原始方法
|
|
378
|
-
BrowserFetcher['saveCookies'] = originalSaveCookies;
|
|
379
|
-
BrowserFetcher.fetch = originalFetch;
|
|
380
|
-
});
|
|
381
|
-
it('当 saveCookies 为 false 时不应保存 Cookie (Should not save cookies when saveCookies is false)', async () => {
|
|
382
|
-
// 设置测试数据
|
|
383
|
-
const testUrl = 'https://example.com';
|
|
384
|
-
const testDomain = 'example.com';
|
|
385
|
-
const testCookie = 'test=value; path=/';
|
|
386
|
-
// 模拟 getDomain 方法
|
|
387
|
-
jest.spyOn(BrowserFetcher, 'getDomain')
|
|
388
|
-
.mockReturnValue(testDomain);
|
|
389
|
-
// 直接模拟 saveCookies 方法
|
|
390
|
-
const originalSaveCookies = BrowserFetcher['saveCookies'];
|
|
391
|
-
BrowserFetcher['saveCookies'] = jest.fn();
|
|
392
|
-
// 模拟 evaluate 方法返回 cookie
|
|
393
|
-
mockPage.evaluate.mockImplementation((fn) => {
|
|
394
|
-
// 检查是否是获取 document.cookie 的调用
|
|
395
|
-
if (fn && fn.toString && fn.toString().includes('document.cookie')) {
|
|
396
|
-
return Promise.resolve(testCookie);
|
|
397
|
-
}
|
|
398
|
-
return Promise.resolve('');
|
|
399
|
-
});
|
|
400
|
-
// 模拟 fetch 方法
|
|
401
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
402
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async (params) => {
|
|
403
|
-
// 模拟 fetch 方法的行为
|
|
404
|
-
const { url, saveCookies } = params;
|
|
405
|
-
// 模拟获取页面内容
|
|
406
|
-
await mockPage.goto(url, { waitUntil: 'networkidle2' });
|
|
407
|
-
// 如果需要保存 cookies
|
|
408
|
-
if (saveCookies) {
|
|
409
|
-
const cookies = await mockPage.evaluate(() => document.cookie);
|
|
410
|
-
if (cookies) {
|
|
411
|
-
BrowserFetcher['saveCookies'](url, cookies);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return {
|
|
415
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
416
|
-
isError: false
|
|
417
|
-
};
|
|
418
|
-
});
|
|
419
|
-
// 调用 fetch,设置 saveCookies 为 false
|
|
420
|
-
await BrowserFetcher.fetch({
|
|
421
|
-
url: testUrl,
|
|
422
|
-
useBrowser: true,
|
|
423
|
-
saveCookies: false
|
|
424
|
-
});
|
|
425
|
-
// 验证 saveCookies 没有被调用
|
|
426
|
-
expect(BrowserFetcher['saveCookies']).not.toHaveBeenCalled();
|
|
427
|
-
// 恢复原始方法
|
|
428
|
-
BrowserFetcher['saveCookies'] = originalSaveCookies;
|
|
429
|
-
BrowserFetcher.fetch = originalFetch;
|
|
430
|
-
});
|
|
431
|
-
it('应该为不同域名分别保存 Cookie (Should save cookies separately for different domains)', async () => {
|
|
432
|
-
// 设置测试数据
|
|
433
|
-
const testUrl1 = 'https://example.com/page1';
|
|
434
|
-
const testUrl2 = 'https://another-site.com/page2';
|
|
435
|
-
const testCookie1 = 'cookie1=value1';
|
|
436
|
-
const testCookie2 = 'cookie2=value2';
|
|
437
|
-
// 直接设置 cookieStore
|
|
438
|
-
BrowserFetcher['cookieStore'] = {
|
|
439
|
-
'example.com': testCookie1,
|
|
440
|
-
'another-site.com': testCookie2
|
|
441
|
-
};
|
|
442
|
-
// 模拟 getDomain 方法
|
|
443
|
-
const originalGetDomain = BrowserFetcher['getDomain'];
|
|
444
|
-
BrowserFetcher['getDomain'] = jest.fn().mockImplementation((url) => {
|
|
445
|
-
if (url.includes('example.com'))
|
|
446
|
-
return 'example.com';
|
|
447
|
-
if (url.includes('another-site.com'))
|
|
448
|
-
return 'another-site.com';
|
|
449
|
-
return '';
|
|
450
|
-
});
|
|
451
|
-
// 模拟 getCookies 方法
|
|
452
|
-
const originalGetCookies = BrowserFetcher['getCookies'];
|
|
453
|
-
BrowserFetcher['getCookies'] = jest.fn().mockImplementation((url) => {
|
|
454
|
-
const domain = BrowserFetcher['getDomain'](url);
|
|
455
|
-
return BrowserFetcher['cookieStore'][domain];
|
|
456
|
-
});
|
|
457
|
-
// 验证第一个域名的 Cookie
|
|
458
|
-
const cookie1 = BrowserFetcher['getCookies'](testUrl1);
|
|
459
|
-
expect(cookie1).toBe(testCookie1);
|
|
460
|
-
// 验证第二个域名的 Cookie
|
|
461
|
-
const cookie2 = BrowserFetcher['getCookies'](testUrl2);
|
|
462
|
-
expect(cookie2).toBe(testCookie2);
|
|
463
|
-
// 恢复原始方法
|
|
464
|
-
BrowserFetcher['getDomain'] = originalGetDomain;
|
|
465
|
-
BrowserFetcher['getCookies'] = originalGetCookies;
|
|
466
|
-
}, 10000); // 增加超时时间
|
|
467
|
-
});
|
|
468
|
-
describe('代理设置测试 (Proxy Settings Tests)', () => {
|
|
469
|
-
test('应该正确处理用户指定的代理 (Should handle user-specified proxy correctly)', async () => {
|
|
470
|
-
// 设置测试数据
|
|
471
|
-
const testUrl = 'https://example.com';
|
|
472
|
-
const testProxy = 'http://proxy.example.com:8080';
|
|
473
|
-
const testPayload = {
|
|
474
|
-
url: testUrl,
|
|
475
|
-
proxy: testProxy,
|
|
476
|
-
debug: true
|
|
477
|
-
};
|
|
478
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
479
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
480
|
-
// 模拟 fetch 方法
|
|
481
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
482
|
-
BrowserFetcher.fetch = jest.fn().mockResolvedValue({
|
|
483
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
484
|
-
isError: false
|
|
485
|
-
});
|
|
486
|
-
// 调用方法
|
|
487
|
-
await BrowserFetcher.html(testPayload);
|
|
488
|
-
// 验证 fetch 方法被调用,并且传递了正确的代理参数
|
|
489
|
-
expect(BrowserFetcher.fetch).toHaveBeenCalledWith(expect.objectContaining({
|
|
490
|
-
url: testUrl,
|
|
491
|
-
proxy: testProxy
|
|
492
|
-
}));
|
|
493
|
-
// 恢复原始方法
|
|
494
|
-
BrowserFetcher.fetch = originalFetch;
|
|
495
|
-
});
|
|
496
|
-
test('应该正确处理系统代理设置 (Should handle system proxy settings correctly)', async () => {
|
|
497
|
-
// 模拟环境变量
|
|
498
|
-
const originalEnv = process.env;
|
|
499
|
-
process.env = {
|
|
500
|
-
...originalEnv,
|
|
501
|
-
HTTP_PROXY: 'http://system-proxy.example.com:8080'
|
|
502
|
-
};
|
|
503
|
-
// 模拟 execSync
|
|
504
|
-
const { execSync } = require('child_process');
|
|
505
|
-
execSync.mockReturnValue('HTTP_PROXY=http://system-proxy.example.com:8080');
|
|
506
|
-
// 设置测试数据
|
|
507
|
-
const testUrl = 'https://example.com';
|
|
508
|
-
const testPayload = {
|
|
509
|
-
url: testUrl,
|
|
510
|
-
useSystemProxy: true,
|
|
511
|
-
debug: true
|
|
512
|
-
};
|
|
513
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
514
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
515
|
-
// 模拟 fetch 方法
|
|
516
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
517
|
-
BrowserFetcher.fetch = jest.fn().mockResolvedValue({
|
|
518
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
519
|
-
isError: false
|
|
520
|
-
});
|
|
521
|
-
// 调用方法
|
|
522
|
-
await BrowserFetcher.html(testPayload);
|
|
523
|
-
// 验证 fetch 方法被调用,并且传递了正确的系统代理参数
|
|
524
|
-
expect(BrowserFetcher.fetch).toHaveBeenCalledWith(expect.objectContaining({
|
|
525
|
-
url: testUrl,
|
|
526
|
-
useSystemProxy: true
|
|
527
|
-
}));
|
|
528
|
-
// 恢复原始方法和环境变量
|
|
529
|
-
BrowserFetcher.fetch = originalFetch;
|
|
530
|
-
process.env = originalEnv;
|
|
531
|
-
});
|
|
532
|
-
test('当 useSystemProxy 为 false 时不应使用系统代理 (Should not use system proxy when useSystemProxy is false)', async () => {
|
|
533
|
-
// 模拟环境变量
|
|
534
|
-
const originalEnv = process.env;
|
|
535
|
-
process.env = {
|
|
536
|
-
...originalEnv,
|
|
537
|
-
HTTP_PROXY: 'http://system-proxy.example.com:8080'
|
|
538
|
-
};
|
|
539
|
-
// 设置测试数据
|
|
540
|
-
const testUrl = 'https://example.com';
|
|
541
|
-
const testPayload = {
|
|
542
|
-
url: testUrl,
|
|
543
|
-
useSystemProxy: false,
|
|
544
|
-
debug: true
|
|
545
|
-
};
|
|
546
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
547
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
548
|
-
// 模拟 fetch 方法
|
|
549
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
550
|
-
BrowserFetcher.fetch = jest.fn().mockResolvedValue({
|
|
551
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
552
|
-
isError: false
|
|
553
|
-
});
|
|
554
|
-
// 调用方法
|
|
555
|
-
await BrowserFetcher.html(testPayload);
|
|
556
|
-
// 验证 fetch 方法被调用,并且传递了正确的 useSystemProxy 参数
|
|
557
|
-
expect(BrowserFetcher.fetch).toHaveBeenCalledWith(expect.objectContaining({
|
|
558
|
-
url: testUrl,
|
|
559
|
-
useSystemProxy: false
|
|
560
|
-
}));
|
|
561
|
-
// 恢复原始方法和环境变量
|
|
562
|
-
BrowserFetcher.fetch = originalFetch;
|
|
563
|
-
process.env = originalEnv;
|
|
564
|
-
});
|
|
565
|
-
test('用户指定的代理应优先于系统代理 (User-specified proxy should take precedence over system proxy)', async () => {
|
|
566
|
-
// 模拟环境变量
|
|
567
|
-
const originalEnv = process.env;
|
|
568
|
-
process.env = {
|
|
569
|
-
...originalEnv,
|
|
570
|
-
HTTP_PROXY: 'http://system-proxy.example.com:8080'
|
|
571
|
-
};
|
|
572
|
-
// 设置测试数据
|
|
573
|
-
const testUrl = 'https://example.com';
|
|
574
|
-
const testProxy = 'http://user-proxy.example.com:8080';
|
|
575
|
-
const testPayload = {
|
|
576
|
-
url: testUrl,
|
|
577
|
-
proxy: testProxy,
|
|
578
|
-
useSystemProxy: true,
|
|
579
|
-
debug: true
|
|
580
|
-
};
|
|
581
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
582
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
583
|
-
// 模拟 fetch 方法
|
|
584
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
585
|
-
BrowserFetcher.fetch = jest.fn().mockResolvedValue({
|
|
586
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
587
|
-
isError: false
|
|
588
|
-
});
|
|
589
|
-
// 调用方法
|
|
590
|
-
await BrowserFetcher.html(testPayload);
|
|
591
|
-
// 验证 fetch 方法被调用,并且传递了正确的代理参数(用户指定的代理)
|
|
592
|
-
expect(BrowserFetcher.fetch).toHaveBeenCalledWith(expect.objectContaining({
|
|
593
|
-
url: testUrl,
|
|
594
|
-
proxy: testProxy,
|
|
595
|
-
useSystemProxy: true
|
|
596
|
-
}));
|
|
597
|
-
// 恢复原始方法和环境变量
|
|
598
|
-
BrowserFetcher.fetch = originalFetch;
|
|
599
|
-
process.env = originalEnv;
|
|
600
|
-
});
|
|
601
|
-
});
|
|
602
|
-
describe('内容获取测试 (Content Fetching Tests)', () => {
|
|
603
|
-
test('应该正确获取 HTML 内容 (Should fetch HTML content correctly)', async () => {
|
|
604
|
-
// 设置测试数据
|
|
605
|
-
const testUrl = 'https://example.com';
|
|
606
|
-
const testHtml = '<html><body><h1>Test Content</h1></body></html>';
|
|
607
|
-
const testPayload = {
|
|
608
|
-
url: testUrl,
|
|
609
|
-
debug: true
|
|
610
|
-
};
|
|
611
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
612
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
613
|
-
// 模拟页面内容
|
|
614
|
-
mockPage.content.mockResolvedValue(testHtml);
|
|
615
|
-
// 模拟 fetch 方法
|
|
616
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
617
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
618
|
-
return {
|
|
619
|
-
content: [{ type: 'text', text: testHtml }],
|
|
620
|
-
isError: false
|
|
621
|
-
};
|
|
622
|
-
});
|
|
623
|
-
// 调用方法
|
|
624
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
625
|
-
// 验证结果
|
|
626
|
-
expect(result).toEqual({
|
|
627
|
-
content: [{ type: 'text', text: testHtml }],
|
|
628
|
-
isError: false
|
|
629
|
-
});
|
|
630
|
-
// 恢复原始方法
|
|
631
|
-
BrowserFetcher.fetch = originalFetch;
|
|
632
|
-
});
|
|
633
|
-
test('应该正确获取 JSON 内容 (Should fetch JSON content correctly)', async () => {
|
|
634
|
-
// 设置测试数据
|
|
635
|
-
const testUrl = 'https://api.example.com/data.json';
|
|
636
|
-
const testJson = { name: 'Test', value: 123 };
|
|
637
|
-
const testPayload = {
|
|
638
|
-
url: testUrl,
|
|
639
|
-
debug: true
|
|
640
|
-
};
|
|
641
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
642
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
643
|
-
// 模拟页面内容
|
|
644
|
-
mockPage.content.mockResolvedValue(JSON.stringify(testJson));
|
|
645
|
-
// 模拟 fetch 方法
|
|
646
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
647
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
648
|
-
return {
|
|
649
|
-
content: [{ type: 'text', text: JSON.stringify(testJson) }],
|
|
650
|
-
isError: false
|
|
651
|
-
};
|
|
652
|
-
});
|
|
653
|
-
// 调用方法
|
|
654
|
-
const result = await BrowserFetcher.json(testPayload);
|
|
655
|
-
// 验证结果
|
|
656
|
-
expect(result).toEqual({
|
|
657
|
-
content: [{ type: 'text', text: JSON.stringify(testJson) }],
|
|
658
|
-
isError: false
|
|
659
|
-
});
|
|
660
|
-
// 恢复原始方法
|
|
661
|
-
BrowserFetcher.fetch = originalFetch;
|
|
662
|
-
});
|
|
663
|
-
test('应该正确获取纯文本内容 (Should fetch text content correctly)', async () => {
|
|
664
|
-
// 设置测试数据
|
|
665
|
-
const testUrl = 'https://example.com/text';
|
|
666
|
-
const testHtml = '<html><body><h1>Test Content</h1><p>This is a paragraph.</p></body></html>';
|
|
667
|
-
const expectedText = 'Test Content This is a paragraph.';
|
|
668
|
-
const testPayload = {
|
|
669
|
-
url: testUrl,
|
|
670
|
-
debug: true
|
|
671
|
-
};
|
|
672
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
673
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
674
|
-
// 模拟页面内容
|
|
675
|
-
mockPage.content.mockResolvedValue(testHtml);
|
|
676
|
-
// 模拟 JSDOM
|
|
677
|
-
const mockJsdom = require('jsdom').JSDOM;
|
|
678
|
-
mockJsdom.mockImplementation(() => ({
|
|
679
|
-
window: {
|
|
680
|
-
document: {
|
|
681
|
-
body: {
|
|
682
|
-
textContent: expectedText
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}));
|
|
687
|
-
// 模拟 fetch 方法
|
|
688
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
689
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
690
|
-
return {
|
|
691
|
-
content: [{ type: 'text', text: testHtml }],
|
|
692
|
-
isError: false
|
|
693
|
-
};
|
|
694
|
-
});
|
|
695
|
-
// 调用方法
|
|
696
|
-
const result = await BrowserFetcher.txt(testPayload);
|
|
697
|
-
// 验证结果
|
|
698
|
-
expect(result).toEqual({
|
|
699
|
-
content: [{ type: 'text', text: expectedText }],
|
|
700
|
-
isError: false
|
|
701
|
-
});
|
|
702
|
-
// 恢复原始方法
|
|
703
|
-
BrowserFetcher.fetch = originalFetch;
|
|
704
|
-
});
|
|
705
|
-
test('应该正确获取 Markdown 内容 (Should fetch Markdown content correctly)', async () => {
|
|
706
|
-
// 设置测试数据
|
|
707
|
-
const testUrl = 'https://example.com/markdown';
|
|
708
|
-
const testHtml = '<html><body><h1>Test Content</h1><p>This is a paragraph.</p></body></html>';
|
|
709
|
-
const expectedMarkdown = '# 转换后的Markdown内容';
|
|
710
|
-
const testPayload = {
|
|
711
|
-
url: testUrl,
|
|
712
|
-
debug: true
|
|
713
|
-
};
|
|
714
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
715
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
716
|
-
// 模拟页面内容
|
|
717
|
-
mockPage.content.mockResolvedValue(testHtml);
|
|
718
|
-
// 模拟 TurndownService
|
|
719
|
-
const mockTurndownService = require('turndown');
|
|
720
|
-
mockTurndownService.mockImplementation(() => ({
|
|
721
|
-
addRule: jest.fn(),
|
|
722
|
-
turndown: jest.fn().mockReturnValue(expectedMarkdown)
|
|
723
|
-
}));
|
|
724
|
-
// 模拟 fetch 方法
|
|
725
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
726
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
727
|
-
return {
|
|
728
|
-
content: [{ type: 'text', text: testHtml }],
|
|
729
|
-
isError: false
|
|
730
|
-
};
|
|
731
|
-
});
|
|
732
|
-
// 调用方法
|
|
733
|
-
const result = await BrowserFetcher.markdown(testPayload);
|
|
734
|
-
// 验证结果
|
|
735
|
-
expect(result).toEqual({
|
|
736
|
-
content: [{ type: 'text', text: expectedMarkdown }],
|
|
737
|
-
isError: false
|
|
738
|
-
});
|
|
739
|
-
// 恢复原始方法
|
|
740
|
-
BrowserFetcher.fetch = originalFetch;
|
|
741
|
-
});
|
|
742
|
-
test('应该正确处理滚动到底部的选项 (Should handle scrollToBottom option correctly)', async () => {
|
|
743
|
-
// 设置测试数据
|
|
744
|
-
const testUrl = 'https://example.com';
|
|
745
|
-
const testHtml = '<html><body><h1>Test Content</h1></body></html>';
|
|
746
|
-
const testPayload = {
|
|
747
|
-
url: testUrl,
|
|
748
|
-
scrollToBottom: true,
|
|
749
|
-
debug: true
|
|
750
|
-
};
|
|
751
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
752
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
753
|
-
// 模拟页面内容和评估方法
|
|
754
|
-
mockPage.content.mockResolvedValue(testHtml);
|
|
755
|
-
mockPage.evaluate.mockImplementation((fn) => {
|
|
756
|
-
// 检查是否是自动滚动的调用
|
|
757
|
-
if (fn && fn.toString && fn.toString().includes('scrollBy')) {
|
|
758
|
-
return Promise.resolve();
|
|
759
|
-
}
|
|
760
|
-
return Promise.resolve('');
|
|
761
|
-
});
|
|
762
|
-
// 模拟 fetch 方法
|
|
763
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
764
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async (params) => {
|
|
765
|
-
// 如果需要滚动到底部
|
|
766
|
-
if (params.scrollToBottom) {
|
|
767
|
-
// 模拟自动滚动
|
|
768
|
-
await mockPage.evaluate(() => { });
|
|
769
|
-
}
|
|
770
|
-
return {
|
|
771
|
-
content: [{ type: 'text', text: testHtml }],
|
|
772
|
-
isError: false
|
|
773
|
-
};
|
|
774
|
-
});
|
|
775
|
-
// 调用方法
|
|
776
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
777
|
-
// 验证结果
|
|
778
|
-
expect(result).toEqual({
|
|
779
|
-
content: [{ type: 'text', text: testHtml }],
|
|
780
|
-
isError: false
|
|
781
|
-
});
|
|
782
|
-
// 验证 fetch 方法被调用,并且传递了正确的 scrollToBottom 参数
|
|
783
|
-
expect(BrowserFetcher.fetch).toHaveBeenCalledWith(expect.objectContaining({
|
|
784
|
-
url: testUrl,
|
|
785
|
-
scrollToBottom: true
|
|
786
|
-
}));
|
|
787
|
-
// 恢复原始方法
|
|
788
|
-
BrowserFetcher.fetch = originalFetch;
|
|
789
|
-
});
|
|
790
|
-
});
|
|
791
|
-
describe('错误处理和重试测试 (Error Handling and Retry Tests)', () => {
|
|
792
|
-
test('应该正确处理网络错误并重试 (Should handle network errors and retry correctly)', async () => {
|
|
793
|
-
// 设置测试数据
|
|
794
|
-
const testUrl = 'https://example.com';
|
|
795
|
-
const testPayload = {
|
|
796
|
-
url: testUrl,
|
|
797
|
-
debug: true
|
|
798
|
-
};
|
|
799
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
800
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
801
|
-
// 模拟 fetch 方法抛出错误,然后成功
|
|
802
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
803
|
-
let fetchCallCount = 0;
|
|
804
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
805
|
-
fetchCallCount++;
|
|
806
|
-
if (fetchCallCount === 1) {
|
|
807
|
-
throw new Error('Network error');
|
|
808
|
-
}
|
|
809
|
-
return {
|
|
810
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
811
|
-
isError: false
|
|
812
|
-
};
|
|
813
|
-
});
|
|
814
|
-
// 模拟 setTimeout
|
|
815
|
-
jest.useFakeTimers();
|
|
816
|
-
const originalSetTimeout = global.setTimeout;
|
|
817
|
-
// @ts-ignore - 忽略 setTimeout 类型错误
|
|
818
|
-
global.setTimeout = jest.fn().mockImplementation((callback, ms) => {
|
|
819
|
-
callback();
|
|
820
|
-
return null;
|
|
821
|
-
});
|
|
822
|
-
// 调用方法
|
|
823
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
824
|
-
// 验证 fetch 被调用了两次(一次失败,一次成功)
|
|
825
|
-
expect(fetchCallCount).toBe(2);
|
|
826
|
-
// 验证结果
|
|
827
|
-
expect(result).toEqual({
|
|
828
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
829
|
-
isError: false
|
|
830
|
-
});
|
|
831
|
-
// 恢复原始方法
|
|
832
|
-
BrowserFetcher.fetch = originalFetch;
|
|
833
|
-
global.setTimeout = originalSetTimeout;
|
|
834
|
-
jest.useRealTimers();
|
|
835
|
-
});
|
|
836
|
-
test('应该在超过最大重试次数后返回错误 (Should return error after exceeding maximum retry count)', async () => {
|
|
837
|
-
// 设置测试数据
|
|
838
|
-
const testUrl = 'https://example.com';
|
|
839
|
-
const testPayload = {
|
|
840
|
-
url: testUrl,
|
|
841
|
-
debug: true
|
|
842
|
-
};
|
|
843
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
844
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
845
|
-
// 模拟 fetch 方法始终抛出错误
|
|
846
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
847
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
848
|
-
throw new Error('Network error');
|
|
849
|
-
});
|
|
850
|
-
// 模拟 setTimeout
|
|
851
|
-
jest.useFakeTimers();
|
|
852
|
-
const originalSetTimeout = global.setTimeout;
|
|
853
|
-
// @ts-ignore - 忽略 setTimeout 类型错误
|
|
854
|
-
global.setTimeout = jest.fn().mockImplementation((callback, ms) => {
|
|
855
|
-
callback();
|
|
856
|
-
return null;
|
|
857
|
-
});
|
|
858
|
-
// 调用方法
|
|
859
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
860
|
-
// 验证 fetch 被调用了 3 次(初始 + 2 次重试)
|
|
861
|
-
expect(BrowserFetcher.fetch).toHaveBeenCalledTimes(3);
|
|
862
|
-
// 验证结果是错误
|
|
863
|
-
expect(result.isError).toBe(true);
|
|
864
|
-
// 恢复原始方法
|
|
865
|
-
BrowserFetcher.fetch = originalFetch;
|
|
866
|
-
global.setTimeout = originalSetTimeout;
|
|
867
|
-
jest.useRealTimers();
|
|
868
|
-
});
|
|
869
|
-
test('应该正确处理 Cloudflare 保护 (Should handle Cloudflare protection correctly)', async () => {
|
|
870
|
-
// 设置测试数据
|
|
871
|
-
const testUrl = 'https://example.com';
|
|
872
|
-
const testPayload = {
|
|
873
|
-
url: testUrl,
|
|
874
|
-
debug: true
|
|
875
|
-
};
|
|
876
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
877
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
878
|
-
// 模拟页面内容和评估方法
|
|
879
|
-
mockPage.content.mockResolvedValue('<html><body><h1>Test Content</h1></body></html>');
|
|
880
|
-
// 模拟 fetch 方法
|
|
881
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
882
|
-
BrowserFetcher.fetch = jest.fn().mockResolvedValue({
|
|
883
|
-
content: [{ type: 'text', text: '<html><body>Test content</body></html>' }],
|
|
884
|
-
isError: false
|
|
885
|
-
});
|
|
886
|
-
// 调用方法
|
|
887
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
888
|
-
// 验证结果
|
|
889
|
-
expect(result.isError).toBe(false);
|
|
890
|
-
// 恢复原始方法
|
|
891
|
-
BrowserFetcher.fetch = originalFetch;
|
|
892
|
-
}, 10000); // 增加超时时间
|
|
893
|
-
test('应该正确处理 about:blank 和 closeBrowser 选项 (Should handle about:blank and closeBrowser option correctly)', async () => {
|
|
894
|
-
// 设置测试数据
|
|
895
|
-
const testPayload = {
|
|
896
|
-
url: 'about:blank',
|
|
897
|
-
closeBrowser: true,
|
|
898
|
-
debug: true
|
|
899
|
-
};
|
|
900
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
901
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
902
|
-
// 设置浏览器实例
|
|
903
|
-
// @ts-ignore - 访问私有属性用于测试
|
|
904
|
-
BrowserFetcher.browser = mockBrowser;
|
|
905
|
-
// 调用方法
|
|
906
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
907
|
-
// 验证浏览器关闭方法被调用
|
|
908
|
-
expect(mockBrowser.close).toHaveBeenCalled();
|
|
909
|
-
// 验证结果
|
|
910
|
-
expect(result).toEqual({
|
|
911
|
-
content: [{ type: 'text', text: 'Browser closed successfully' }],
|
|
912
|
-
isError: false
|
|
913
|
-
});
|
|
914
|
-
});
|
|
915
|
-
test('应该在内容过长时截断内容 (Should truncate content when it is too long)', async () => {
|
|
916
|
-
// 设置测试数据
|
|
917
|
-
const testUrl = 'https://example.com';
|
|
918
|
-
const testPayload = {
|
|
919
|
-
url: testUrl,
|
|
920
|
-
debug: true
|
|
921
|
-
};
|
|
922
|
-
// 生成一个超过 10MB 的内容
|
|
923
|
-
const longContent = 'a'.repeat(11 * 1024 * 1024); // 11MB
|
|
924
|
-
// 设置 puppeteer-extra 的 launch 方法返回值
|
|
925
|
-
mockPuppeteerExtra.launch.mockResolvedValue(mockBrowser);
|
|
926
|
-
// 模拟页面内容
|
|
927
|
-
mockPage.content.mockResolvedValue(longContent);
|
|
928
|
-
// 模拟 fetch 方法
|
|
929
|
-
const originalFetch = BrowserFetcher.fetch;
|
|
930
|
-
BrowserFetcher.fetch = jest.fn().mockImplementation(async () => {
|
|
931
|
-
// 获取页面内容
|
|
932
|
-
const content = await mockPage.content();
|
|
933
|
-
// 如果内容太长,截断它
|
|
934
|
-
let finalContent = content;
|
|
935
|
-
if (content.length > 10 * 1024 * 1024) { // 10MB
|
|
936
|
-
finalContent = content.substring(0, 10 * 1024 * 1024);
|
|
937
|
-
}
|
|
938
|
-
return {
|
|
939
|
-
content: [{ type: 'text', text: finalContent }],
|
|
940
|
-
isError: false
|
|
941
|
-
};
|
|
942
|
-
});
|
|
943
|
-
// 调用方法
|
|
944
|
-
const result = await BrowserFetcher.html(testPayload);
|
|
945
|
-
// 验证结果内容长度不超过 10MB
|
|
946
|
-
expect(result.content[0].text.length).toBe(10 * 1024 * 1024);
|
|
947
|
-
// 恢复原始方法
|
|
948
|
-
BrowserFetcher.fetch = originalFetch;
|
|
949
|
-
});
|
|
950
|
-
});
|
|
951
|
-
});
|