本文档定义了 Snapka 系列库的标准 API 规范。所有 Snapka 实现(如 @snapka/playwright、@snapka/puppeteer 等)都应遵循此规范,以确保 API 的一致性和可互换性。
- 一致性: 所有实现必须提供相同的核心 API
- 可扩展性: 允许特定实现添加专有功能
- 类型安全: 使用 TypeScript 提供完整的类型定义
- 向后兼容: API 变更应保持向后兼容
- 类型完整性: 即使某些参数在特定实现中无法实际使用,也必须在类型定义中保留,以确保 API 的一致性
每个实现必须导出一个名为 snapka 的对象:
export const snapka = {
browsers: Browser[],
launch: (options?: LaunchOptions) => Promise<Core>,
connect: (options: ConnectOptions) => Promise<Core>
}- 类型:
Browser[] - 必需: ✅ 是
- 描述: 存储所有已启动的浏览器实例
- 用途: 用于跟踪和管理浏览器实例
- 签名:
(options?: LaunchOptions) => Promise<Core> - 必需: ✅ 是
- 描述: 启动一个新的浏览器实例
- 参数:
options: 可选的启动配置,必须继承自SnapkaLaunchOptions
- 返回: 返回核心控制类实例
- 行为:
- 解析浏览器可执行路径(查找或下载)
- 启动浏览器
- 将浏览器实例添加到
browsers数组 - 创建并返回核心控制类实例
- 签名:
(options: ConnectOptions) => Promise<Core> - 必需: ✅ 是
- 描述: 连接到已启动的浏览器实例
- 参数:
options: 必需的连接配置,必须继承自SnapkaConnectOptions
- 返回: 返回核心控制类实例
- 行为:
- 连接到指定的浏览器 WebSocket 端点
- 将浏览器实例添加到
browsers数组 - 创建并返回核心控制类实例
所有实现的启动选项必须继承此接口:
interface SnapkaLaunchOptions {
// ===== 必需字段 =====
// ===== 可选字段 =====
/** 浏览器可执行文件路径 */
executablePath?: string
/** 是否启用浏览器查找功能 */
findBrowser?: boolean
/** 是否开启调试模式 */
debug?: boolean
/** 浏览器下载配置 */
download?: {
/** 是否允许下载 */
enable?: boolean
/** 下载缓存目录 */
dir?: string
/** 浏览器版本 */
version?: string
/** 浏览器类型 */
browser?: 'chrome' | 'chromium' | 'chrome-headless-shell'
/** 自定义下载源 */
baseUrl?: string
}
/** 无头模式 */
headless?: boolean | 'new' | 'shell'
/** 默认视口大小 */
defaultViewport?: { width: number; height: number } | null
/** 最大同时打开的页面数 */
maxOpenPages?: number
/** 页面管理模式 */
pageMode?: 'reuse' | 'disposable'
/** 页面空闲超时时间(毫秒) */
pageIdleTimeout?: number
}字段说明:
- executablePath: 自定义浏览器路径,优先级最高
- findBrowser: 是否自动查找系统浏览器(默认:
true) - debug: 调试模式,在支持的平台优先使用有界面浏览器
- download: 浏览器下载配置
enable: 是否允许自动下载(默认:true)dir: 缓存目录路径version: 浏览器版本标签(默认:'stable')browser: 浏览器类型baseUrl: 自定义下载镜像源
- headless: 无头模式配置
true: 标准无头模式'new'/'shell': 使用 Headless Shellfalse: 有界面模式
- defaultViewport: 默认视口大小
- maxOpenPages: 并发页面数限制(默认:
10) - pageMode: 页面管理策略
'reuse': 复用模式(默认,推荐)'disposable': 一次性模式
- pageIdleTimeout: 空闲页面超时时间(默认:
60000)
所有实现的连接选项必须继承此接口:
interface SnapkaConnectOptions {
// ===== 必需字段 =====
/** 浏览器 WebSocket 连接地址 */
baseUrl?: string
// ===== 可选字段 =====
/** 默认视口大小 */
defaultViewport?: { width: number; height: number } | null
/** 最大同时打开的页面数 */
maxOpenPages?: number
/** 页面管理模式 */
pageMode?: 'reuse' | 'disposable'
/** 页面空闲超时时间(毫秒) */
pageIdleTimeout?: number
}实现可以扩展基础选项,但必须遵循以下规则:
// ✅ 正确:继承并扩展
interface PlaywrightLaunchOptions extends SnapkaLaunchOptions {
// Playwright 特有选项
channel?: string
chromiumSandbox?: boolean
// ...
}
// ❌ 错误:不继承基础接口
interface PlaywrightLaunchOptions {
executablePath?: string
// ...
}重要规则:所有实现必须在类型定义中包含标准中定义的所有参数,即使某些参数在该实现中无法实际使用。
- API 一致性: 确保用户在不同实现间切换时,类型定义保持一致
- IDE 支持: 提供统一的代码补全和类型检查体验
- 文档完整性: 避免用户困惑某个参数是否可用
- 未来兼容性: 为将来可能的实现留出空间
// ✅ 正确:即使 Playwright 不支持 clip 参数,也要在类型中保留
interface PlaywrightScreenshotOptions<T> extends SnapkaScreenshotOptions<T> {
// 标准参数都已通过继承包含
// 即使 clip 在 Playwright 中无法使用,也不应该 Omit
// Playwright 特有的额外选项
playwright?: {
animations?: 'disabled' | 'allow'
caret?: 'hide' | 'initial'
}
}
// ❌ 错误:不应该排除标准参数
interface PlaywrightScreenshotOptions<T>
extends Omit<SnapkaScreenshotOptions<T>, 'clip'> {
// 这样做会破坏类型一致性
}当实现无法支持某个标准参数时,应该:
- 类型定义中保留: 不要从类型中移除该参数
- 运行时处理: 在代码中静默忽略或记录警告
- 文档说明: 在文档中明确说明哪些参数不被支持
示例:
// 类型定义 - 保留所有标准参数
interface PlaywrightScreenshotOptions<T> extends SnapkaScreenshotOptions<T> {
// 所有标准参数都可用(包括 clip)
}
// 实现代码 - 处理不支持的参数
class PlaywrightCore {
async screenshot<T>(options: PlaywrightScreenshotOptions<T>) {
// 如果用户传入了 clip 参数,记录警告
if (options.clip) {
console.warn('Playwright 实现不支持 clip 参数,该参数将被忽略')
}
// 继续执行截图逻辑
// ...
}
}在实现的文档中,应明确说明不支持的参数:
## 参数支持情况
以下标准参数在 Playwright 实现中不被支持:
- `clip`: Playwright 使用不同的方式实现裁剪功能
- 替代方案: 使用 `selector` 参数选择特定元素
虽然这些参数在类型定义中存在,但运行时会被忽略。每个实现必须提供一个核心控制类(如 PlaywrightCore、PuppeteerCore),该类必须实现以下接口:
interface SnapkaCore {
// ===== 必需属性 =====
/** 引擎名称 */
readonly engine: string
// ===== 必需方法 =====
/** 获取浏览器可执行路径 */
executablePath(): string | null
/** 重启浏览器实例 */
restart(): Promise<void>
/** 关闭浏览器并清理资源 */
close(): Promise<void>
/** 执行页面截图 */
screenshot<T extends 'base64' | 'binary' = 'binary'>(
options: SnapkaScreenshotOptions<T>
): Promise<ScreenshotResult<T>>
/** 执行视口分片截图 */
screenshotViewport<T extends 'base64' | 'binary' = 'binary'>(
options: SnapkaScreenshotViewportOptions<T>
): Promise<ScreenshotViewportResult<T>>
}interface ScreenshotResult<T extends 'base64' | 'binary'> {
/** 执行截图的函数 */
run: () => Promise<T extends 'base64' ? string : Uint8Array>
/** 页面实例(底层库类型) */
page: any
/** 浏览器实例(底层库类型) */
browser: any
}
interface ScreenshotViewportResult<T extends 'base64' | 'binary'> {
/** 执行分片截图的函数 */
run: () => Promise<T extends 'base64' ? string[] : Uint8Array[]>
/** 页面实例(底层库类型) */
page: any
/** 浏览器实例(底层库类型) */
browser: any
}- 类型:
string - 必需: ✅ 是
- 访问器: getter
- 描述: 返回底层引擎名称
- 示例:
'playwright'、'puppeteer'、'selenium'
- 签名:
() => string | null - 必需: ✅ 是
- 返回: 浏览器可执行文件路径,连接模式下返回
null - 行为: 返回当前使用的浏览器二进制文件路径
- 签名:
() => Promise<void> - 必需: ✅ 是
- 返回:
Promise<void> - 行为:
- 停止空闲检查定时器
- 关闭所有页面/上下文
- 关闭当前浏览器
- 使用相同配置重新启动/连接浏览器
- 恢复空闲检查定时器
- 签名:
() => Promise<void> - 必需: ✅ 是
- 返回:
Promise<void> - 行为:
- 停止空闲检查定时器
- 关闭所有页面/上下文
- 关闭浏览器实例
- 清理所有资源
- 签名:
<T extends 'base64' | 'binary'>(options: SnapkaScreenshotOptions<T>) => Promise<ScreenshotResult<T>> - 必需: ✅ 是
- 参数: 截图配置选项
- 返回: 包含
run函数、page和browser的对象 - 行为:
- 从池中获取或创建新页面
- 导航到指定 URL
- 等待配置的条件
- 返回包含截图执行函数的对象
- 执行
run()后自动释放页面资源
- 签名:
<T extends 'base64' | 'binary'>(options: SnapkaScreenshotViewportOptions<T>) => Promise<ScreenshotViewportResult<T>> - 必需: ✅ 是
- 参数: 分片截图配置选项
- 返回: 包含
run函数、page和browser的对象 - 行为:
- 从池中获取或创建新页面
- 导航到指定 URL
- 等待配置的条件
- 返回包含分片截图执行函数的对象
- 执行
run()后自动释放页面资源
interface SnapkaScreenshotOptions<T extends 'base64' | 'binary' = 'binary'> {
// ===== 必需字段 =====
/** 要截图的页面 URL */
file: string
// ===== 可选字段 =====
/** 图片格式 */
type?: 'png' | 'jpeg' | 'webp'
/** 编码方式 */
encoding?: T
/** 是否截取完整页面 */
fullPage?: boolean
/** 元素选择器 */
selector?: string
/** 图片质量(1-100,仅对 jpeg/webp 有效) */
quality?: number
/** 是否省略背景 */
omitBackground?: boolean
/** 保存路径 */
path?: string
/** 页面跳转参数 */
pageGotoParams?: {
/** 超时时间(毫秒) */
timeout?: number
/** 等待条件 */
waitUntil?: string
}
/** 等待元素选择器 */
waitForSelector?: string | string[]
/** 等待函数 */
waitForFunction?: string | string[]
/** 等待请求 */
waitForRequest?: string | string[]
/** 等待响应 */
waitForResponse?: string | string[]
/** HTTP 请求头 */
headers?: Record<string, string>
/** 重试次数 */
retry?: number
}字段说明:
- file: ✅ 必需,页面 URL
- type: 图片格式,默认
'png' - encoding: 编码方式,默认
'binary' - fullPage: 是否截取完整页面,默认
false - selector: CSS 选择器,指定截图元素
- quality: 图片质量,仅对
jpeg/webp有效 - omitBackground: 是否省略背景,默认
false - path: 保存文件路径
- pageGotoParams: 页面导航参数
- waitForSelector: 等待元素加载
- waitForFunction: 等待 JavaScript 函数返回 true
- waitForRequest: 等待特定请求
- waitForResponse: 等待特定响应
- headers: 自定义 HTTP 请求头
- retry: 重试次数,默认
1
interface SnapkaScreenshotViewportOptions<T extends 'base64' | 'binary' = 'binary'>
extends SnapkaScreenshotOptions<T> {
// ===== 必需字段 =====
/** 元素选择器(此模式下必需) */
selector: string
// ===== 可选字段 =====
/** 视口高度(每个分片的高度) */
viewportHeight?: number
}实现可以添加特定引擎的选项,但应作为独立字段:
// ✅ 正确:使用独立的命名空间字段
interface PlaywrightScreenshotOptions<T> extends SnapkaScreenshotOptions<T> {
/** Playwright 特定选项 */
playwright?: {
animations?: 'disabled' | 'allow'
caret?: 'hide' | 'initial'
// ...
}
}
// ✅ 也可以直接扩展(明确标注)
interface PuppeteerScreenshotOptions<T> extends SnapkaScreenshotOptions<T> {
/** 裁剪区域(Puppeteer 专有) */
clip?: {
x: number
y: number
width: number
height: number
}
}所有实现必须按以下优先级解析浏览器路径:
- 自定义路径:
options.executablePath - 系统查找: 如果
options.findBrowser !== false - 自动下载: 如果
options.download.enable !== false
伪代码:
async function getPath(options: LaunchOptions): Promise<string | null> {
// 1. 验证自定义路径
if (options.executablePath) {
if (!exists(options.executablePath)) {
throw new Error('executablePath 指定的路径不存在')
}
return options.executablePath
}
// 2. 查找系统浏览器
if (options.findBrowser !== false) {
const foundPath = await findBrowser(options.debug)
if (foundPath) return foundPath
}
// 3. 下载浏览器
if (options.download?.enable !== false) {
return await downloadBrowser(options.download)
}
return null
}所有实现必须实现页面池机制:
规则:
-
复用模式 (
pageMode: 'reuse'):- 页面使用完毕后返回池中
- 池满时关闭超出的页面
- 空闲超时后自动清理
-
一次性模式 (
pageMode: 'disposable'):- 每次使用完立即销毁页面
- 不使用页面池
-
并发控制:
- 使用
maxOpenPages限制并发数 - 超出限制的请求需排队等待
- 使用
-
空闲检查:
- 仅在复用模式且
pageIdleTimeout > 0时启动 - 定期(建议 30 秒)清理超时的空闲页面
- 仅在复用模式且
所有实现必须支持重试机制:
规则:
- 根据
options.retry参数重试 - 使用指数退避策略延迟重试
- 记录每次重试的错误信息
- 达到最大重试次数后抛出错误
伪代码:
async function retryExecute<T>(
fn: () => Promise<T>,
maxRetries: number,
operation: string
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn()
} catch (error) {
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000)
console.warn(`${operation}失败 (第 ${attempt}/${maxRetries} 次): ${error.message}`)
await sleep(delay)
} else {
throw new Error(`${operation}在 ${maxRetries} 次尝试后失败: ${error.message}`)
}
}
}
}所有实现必须支持以下等待条件:
- waitForSelector: 等待元素出现
- waitForFunction: 等待 JavaScript 函数返回 true
- waitForRequest: 等待特定网络请求
- waitForResponse: 等待特定网络响应
规则:
- 支持字符串或字符串数组
- 使用
Promise.allSettled()并行等待所有条件 - 超时不应抛出错误,仅记录警告
- 元素不存在时跳过等待
所有实现必须包含以下测试:
-
基础功能测试
- 启动浏览器
- 连接浏览器
- 关闭浏览器
- 重启浏览器
-
截图功能测试
- PNG 格式截图
- JPEG 格式截图
- WebP 格式截图(如支持)
- Base64 编码
- Binary 编码
- 完整页面截图
- 元素截图
- 分片截图
-
配置选项测试
- 自定义浏览器路径
- 查找系统浏览器
- 下载浏览器
- 页面池复用
- 一次性模式
-
等待条件测试
- 等待元素
- 等待函数
- 等待请求
- 等待响应
-
错误处理测试
- 无效 URL
- 超时错误
- 元素不存在
- 重试机制
describe('Snapka Implementation', () => {
describe('launch()', () => {
it('should launch browser successfully', async () => {
const core = await snapka.launch()
expect(core).toBeDefined()
expect(core.engine).toBe('your-engine')
await core.close()
})
})
describe('screenshot()', () => {
it('should take PNG screenshot', async () => {
const core = await snapka.launch()
const { run } = await core.screenshot({
file: 'https://example.com',
type: 'png'
})
const screenshot = await run()
expect(screenshot).toBeInstanceOf(Uint8Array)
await core.close()
})
})
})每个实现必须提供以下文档:
- README.md: 项目简介和快速开始
- API.md: 完整 API 文档
- CHANGELOG.md: 版本更新日志
参考模板:
# @snapka/your-implementation
## 安装
## 快速开始
## API 参考
- snapka 对象
- LaunchOptions
- ConnectOptions
- Core 类
- 截图选项
## 使用示例
## 高级配置
## 常见问题
## 与其他实现的差异@snapka/your-implementation/
├── src/
│ ├── index.ts # 主入口,导出 snapka 对象
│ ├── core.ts # 核心控制类
│ ├── launch.ts # 启动器类
│ └── util.ts # 工具函数
├── test/
│ └── index.test.ts # 测试文件
├── package.json
├── tsconfig.json
└── README.md
// src/index.ts
import type { LaunchOptions, ConnectOptions } from './launch'
const browsers: Browser[] = []
export const snapka = {
browsers,
async launch(options: LaunchOptions = {}) {
// 1. 获取浏览器路径
// 2. 启动浏览器
// 3. 添加到 browsers 数组
// 4. 创建并返回 Core 实例
},
async connect(options: ConnectOptions) {
// 1. 连接到浏览器
// 2. 添加到 browsers 数组
// 3. 创建并返回 Core 实例
}
}// src/core.ts
export class YourCore implements SnapkaCore {
get engine() { return 'your-engine' }
executablePath() { /* ... */ }
async restart() { /* ... */ }
async close() { /* ... */ }
async screenshot(options) { /* ... */ }
async screenshotViewport(options) { /* ... */ }
}// src/launch.ts
import type { SnapkaLaunchOptions, SnapkaConnectOptions } from '@snapka/types'
export interface LaunchOptions extends SnapkaLaunchOptions {
// 添加特定引擎的选项
}
export interface ConnectOptions extends SnapkaConnectOptions {
// 添加特定引擎的选项
}参考 测试规范 编写完整的测试用例。
参考 文档规范 编写完整的文档。
在发布新实现前,请确保:
- 导出
snapka对象 - 实现
snapka.browsers - 实现
snapka.launch() - 实现
snapka.connect() - 核心类继承
SnapkaCore接口 - 选项继承
SnapkaLaunchOptions和SnapkaConnectOptions
- 支持浏览器路径解析(自定义、查找、下载)
- 实现页面池管理
- 支持并发控制
- 实现重试机制
- 支持所有等待条件
- 支持 PNG、JPEG 格式
- 支持 Base64 和 Binary 编码
- 基础功能测试覆盖率 > 80%
- 截图功能测试覆盖率 > 80%
- 包含错误处理测试
- 所有测试通过
- 提供 README.md
- 提供 API 文档
- 提供使用示例
- 提供 CHANGELOG.md
- 参数支持说明: 在文档中明确列出不支持的标准参数及其替代方案
- @snapka/types - 标准类型定义
- @snapka/playwright - Playwright 实现参考
- @snapka/puppeteer - Puppeteer 实现参考
如有关于标准规范的疑问:
- 提交 Issue: GitHub Issues
- 讨论区: GitHub Discussions
- 初始标准规范发布
- 定义核心 API 接口
- 定义配置选项标准
- 定义行为规范
遵循标准,创造更好的 Snapka 生态