diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..b756445
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,32 @@
+node_modules
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.env
+.env.local
+.env.*.local
+.next
+out
+.git
+.gitignore
+.vscode
+.idea
+.DS_Store
+*.log
+*.tmp
+*.temp
+test-results
+playwright-report
+allure-results
+allure-report
+coverage
+.nyc_output
+*.tgz
+*.zip
+e2e
+test-framework
+reports
+scripts.backup
+logs
+*.tar.gz
+tsconfig.tsbuildinfo
\ No newline at end of file
diff --git a/.env.example b/.env.example
index 1801f5c..c56574a 100644
--- a/.env.example
+++ b/.env.example
@@ -2,4 +2,10 @@ DATABASE_URL=postgresql://user:password@localhost:5432/novalon
NEXTAUTH_SECRET=your-secret-key-here
NEXTAUTH_URL=https://novalon.cn
RESEND_API_KEY=your-resend-api-key-here
-OPS_ALERT_EMAIL=ops@novalon.cn
\ No newline at end of file
+OPS_ALERT_EMAIL=ops@novalon.cn
+
+CDN_DOMAIN=https://cdn.novalon.cn
+COS_SECRET_ID=your-tencent-cloud-secret-id
+COS_SECRET_KEY=your-tencent-cloud-secret-key
+COS_BUCKET=novalon-cdn-1250000000
+COS_REGION=ap-chengdu
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index ed1b2cb..ff6b296 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,39 +1,41 @@
-FROM node:18-alpine AS base
+FROM node:20-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
-RUN npm ci
+RUN npm ci && npm cache clean --force
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
-ENV NEXT_TELEMETRY_DISABLED 1
+ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM base AS runner
WORKDIR /app
-ENV NODE_ENV production
-ENV NEXT_TELEMETRY_DISABLED 1
+ENV NODE_ENV=production
+ENV NEXT_TELEMETRY_DISABLED=1
-RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
+RUN addgroup --system --gid 1001 nodejs && \
+ adduser --system --uid 1001 nextjs
+COPY --from=builder /app/dist/standalone ./
+COPY --from=builder /app/dist/static ./dist/static
COPY --from=builder /app/public ./public
-COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
-COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+
+RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
-ENV PORT 3000
-ENV HOSTNAME "0.0.0.0"
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 8f00b29..2b2f568 100644
--- a/README.md
+++ b/README.md
@@ -668,6 +668,27 @@ NEXT_PUBLIC_SITE_URL=https://novalon.cn
0 2 * * * /path/to/scripts/backup.sh
```
+8. **配置CDN加速** (可选)
+
+ 为静态资源配置CDN加速,提升网站加载速度:
+
+ ```bash
+ # 配置CDN环境变量
+ export CDN_DOMAIN=https://cdn.novalon.cn
+ export COS_SECRET_ID=your-tencent-cloud-secret-id
+ export COS_SECRET_KEY=your-tencent-cloud-secret-key
+ export COS_BUCKET=novalon-cdn-1250000000
+ export COS_REGION=ap-chengdu
+
+ # 上传静态资源到COS
+ npm run deploy:cdn
+
+ # 刷新CDN缓存
+ npm run deploy:cdn:refresh
+ ```
+
+ 详细配置步骤请参考 [CDN配置文档](./docs/CDN_CONFIGURATION.md)
+
## 文档
详细文档位于 `docs/` 目录:
diff --git a/docker-compose.yml b/docker-compose.yml
index 1795df8..0f0c344 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,12 +1,9 @@
-version: '3.8'
+version: "3.8"
services:
novalon-website:
- image: node:18-alpine
+ image: novalon-website:1.0.0
container_name: novalon-website
- build:
- context: .
- dockerfile: Dockerfile
restart: unless-stopped
environment:
- NODE_ENV=production
@@ -16,14 +13,8 @@ services:
- NEXTAUTH_URL=${NEXTAUTH_URL}
- RESEND_API_KEY=${RESEND_API_KEY}
- OPS_ALERT_EMAIL=${OPS_ALERT_EMAIL:-ops@novalon.cn}
- volumes:
- - ./public:/app/public
- - ./node_modules:/app/node_modules
- - .next:/app/.next
networks:
- novalon-network
- depends_on:
- - nginx
nginx:
image: nginx:alpine
@@ -43,4 +34,4 @@ services:
networks:
novalon-network:
- driver: bridge
\ No newline at end of file
+ driver: bridge
diff --git a/docs/CDN_CONFIGURATION.md b/docs/CDN_CONFIGURATION.md
new file mode 100644
index 0000000..df0fd71
--- /dev/null
+++ b/docs/CDN_CONFIGURATION.md
@@ -0,0 +1,352 @@
+# CDN加速静态资源配置指南
+
+## 概述
+
+本文档详细说明如何为novalon.cn配置腾讯云CDN加速静态资源,提升网站加载速度和用户体验。
+
+## 前置条件
+
+- 腾讯云账号
+- 已备案域名: novalon.cn
+- 已部署的Next.js应用
+- 腾讯云COS存储桶
+
+## 方案架构
+
+```
+用户请求
+ ↓
+cdn.novalon.cn (CDN加速域名)
+ ↓
+腾讯云CDN节点 (全国边缘节点)
+ ↓
+COS存储桶 (源站)
+ ↓
+静态资源 (JS/CSS/图片/字体)
+```
+
+## 实施步骤
+
+### 步骤1: 创建COS存储桶
+
+1. 登录腾讯云控制台: https://console.cloud.tencent.com
+2. 进入对象存储COS服务
+3. 创建存储桶:
+ - 名称: `novalon-cdn-[APPID]` (APPID在账号信息中查看)
+ - 地域: 成都 (ap-chengdu) - 与服务器同地域
+ - 访问权限: 公有读、私有写
+ - 版本控制: 不启用
+
+4. 记录存储桶信息:
+ - 存储桶名称: `novalon-cdn-[APPID]`
+ - 地域: `ap-chengdu`
+ - 访问域名: `novalon-cdn-[APPID].cos.ap-chengdu.myqcloud.com`
+
+### 步骤2: 配置CDN加速域名
+
+1. 进入CDN控制台: https://console.cloud.tencent.com/cdn
+2. 点击"域名管理" > "添加域名"
+3. 配置域名信息:
+ - 加速域名: `cdn.novalon.cn`
+ - 业务类型: CDN网页小文件
+ - 加速区域: 中国大陆
+ - 源站类型: COS源
+ - 源站地址: `novalon-cdn-[APPID].cos.ap-chengdu.myqcloud.com`
+ - 回源协议: HTTPS
+ - 回源Host: `novalon-cdn-[APPID].cos.ap-chengdu.myqcloud.com`
+
+4. 点击"提交",等待审核通过
+
+### 步骤3: 配置DNS解析
+
+1. 进入DNS解析控制台 (新网)
+2. 为`cdn.novalon.cn`添加CNAME记录:
+ - 记录类型: CNAME
+ - 主机记录: cdn
+ - 记录值: `cdn.novalon.cn.cdn.dnsv1.com` (腾讯云CDN提供的CNAME地址)
+ - TTL: 600
+
+3. 验证DNS解析:
+```bash
+nslookup cdn.novalon.cn
+```
+
+### 步骤4: 配置HTTPS证书
+
+1. 在CDN域名管理页面,点击域名名称进入详情页
+2. 选择"高级配置" > "HTTPS配置"
+3. 上传SSL证书:
+ - 证书来源: 自有证书
+ - 证书内容: 复制`novalon.cn`的SSL证书
+ - 私钥内容: 复制私钥
+
+4. 开启HTTP2.0
+5. 开启强制跳转HTTPS
+
+### 步骤5: 配置缓存规则
+
+在CDN域名详情页 > "缓存配置" > "节点缓存过期配置":
+
+1. 添加缓存规则:
+ - 类型: 目录
+ - 内容: `/_next/static/`
+ - 过期时间: 365天
+ - 权重: 90
+
+2. 添加文件类型规则:
+ - 类型: 文件后缀
+ - 内容: `js;css;jpg;jpeg;png;gif;webp;avif;svg;woff;woff2;ttf;eot`
+ - 过期时间: 365天
+ - 权重: 80
+
+3. 配置不缓存规则:
+ - 类型: 目录
+ - 内容: `/api/`
+ - 过期时间: 不缓存
+ - 权重: 100
+
+### 步骤6: 配置访问控制
+
+1. 防盗链配置:
+ - 类型: 白名单
+ - 名单: `novalon.cn;*.novalon.cn`
+ - 是否包含空Referer: 是
+
+2. IP访问限频:
+ - 单IP访问频率限制: 开启
+ - 限制频率: 100次/秒
+
+### 步骤7: 上传静态资源到COS
+
+#### 方法1: 使用部署脚本 (推荐)
+
+1. 安装依赖:
+```bash
+pip install coscmd
+```
+
+2. 配置环境变量:
+```bash
+export COS_SECRET_ID="your-secret-id"
+export COS_SECRET_KEY="your-secret-key"
+export COS_BUCKET="novalon-cdn-[APPID]"
+export COS_REGION="ap-chengdu"
+```
+
+3. 运行部署脚本:
+```bash
+chmod +x scripts/deploy-cdn.sh
+./scripts/deploy-cdn.sh
+```
+
+#### 方法2: 手动上传
+
+1. 使用COS控制台上传:
+ - 进入存储桶
+ - 创建目录: `_next/static/`
+ - 上传`dist/static/`目录下的所有文件
+
+2. 设置对象元数据:
+ - Cache-Control: `public, max-age=31536000, immutable`
+
+### 步骤8: 更新应用配置
+
+1. 更新环境变量:
+```bash
+# 在.env文件中添加
+CDN_DOMAIN=https://cdn.novalon.cn
+```
+
+2. 重新构建并部署应用:
+```bash
+npm run build
+docker buildx build --platform linux/amd64 -t novalon-website:1.0.1 --load .
+# 部署到服务器...
+```
+
+### 步骤9: 验证CDN加速效果
+
+1. 检查静态资源URL:
+```bash
+curl -I https://cdn.novalon.cn/_next/static/chunks/main.js
+```
+
+2. 检查响应头:
+```
+HTTP/2 200
+server: tencent-cdn
+x-cache-lookup: Cache Hit
+age: 3600
+cache-control: public, max-age=31536000, immutable
+```
+
+3. 使用浏览器开发者工具:
+ - Network面板查看资源加载时间
+ - 确认资源来自CDN节点
+
+## 性能优化建议
+
+### 1. 图片优化
+
+使用Next.js Image组件自动优化图片:
+```tsx
+import Image from 'next/image'
+
+
+```
+
+### 2. 字体优化
+
+配置字体CDN:
+```tsx
+import { Inter } from 'next/font/google'
+
+const inter = Inter({
+ subsets: ['latin'],
+ display: 'swap',
+ variable: '--font-inter',
+})
+```
+
+### 3. 代码分割
+
+Next.js自动进行代码分割,确保每个页面只加载必要的代码。
+
+### 4. 预加载关键资源
+
+```tsx
+import Head from 'next/head'
+
+
+
+
+```
+
+## 监控与告警
+
+### 1. CDN监控指标
+
+在CDN控制台配置监控告警:
+- 带宽峰值
+- 请求数
+- 命中率
+- 状态码分布
+- 回源带宽
+
+### 2. 告警规则
+
+- 带宽峰值 > 100Mbps
+- 命中率 < 80%
+- 4xx/5xx错误率 > 5%
+
+### 3. 日志分析
+
+开启CDN访问日志,定期分析:
+- 热门资源
+- 访问地域分布
+- 异常访问模式
+
+## 成本估算
+
+### CDN费用 (按流量计费)
+
+- 基础费用: 0.21元/GB (中国大陆)
+- HTTPS请求: 0.05元/万次
+- 预估月流量: 100GB
+- 预估月费用: 21元 + 5元 = 26元
+
+### COS费用
+
+- 存储费用: 0.118元/GB/月
+- 流量费用: 0.5元/GB (CDN回源)
+- 预估存储: 500MB
+- 预估月费用: 0.06元 + 50元 = 50.06元
+
+### 总计
+
+预估月费用: **76元** (实际费用根据访问量动态调整)
+
+## 常见问题
+
+### Q1: CDN缓存未命中怎么办?
+
+**原因**:
+- 首次访问
+- 缓存已过期
+- 缓存规则配置错误
+
+**解决方案**:
+1. 检查缓存规则配置
+2. 使用CDN刷新功能预热缓存
+3. 增加缓存过期时间
+
+### Q2: 静态资源404错误?
+
+**原因**:
+- 资源未上传到COS
+- 路径不匹配
+- 权限配置错误
+
+**解决方案**:
+1. 检查COS存储桶中是否存在该文件
+2. 确认文件路径与URL匹配
+3. 检查存储桶访问权限
+
+### Q3: 跨域问题?
+
+**原因**:
+- CDN域名与应用域名不同
+- CORS配置缺失
+
+**解决方案**:
+在COS存储桶设置CORS规则:
+```json
+[
+ {
+ "AllowedOrigin": ["https://novalon.cn"],
+ "AllowedMethod": ["GET", "HEAD"],
+ "AllowedHeader": ["*"],
+ "MaxAgeSeconds": 3600
+ }
+]
+```
+
+### Q4: 如何回滚?
+
+如果CDN出现问题,可以快速回滚:
+1. 修改`next.config.ts`,移除`assetPrefix`配置
+2. 重新构建并部署应用
+3. 所有静态资源将从源站加载
+
+## 维护计划
+
+### 日常维护
+
+1. 每周检查CDN命中率
+2. 每月分析访问日志
+3. 定期清理无用资源
+
+### 版本更新
+
+1. 更新应用时同步上传新静态资源
+2. 使用版本化文件名避免缓存冲突
+3. 更新后刷新CDN缓存
+
+### 安全加固
+
+1. 定期更新SSL证书
+2. 配置IP黑名单
+3. 开启CC防护
+
+## 参考资料
+
+- [Next.js CDN配置官方文档](https://nextjs.org/docs/app/api-reference/next-config-js/assetPrefix)
+- [腾讯云CDN产品文档](https://cloud.tencent.com/document/product/228)
+- [腾讯云COS产品文档](https://cloud.tencent.com/document/product/436)
+- [Web性能优化最佳实践](https://web.dev/performance/)
diff --git a/docs/CDN_QUICK_START.md b/docs/CDN_QUICK_START.md
new file mode 100644
index 0000000..6fbb5db
--- /dev/null
+++ b/docs/CDN_QUICK_START.md
@@ -0,0 +1,106 @@
+# CDN快速配置指南
+
+## 5分钟快速配置CDN
+
+本指南帮助您快速为novalon.cn配置CDN加速。
+
+### 前置准备
+
+- ✅ 腾讯云账号
+- ✅ 已备案域名: novalon.cn
+- ✅ 腾讯云API密钥 (SecretId和SecretKey)
+
+### 快速配置步骤
+
+#### 1️⃣ 创建COS存储桶 (2分钟)
+
+```bash
+# 登录腾讯云控制台
+open https://console.cloud.tencent.com/cos
+
+# 创建存储桶
+名称: novalon-cdn-[您的APPID]
+地域: 成都 (ap-chengdu)
+访问权限: 公有读、私有写
+```
+
+#### 2️⃣ 配置CDN加速域名 (2分钟)
+
+```bash
+# 进入CDN控制台
+open https://console.cloud.tencent.com/cdn
+
+# 添加域名
+加速域名: cdn.novalon.cn
+业务类型: CDN网页小文件
+源站地址: novalon-cdn-[APPID].cos.ap-chengdu.myqcloud.com
+回源协议: HTTPS
+```
+
+#### 3️⃣ 配置DNS解析 (1分钟)
+
+```bash
+# 在新网DNS管理后台添加CNAME记录
+记录类型: CNAME
+主机记录: cdn
+记录值: cdn.novalon.cn.cdn.dnsv1.com
+```
+
+#### 4️⃣ 上传静态资源 (1分钟)
+
+```bash
+# 配置环境变量
+export CDN_DOMAIN=https://cdn.novalon.cn
+export COS_SECRET_ID=您的SecretId
+export COS_SECRET_KEY=您的SecretKey
+export COS_BUCKET=novalon-cdn-[APPID]
+export COS_REGION=ap-chengdu
+
+# 运行部署脚本
+npm run deploy:cdn
+```
+
+### 验证配置
+
+```bash
+# 检查CDN域名解析
+nslookup cdn.novalon.cn
+
+# 测试静态资源访问
+curl -I https://cdn.novalon.cn/_next/static/chunks/main.js
+
+# 预期响应
+HTTP/2 200
+server: tencent-cdn
+x-cache-lookup: Cache Hit
+cache-control: public, max-age=31536000, immutable
+```
+
+### 常见问题
+
+**Q: CDN域名无法访问?**
+- 检查DNS解析是否生效
+- 确认CDN域名审核通过
+- 验证COS存储桶权限
+
+**Q: 静态资源404?**
+- 确认已运行`npm run deploy:cdn`
+- 检查COS存储桶中是否存在文件
+- 验证文件路径是否正确
+
+**Q: 如何回滚?**
+- 移除`next.config.ts`中的`assetPrefix`配置
+- 重新构建并部署应用
+
+### 下一步
+
+- 📖 查看详细配置文档: [CDN_CONFIGURATION.md](./CDN_CONFIGURATION.md)
+- 🔧 配置HTTPS证书
+- 📊 设置监控告警
+- 🚀 优化缓存策略
+
+### 技术支持
+
+如遇问题,请联系:
+- 邮箱: ops@novalon.cn
+- 文档: [项目文档](../README.md)
diff --git a/e2e-tests/.env.example b/e2e-tests/.env.example
deleted file mode 100644
index 7cc40f6..0000000
--- a/e2e-tests/.env.example
+++ /dev/null
@@ -1,107 +0,0 @@
-# 测试环境配置文件
-# 复制此文件为 .env 并根据实际情况修改配置
-
-# ===========================================
-# 基础配置
-# ===========================================
-
-# 测试环境URL
-TEST_BASE_URL=http://localhost:3000
-
-# 备用测试URL(如果本地不可用)
-TEST_BASE_URL_FALLBACK=https://novalon-website.example.com
-
-# 测试环境
-TEST_ENV=development
-
-# ===========================================
-# 浏览器配置
-# ===========================================
-
-# 默认浏览器
-DEFAULT_BROWSER=chromium
-
-# 视口配置
-DEFAULT_VIEWPORT_WIDTH=1920
-DEFAULT_VIEWPORT_HEIGHT=1080
-
-# 是否以无头模式运行
-HEADLESS_MODE=false
-
-# ===========================================
-# 测试执行配置
-# ===========================================
-
-# 最大重试次数
-MAX_RETRIES=2
-
-# 测试超时时间(秒)
-TEST_TIMEOUT=60
-
-# 页面加载超时
-PAGE_LOAD_TIMEOUT=30000
-
-# 元素等待超时
-ELEMENT_TIMEOUT=10000
-
-# 并行执行配置
-PARALLEL_WORKERS=4
-
-# ===========================================
-# 截图和视频配置
-# ===========================================
-
-# 是否在测试失败时截图
-SCREENSHOT_ON_FAILURE=true
-
-# 是否录制视频
-VIDEO_RECORDING=false
-
-# 截图保存路径
-SCREENSHOTS_DIR=reports/screenshots
-
-# 视频保存路径
-VIDEOS_DIR=reports/videos
-
-# ===========================================
-# 日志配置
-# ===========================================
-
-# 日志级别
-LOG_LEVEL=INFO
-
-# 日志文件路径
-LOG_FILE=reports/e2e_tests.log
-
-# 是否在控制台输出日志
-CONSOLE_LOG=true
-
-# ===========================================
-# 报告配置
-# ===========================================
-
-# HTML报告标题
-REPORT_TITLE=Novalon Website E2E测试报告
-
-# 报告描述
-REPORT_DESCRIPTION=Novalon Website端到端自动化测试报告
-
-# 是否生成JUnit XML报告(用于CI/CD)
-JUNIT_XML_REPORT=false
-JUNIT_XML_PATH=reports/test-results.xml
-
-# ===========================================
-# CI/CD配置
-# ===========================================
-
-# CI环境标识
-CI=false
-
-# Git分支(CI环境中自动填充)
-GIT_BRANCH=
-
-# Git提交(CI环境中自动填充)
-GIT_COMMIT=
-
-# Git仓库(CI环境中自动填充)
-GIT_REPOSITORY=
diff --git a/e2e-tests/README.md b/e2e-tests/README.md
deleted file mode 100644
index 9429f24..0000000
--- a/e2e-tests/README.md
+++ /dev/null
@@ -1,439 +0,0 @@
-# Novalon Website E2E 测试框架
-
-基于 Playwright 和 Python 的端到端测试解决方案,为 Novalon Website 提供全面的自动化测试覆盖。
-
-## 目录
-
-- [特性](#特性)
-- [技术栈](#技术栈)
-- [项目结构](#项目结构)
-- [快速开始](#快速开始)
-- [测试运行](#测试运行)
-- [CI/CD 集成](#cicd-集成)
-- [测试标记](#测试标记)
-- [配置说明](#配置说明)
-- [最佳实践](#最佳实践)
-
-## 特性
-
-- **模块化设计**: 采用 Page Object Model (POM) 设计模式
-- **跨浏览器测试**: 支持 Chrome、Firefox、WebKit
-- **响应式测试**: 覆盖多端(移动端、平板、桌面)
-- **性能测试**: 页面加载性能指标监控
-- **多格式报告**: HTML、JSON、Markdown 报告生成
-- **完整测试数据**: 自动生成测试数据(中文/英文)
-- **详细日志**: 彩色日志输出,便于问题定位
-
-## 技术栈
-
-| 技术 | 用途 |
-|------|------|
-| Python 3.9+ | 编程语言 |
-| Playwright | 浏览器自动化框架 |
-| pytest | 测试框架 |
-| pytest-html | HTML 报告 |
-| pytest-cov | 代码覆盖率 |
-| Jinja2 | 报告模板 |
-
-## 项目结构
-
-```
-e2e-tests/
-├── config/
-│ ├── __init__.py
-│ ├── settings.py # 应用配置
-│ └── browsers.py # 浏览器配置
-├── pages/
-│ ├── __init__.py
-│ ├── base_page.py # 页面基类
-│ ├── home_page.py # 首页对象
-│ └── contact_page.py # 联系页面对象
-├── tests/
-│ ├── __init__.py
-│ ├── conftest.py # pytest 配置和 fixture
-│ ├── test_home_page.py # 首页测试
-│ ├── test_contact_form.py # 联系表单测试
-│ ├── test_navigation.py # 导航测试
-│ ├── test_performance.py # 性能测试
-│ └── test_responsive.py # 响应式测试
-├── utils/
-│ ├── __init__.py
-│ ├── helpers.py # 辅助工具函数
-│ ├── logger.py # 日志配置
-│ ├── data_generator.py # 测试数据生成
-│ └── report_generator.py # 报告生成器
-├── scripts/
-│ ├── run_tests.py # 测试运行脚本
-│ └── ci_test.py # CI/CD 测试脚本
-├── reports/ # 测试报告目录
-├── screenshots/ # 失败截图目录
-├── videos/ # 测试视频目录
-├── requirements.txt # Python 依赖
-├── pyproject.toml # pytest 配置
-├── pytest.ini # pytest 配置
-├── .env.example # 环境变量模板
-└── README.md # 本文档
-```
-
-## 快速开始
-
-### 1. 环境准备
-
-```bash
-# 创建虚拟环境
-python -m venv venv
-source venv/bin/activate # Linux/Mac
-# 或
-.\venv\Scripts\activate # Windows
-
-# 安装依赖
-pip install -r requirements.txt
-
-# 安装 Playwright 浏览器
-playwright install
-```
-
-### 2. 环境配置
-
-```bash
-# 复制环境变量模板
-cp .env.example .env
-
-# 编辑 .env 文件
-# BASE_URL=http://localhost:3000
-# 默认开发环境: http://localhost:3000
-```
-
-### 3. 运行测试
-
-```bash
-# 运行所有测试
-python scripts/run_tests.py
-
-# 运行冒烟测试
-python scripts/run_tests.py -m smoke
-
-# 运行特定测试
-python scripts/run_tests.py tests/test_home_page.py
-
-# 使用关键字过滤
-python scripts/run_tests.py -k home
-
-# 多浏览器测试
-python scripts/run_tests.py -b all
-```
-
-## 测试运行
-
-### 命令行参数
-
-| 参数 | 说明 | 默认值 |
-|------|------|--------|
-| `-b, --browser` | 浏览器 (chromium/firefox/webkit/all) | chromium |
-| `-h, --headless` | 无头模式 | False |
-| `-m, --marker` | 运行指定标记的测试 | - |
-| `-k, --keyword` | 关键字过滤 | - |
-| `-v, --verbose` | 详细输出 | False |
-| `--html` | 生成 HTML 报告 | False |
-| `--video` | 录制测试视频 | False |
-| `--screenshot` | 失败时截图 | False |
-| `--parallel` | 并行执行 | False |
-| `--workers` | 并行工作数 | 4 |
-| `--env` | 测试环境 | development |
-
-### 示例命令
-
-```bash
-# 冒烟测试
-python scripts/run_tests.py -m smoke -v
-
-# 性能测试
-python scripts/run_tests.py -m performance
-
-# 响应式测试
-python scripts/run_tests.py -m responsive
-
-# 完整回归测试
-python scripts/run_tests.py -m regression -v --html
-
-# 无头模式运行
-python scripts/run_tests.py -h --parallel
-
-# 跨浏览器测试
-python scripts/run_tests.py -b all --html
-```
-
-## CI/CD 集成
-
-### GitHub Actions 示例
-
-```yaml
-name: E2E Tests
-
-on:
- push:
- branches: [main, develop]
- pull_request:
- branches: [main]
-
-jobs:
- e2e-tests:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.10'
-
- - name: Install dependencies
- run: |
- python -m venv venv
- source venv/bin/activate
- pip install -r e2e-tests/requirements.txt
- playwright install --with-deps chromium
-
- - name: Run E2E Tests
- run: |
- source venv/bin/activate
- cp e2e-tests/.env.example e2e-tests/.env
- python e2e-tests/scripts/run_tests.py -m smoke --html
- env:
- BASE_URL: ${{ secrets.BASE_URL }}
-
- - name: Upload Test Report
- if: always()
- uses: actions/upload-artifact@v3
- with:
- name: test-report
- path: e2e-tests/reports/
-```
-
-### CI 测试脚本
-
-```bash
-# 运行冒烟测试
-python scripts/ci_test.py --test-type smoke
-
-# 运行回归测试
-python scripts/ci_test.py --test-type regression
-
-# 运行性能测试
-python scripts/ci_test.py --test-type performance
-
-# 跨浏览器测试
-python scripts/ci_test.py --test-type cross_browser
-
-# 完整测试套件
-python scripts/ci_test.py --test-type full
-```
-
-## 测试标记
-
-| 标记 | 说明 | 用例数量 |
-|------|------|---------|
-| `@pytest.mark.smoke` | 冒烟测试,快速验证核心功能 | 关键路径 |
-| `@pytest.mark.regression` | 回归测试,完整功能验证 | 功能测试 |
-| `@pytest.mark.performance` | 性能测试,页面加载和响应时间 | 性能指标 |
-| `@pytest.mark.responsive` | 响应式测试,不同屏幕尺寸 | 布局适配 |
-| `@pytest.mark.cross_browser` | 跨浏览器测试 | 兼容性 |
-| `@pytest.mark.form` | 表单相关测试 | 表单验证 |
-| `@pytest.mark.navigation` | 导航测试 | 页面跳转 |
-| `@pytest.mark.interactive` | 用户交互测试 | 交互功能 |
-
-### 运行特定标记的测试
-
-```bash
-# 只运行冒烟测试
-pytest -m smoke
-
-# 运行冒烟和性能测试
-pytest -m "smoke or performance"
-
-# 运行冒烟但排除性能测试
-pytest -m "smoke and not performance"
-```
-
-## 配置说明
-
-### 环境变量 (.env)
-
-```env
-# 网站基础URL
-BASE_URL=http://localhost:3000
-
-# 测试环境
-ENVIRONMENT=development
-
-# 默认浏览器
-DEFAULT_BROWSER=chromium
-
-# 无头模式
-HEADLESS_MODE=false
-
-# 页面加载超时
-PAGE_LOAD_TIMEOUT=30000
-
-# 元素等待超时
-ELEMENT_TIMEOUT=10000
-
-# 报告标题
-REPORT_TITLE=Novalon Website E2E 测试报告
-
-# 报告描述
-REPORT_DESCRIPTION=自动化端到端测试结果
-```
-
-### 测试配置 (pytest.ini)
-
-```ini
-[pytest]
-testpaths = tests
-python_files = test_*.py
-python_classes = Test*
-python_functions = test_*
-addopts =
- -v
- --tb=short
- --strict-markers
- --disable-warnings
-filterwarnings =
- ignore::DeprecationWarning
-```
-
-### 浏览器配置 (config/browsers.py)
-
-```python
-# 支持的浏览器配置
-BROWSER_CONFIG = {
- "chromium": {
- "headless": False,
- "viewport": {"width": 1920, "height": 1080},
- "args": ["--start-maximized"]
- },
- "firefox": {
- "headless": False,
- "viewport": {"width": 1920, "height": 1080}
- },
- "webkit": {
- "headless": False,
- "viewport": {"width": 1920, "height": 1080}
- }
-}
-```
-
-## 最佳实践
-
-### 1. 页面对象模式
-
-每个页面都应该有对应的 Page Object 类:
-
-```python
-from pages.base_page import BasePage
-
-class HomePage(BasePage):
- def __init__(self, page):
- super().__init__(page)
- self.path = "/"
- self.selectors = {
- "hero_title": "#home h1",
- "cta_button": ".cta-button"
- }
-
- def verify_hero_title(self):
- self.assert_element_visible("hero_title")
- return self
-```
-
-### 2. 测试数据管理
-
-使用数据生成器创建测试数据:
-
-```python
-from utils.data_generator import get_test_data_generator
-
-def test_contact_form(test_data_generator):
- data = test_data_generator.generate_contact_form_data()
- page.fill_contact_form(data)
-```
-
-### 3. 断言辅助
-
-使用断言助手进行验证:
-
-```python
-def test_example(page):
- assert_(page).title_contains("首页")
- assert_(page).element_visible("#main")
-```
-
-### 4. 截图和日志
-
-测试失败时自动截图,并记录详细日志:
-
-```python
-def test_with_logging(page):
- logger = get_logger()
- logger.log_action("执行测试步骤")
- # 测试代码
-```
-
-### 5. 性能监控
-
-使用内置的性能监控:
-
-```python
-def test_performance(page):
- performance = page.verify_page_performance()
- assert performance["pageLoadTime"] < 5000
-```
-
-## 报告示例
-
-测试完成后,报告将保存在 `reports/` 目录:
-
-```
-reports/
-├── html/
-│ └── test_report.html # HTML 格式报告
-├── json/
-│ └── test_results.json # JSON 格式结果
-└── screenshots/
- └── test_failed.png # 失败截图
-```
-
-## 故障排除
-
-### 常见问题
-
-**1. 浏览器安装失败**
-
-```bash
-playwright install --with-deps
-```
-
-**2. 依赖安装失败**
-
-```bash
-pip install --upgrade pip
-pip install -r requirements.txt
-```
-
-**3. 测试超时**
-
-检查 `.env` 中的 `PAGE_LOAD_TIMEOUT` 设置
-
-**4. 页面元素定位失败**
-
-使用开发者工具检查元素选择器是否正确
-
-### 获取帮助
-
-- 查看详细日志: 运行 `python scripts/run_tests.py -v`
-- 查看 Playwright 文档: https://playwright.dev/python/
-- 查看 pytest 文档: https://docs.pytest.org/
-
-## 许可证
-
-本测试框架基于 MIT 许可证开源。
diff --git a/e2e-tests/config/__init__.py b/e2e-tests/config/__init__.py
deleted file mode 100644
index e274b01..0000000
--- a/e2e-tests/config/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Config模块
diff --git a/e2e-tests/config/__pycache__/__init__.cpython-313.pyc b/e2e-tests/config/__pycache__/__init__.cpython-313.pyc
deleted file mode 100644
index b3e5fa4..0000000
Binary files a/e2e-tests/config/__pycache__/__init__.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/config/__pycache__/browsers.cpython-313.pyc b/e2e-tests/config/__pycache__/browsers.cpython-313.pyc
deleted file mode 100644
index 74cd3b0..0000000
Binary files a/e2e-tests/config/__pycache__/browsers.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/config/__pycache__/settings.cpython-313.pyc b/e2e-tests/config/__pycache__/settings.cpython-313.pyc
deleted file mode 100644
index c97ad04..0000000
Binary files a/e2e-tests/config/__pycache__/settings.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/config/browsers.py b/e2e-tests/config/browsers.py
deleted file mode 100644
index 9394a3e..0000000
--- a/e2e-tests/config/browsers.py
+++ /dev/null
@@ -1,270 +0,0 @@
-"""
-浏览器配置模块
-提供跨浏览器测试的配置和工具函数
-"""
-
-from typing import Dict, List, Optional, Tuple
-from dataclasses import dataclass
-from enum import Enum
-
-from playwright.sync_api import Browser, BrowserType, BrowserContext, Page
-from playwright.sync_api import Error as PlaywrightError
-from playwright.sync_api import sync_playwright
-
-from config.settings import get_settings
-
-
-class BrowserTypeEnum(Enum):
- """支持的浏览器类型"""
- CHROMIUM = "chromium"
- FIREFOX = "firefox"
- WEBKIT = "webkit"
-
-
-@dataclass
-class BrowserCapabilities:
- """浏览器能力描述"""
- name: str
- display_name: str
- channel: Optional[str]
- is_headless_supported: bool
- default_viewport: tuple
- user_agent: str
- description: str
-
-
-class BrowserConfigManager:
- """浏览器配置管理器"""
-
- # 浏览器能力定义
- BROWSER_CAPABILITIES: Dict[str, BrowserCapabilities] = {
- "chromium": BrowserCapabilities(
- name="chromium",
- display_name="Chrome/Chromium",
- channel="chrome",
- is_headless_supported=True,
- default_viewport=(1920, 1080),
- user_agent=(
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
- "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
- ),
- description="Google Chrome / Chromium浏览器"
- ),
- "firefox": BrowserCapabilities(
- name="firefox",
- display_name="Firefox",
- channel=None,
- is_headless_supported=True,
- default_viewport=(1920, 1080),
- user_agent=(
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) "
- "Gecko/20100101 Firefox/121.0"
- ),
- description="Mozilla Firefox浏览器"
- ),
- "webkit": BrowserCapabilities(
- name="webkit",
- display_name="WebKit (Safari)",
- channel=None,
- is_headless_supported=True,
- default_viewport=(1920, 1080),
- user_agent=(
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) "
- "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15"
- ),
- description="Apple WebKit (Safari)浏览器"
- )
- }
-
- def __init__(self):
- self.settings = get_settings()
- self._playwright: Optional[sync_playwright] = None
- self._browser: Optional[Browser] = None
- self._context: Optional[BrowserContext] = None
-
- def _ensure_playwright(self) -> sync_playwright:
- """确保Playwright实例已启动"""
- if self._playwright is None:
- self._playwright = sync_playwright().start()
- return self._playwright
-
- def get_available_browsers(self) -> List[str]:
- """获取可用的浏览器列表"""
- available = []
- p = self._ensure_playwright()
-
- browser_map = {
- "chromium": p.chromium,
- "firefox": p.firefox,
- "webkit": p.webkit
- }
-
- for name in browser_map:
- try:
- available.append(name)
- except Exception:
- continue
-
- return available
-
- def _get_browser_type(self, browser_name: str):
- """获取浏览器类型"""
- p = self._ensure_playwright()
- browser_map = {
- "chromium": p.chromium,
- "firefox": p.firefox,
- "webkit": p.webkit
- }
- return browser_map.get(browser_name)
-
- def launch_browser(
- self,
- browser_name: str = "chromium",
- headless: bool = False,
- viewport: Optional[Tuple[int, int]] = None,
- **kwargs
- ) -> Browser:
- """启动浏览器"""
- capabilities = self.BROWSER_CAPABILITIES.get(browser_name)
- if not capabilities:
- raise ValueError(f"不支持的浏览器类型: {browser_name}")
-
- viewport = viewport or (self.settings.viewport_width, self.settings.viewport_height)
-
- launch_args = self._get_launch_arguments(browser_name, headless)
-
- browser_type = self._get_browser_type(browser_name)
- if not browser_type:
- raise ValueError(f"不支持的浏览器类型: {browser_name}")
-
- self._browser = browser_type.launch(
- headless=headless,
- args=launch_args,
- **kwargs
- )
-
- return self._browser
-
- def _get_launch_arguments(self, browser_name: str, headless: bool) -> List[str]:
- """获取浏览器启动参数"""
- args = []
-
- if browser_name == "chromium":
- args.extend([
- "--disable-extensions",
- "--disable-background-networking",
- "--disable-sync",
- "--disable-translate",
- "--metrics-recording-only",
- "--mute-audio",
- "--no-first-run",
- "--safebrowsing-disable-auto-update",
- "--ignore-certificate-errors",
- "--ignore-ssl-errors",
- "--disable-dev-shm-usage",
- ])
-
- if headless:
- args.extend([
- "--headless=new",
- "--disable-gpu",
- "--no-sandbox",
- ])
-
- elif browser_name == "firefox":
- if headless:
- args.extend(["-headless"])
-
- args.extend([
- "-profile",
- "/tmp/firefox-profile",
- ])
-
- elif browser_name == "webkit":
- if headless:
- args.append("--headless")
-
- args.extend([
- "--no-sandbox",
- "--disable-setuid-sandbox",
- ])
-
- return args
-
- def create_context(
- self,
- browser: Browser,
- viewport: Optional[Tuple[int, int]] = None,
- **context_kwargs
- ) -> BrowserContext:
- """创建浏览器上下文"""
- viewport = viewport or (self.settings.viewport_width, self.settings.viewport_height)
-
- capabilities = self.BROWSER_CAPABILITIES.get(
- browser.browser_type.name,
- self.BROWSER_CAPABILITIES["chromium"]
- )
-
- context_options = {
- "viewport": {
- "width": viewport[0],
- "height": viewport[1]
- },
- "user_agent": capabilities.user_agent,
- "locale": "zh-CN",
- "timezone_id": "Asia/Shanghai",
- **context_kwargs
- }
-
- self._context = browser.new_context(**context_options)
-
- return self._context
-
- def create_browser_session(
- self,
- browser_name: str = "chromium",
- headless: bool = False,
- viewport: Optional[Tuple[int, int]] = None
- ) -> Tuple[Browser, BrowserContext, Page]:
- """创建完整的浏览器会话"""
- browser = self.launch_browser(browser_name, headless, viewport)
- context = self.create_context(browser, viewport)
- page = context.new_page()
-
- return browser, context, page
-
- def close_browser(self) -> None:
- """关闭浏览器和上下文"""
- if self._context:
- try:
- self._context.close()
- except Exception:
- pass
- self._context = None
-
- if self._browser:
- try:
- self._browser.close()
- except Exception:
- pass
- self._browser = None
-
- if self._playwright:
- try:
- self._playwright.stop()
- except Exception:
- pass
- self._playwright = None
-
- def __enter__(self) -> 'BrowserConfigManager':
- """上下文管理器入口"""
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
- """上下文管理器退出"""
- self.close_browser()
-
-
-def get_browser_factory() -> BrowserConfigManager:
- """获取浏览器工厂实例"""
- return BrowserConfigManager()
diff --git a/e2e-tests/config/settings.py b/e2e-tests/config/settings.py
deleted file mode 100644
index 36e7538..0000000
--- a/e2e-tests/config/settings.py
+++ /dev/null
@@ -1,258 +0,0 @@
-"""
-测试框架配置模块
-提供全局配置管理和环境变量加载功能
-"""
-
-import os
-from pathlib import Path
-from typing import Any, Dict, List, Optional
-from dataclasses import dataclass, field
-from dotenv import load_dotenv
-
-# 加载环境变量
-load_dotenv()
-
-
-@dataclass
-class BrowserConfig:
- """浏览器配置类"""
- name: str
- headless: bool = False
- viewport_width: int = 1920
- viewport_height: int = 1080
- device_scale_factor: float = 1.0
- is_mobile: bool = False
- has_touch: bool = False
- locale: str = "zh-CN"
- timezone_id: str = "Asia/Shanghai"
-
-
-@dataclass
-class PerformanceThresholds:
- """性能指标阈值配置"""
- page_load_time: int = 3000 # 毫秒
- first_contentful_paint: int = 1500
- largest_contentful_paint: int = 2500
- time_to_interactive: int = 3000
- first_byte: int = 500
- dom_content_loaded: int = 1000
-
-
-@dataclass
-class ResponsiveBreakpoints:
- """响应式测试断点配置"""
- mobile: Dict[str, int] = field(default_factory=lambda: {"width": 375, "height": 667})
- tablet: Dict[str, int] = field(default_factory=lambda: {"width": 768, "height": 1024})
- desktop: Dict[str, int] = field(default_factory=lambda: {"width": 1920, "height": 1080})
- wide: Dict[str, int] = field(default_factory=lambda: {"width": 2560, "height": 1440})
-
-
-class Settings:
- """全局配置管理类"""
-
- _instance: Optional['Settings'] = None
- _initialized: bool = False
-
- def __new__(cls) -> 'Settings':
- if cls._instance is None:
- cls._instance = super().__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not Settings._initialized:
- self._load_config()
- Settings._initialized = True
-
- def _load_config(self) -> None:
- """加载所有配置"""
- # 基础配置
- self.base_url = self._get_env("TEST_BASE_URL", "http://localhost:3000")
- self.fallback_url = self._get_env("TEST_BASE_URL_FALLBACK", "")
- self.test_env = self._get_env("TEST_ENV", "development")
-
- # 浏览器配置
- self.default_browser = self._get_env("DEFAULT_BROWSER", "chromium")
- self.headless_mode = self._get_env("HEADLESS_MODE", "false").lower() == "true"
- self.viewport_width = int(self._get_env("DEFAULT_VIEWPORT_WIDTH", 1920))
- self.viewport_height = int(self._get_env("DEFAULT_VIEWPORT_HEIGHT", 1080))
-
- # 超时配置
- self.max_retries = int(self._get_env("MAX_RETRIES", 2))
- self.test_timeout = int(self._get_env("TEST_TIMEOUT", 60))
- self.page_load_timeout = int(self._get_env("PAGE_LOAD_TIMEOUT", 30000))
- self.element_timeout = int(self._get_env("ELEMENT_TIMEOUT", 10000))
-
- # 并行配置
- self.parallel_workers = int(self._get_env("PARALLEL_WORKERS", 4))
-
- # 截图和视频
- self.screenshot_on_failure = self._get_env("SCREENSHOT_ON_FAILURE", "true").lower() == "true"
- self.video_recording = self._get_env("VIDEO_RECORDING", "false").lower() == "true"
- self.screenshots_dir = self._get_env("SCREENSHOTS_DIR", "reports/screenshots")
- self.videos_dir = self._get_env("VIDEOS_DIR", "reports/videos")
-
- # 日志配置
- self.log_level = self._get_env("LOG_LEVEL", "INFO")
- self.log_file = self._get_env("LOG_FILE", "reports/e2e_tests.log")
- self.console_log = self._get_env("CONSOLE_LOG", "true").lower() == "true"
-
- # 报告配置
- self.report_title = self._get_env("REPORT_TITLE", "Novalon Website E2E测试报告")
- self.report_description = self._get_env(
- "REPORT_DESCRIPTION",
- "Novalon Website端到端自动化测试报告"
- )
- self.junit_xml_report = self._get_env("JUNIT_XML_REPORT", "false").lower() == "true"
- self.junit_xml_path = self._get_env("JUNIT_XML_PATH", "reports/test-results.xml")
-
- # 性能阈值
- self._load_performance_thresholds()
-
- # 响应式断点
- self._load_responsive_breakpoints()
-
- # 浏览器列表
- self.browsers_to_test = ["chromium", "firefox", "webkit"]
-
- # 测试数据
- self._load_test_form_data()
-
- # CI/CD配置
- self.ci = self._get_env("CI", "false").lower() == "true"
- self.git_branch = self._get_env("GIT_BRANCH", "")
- self.git_commit = self._get_env("GIT_COMMIT", "")
- self.git_repository = self._get_env("GIT_REPOSITORY", "")
-
- # 创建必要的目录
- self._create_directories()
-
- def _get_env(self, key: str, default: str) -> str:
- """获取环境变量"""
- return os.environ.get(key, default)
-
- def _load_performance_thresholds(self) -> None:
- """加载性能阈值配置"""
- import json
- thresholds_str = self._get_env(
- "PERFORMANCE_THRESHOLDS",
- '{"page_load_time": 3000, "first_contentful_paint": 1500, '
- '"largest_contentful_paint": 2500, "time_to_interactive": 3000, '
- '"first_byte": 500, "dom_content_loaded": 1000}'
- )
- try:
- thresholds = json.loads(thresholds_str)
- self.performance_thresholds = PerformanceThresholds(**thresholds)
- except (json.JSONDecodeError, TypeError):
- self.performance_thresholds = PerformanceThresholds()
-
- def _load_responsive_breakpoints(self) -> None:
- """加载响应式断点配置"""
- import json
- breakpoints_str = self._get_env(
- "RESPONSIVE_BREAKPOINTS",
- '{"mobile": {"width": 375, "height": 667}, '
- '"tablet": {"width": 768, "height": 1024}, '
- '"desktop": {"width": 1920, "height": 1080}, '
- '"wide": {"width": 2560, "height": 1440}}'
- )
- try:
- breakpoints = json.loads(breakpoints_str)
- self.responsive_breakpoints = ResponsiveBreakpoints(**breakpoints)
- except (json.JSONDecodeError, TypeError):
- self.responsive_breakpoints = ResponsiveBreakpoints()
-
- def _load_test_form_data(self) -> None:
- """加载测试表单数据"""
- import json
- form_data_str = self._get_env(
- "TEST_FORM_DATA",
- '{"valid": {"name": "测试用户", "phone": "13800138000", '
- '"email": "test@example.com", "subject": "测试主题", '
- '"message": "这是一条测试消息,用于验证表单功能是否正常。"}, '
- '"invalid": {"email": "invalid-email", "phone": "123"}}'
- )
- try:
- self.test_form_data = json.loads(form_data_str)
- except json.JSONDecodeError:
- self.test_form_data = {
- "valid": {
- "name": "测试用户",
- "phone": "13800138000",
- "email": "test@example.com",
- "subject": "测试主题",
- "message": "这是一条测试消息,用于验证表单功能是否正常。"
- },
- "invalid": {
- "email": "invalid-email",
- "phone": "123"
- }
- }
-
- def _create_directories(self) -> None:
- """创建必要的目录"""
- base_dirs = [
- self.screenshots_dir,
- self.videos_dir,
- "reports",
- "logs"
- ]
- for dir_path in base_dirs:
- Path(dir_path).mkdir(parents=True, exist_ok=True)
-
- def get_browser_config(self, browser_name: Optional[str] = None) -> BrowserConfig:
- """获取浏览器配置"""
- name = browser_name or self.default_browser
-
- return BrowserConfig(
- name=name,
- headless=self.headless_mode,
- viewport_width=self.viewport_width,
- viewport_height=self.viewport_height
- )
-
- def get_test_data_path(self, filename: str) -> Path:
- """获取测试数据文件路径"""
- return Path(__file__).parent / "test_data" / filename
-
- def get_reports_path(self, filename: str = "") -> Path:
- """获取报告目录路径"""
- reports_dir = Path("reports")
- reports_dir.mkdir(parents=True, exist_ok=True)
- if filename:
- return reports_dir / filename
- return reports_dir
-
- def is_ci_environment(self) -> bool:
- """检查是否为CI环境"""
- return self.ci or os.environ.get("CI", "").lower() in ["true", "1"]
-
- def get_base_url(self) -> str:
- """获取测试基础URL,自动降级到备用URL"""
- # 首先检查基础URL是否可用
- if self._check_url_accessible(self.base_url):
- return self.base_url
-
- # 如果基础URL不可用,尝试备用URL
- if self.fallback_url and self._check_url_accessible(self.fallback_url):
- return self.fallback_url
-
- # 如果都不可用,返回基础URL(测试时会报错)
- return self.base_url
-
- def _check_url_accessible(self, url: str) -> bool:
- """检查URL是否可访问"""
- import requests
- try:
- response = requests.get(url, timeout=5)
- return response.status_code < 500
- except requests.RequestException:
- return False
-
-
-# 全局配置实例
-settings = Settings()
-
-
-def get_settings() -> Settings:
- """获取全局配置实例"""
- return settings
diff --git a/e2e-tests/pages/__init__.py b/e2e-tests/pages/__init__.py
deleted file mode 100644
index 7b70d65..0000000
--- a/e2e-tests/pages/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Pages模块
diff --git a/e2e-tests/pages/__pycache__/__init__.cpython-313.pyc b/e2e-tests/pages/__pycache__/__init__.cpython-313.pyc
deleted file mode 100644
index 5a4c205..0000000
Binary files a/e2e-tests/pages/__pycache__/__init__.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/pages/__pycache__/base_page.cpython-313.pyc b/e2e-tests/pages/__pycache__/base_page.cpython-313.pyc
deleted file mode 100644
index 7be046f..0000000
Binary files a/e2e-tests/pages/__pycache__/base_page.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/pages/__pycache__/contact_page.cpython-313.pyc b/e2e-tests/pages/__pycache__/contact_page.cpython-313.pyc
deleted file mode 100644
index 79cbc65..0000000
Binary files a/e2e-tests/pages/__pycache__/contact_page.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/pages/__pycache__/home_page.cpython-313.pyc b/e2e-tests/pages/__pycache__/home_page.cpython-313.pyc
deleted file mode 100644
index 4124287..0000000
Binary files a/e2e-tests/pages/__pycache__/home_page.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/pages/base_page.py b/e2e-tests/pages/base_page.py
deleted file mode 100644
index 9056eb9..0000000
--- a/e2e-tests/pages/base_page.py
+++ /dev/null
@@ -1,318 +0,0 @@
-"""
-页面对象基类
-提供页面对象模式的基础框架
-"""
-
-from typing import Any, Dict, List, Optional, Tuple, Union
-from urllib.parse import urljoin, urlparse
-
-from playwright.sync_api import Page, Locator, FrameLocator
-from playwright.sync_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
-
-from config.settings import get_settings
-from utils.logger import get_logger
-from utils.helpers import ElementHelper, PageHelper, AssertionHelper, UrlHelper
-
-
-class BasePage:
- """页面对象基类"""
-
- def __init__(self, page: Page, base_url: Optional[str] = None):
- """
- 初始化页面对象
-
- Args:
- page: Playwright Page实例
- base_url: 基础URL
- """
- self.page = page
- self.base_url = base_url or get_settings().get_base_url()
- self.logger = get_logger()
-
- # 初始化辅助类
- self.element = ElementHelper(page)
- self.page_helper = PageHelper(page)
- self.assertion = AssertionHelper(page)
- self.url_helper = UrlHelper()
-
- # 页面URL路径(子类覆盖)
- self.path: Optional[str] = None
-
- # 页面标题(子类覆盖)
- self.title: Optional[str] = None
-
- # 页面元素选择器(子类覆盖)
- self.selectors: Dict[str, str] = {}
-
- def _resolve_selector(self, selector: str) -> str:
- """解析选择器名称为实际选择器字符串"""
- if selector in self.selectors:
- return self.selectors[selector]
- return selector
-
- def _get_full_url(self, path: str) -> str:
- """获取完整URL"""
- if self.url_helper.is_absolute_url(path):
- return path
- return urljoin(self.base_url, path)
-
- def navigate(self, path: Optional[str] = None, **kwargs) -> 'BasePage':
- """
- 导航到页面
-
- Args:
- path: 页面路径,如果为None则使用self.path
- **kwargs: 传递给page.goto的参数
-
- Returns:
- self
- """
- path = path or self.path
- if not path:
- raise ValueError("页面路径未指定")
-
- url = self._get_full_url(path)
- self.logger.log_action(f"导航到页面: {url}")
-
- self.page_helper.navigate(url, **kwargs)
-
- return self
-
- def reload(self) -> 'BasePage':
- """刷新页面"""
- self.page_helper.reload_page()
- return self
-
- def go_back(self) -> 'BasePage':
- """返回上一页"""
- self.page_helper.go_back()
- return self
-
- def go_forward(self) -> 'BasePage':
- """前进到下一页"""
- self.page_helper.go_forward()
- return self
-
- def get_url(self) -> str:
- """获取当前URL"""
- return self.page_helper.get_current_url()
-
- def get_title(self) -> str:
- """获取页面标题"""
- return self.page_helper.get_page_title()
-
- def wait_for_load(self, state: str = "networkidle") -> 'BasePage':
- """等待页面加载完成"""
- self.page_helper.wait_for_load_state(state)
- return self
-
- def wait_for_selector(
- self,
- selector: str,
- timeout: Optional[int] = None,
- state: str = "visible"
- ) -> Locator:
- """等待选择器"""
- return self.element.wait_for_selector(selector, timeout, state)
-
- def scroll_to_top(self) -> 'BasePage':
- """滚动到页面顶部"""
- self.page_helper.scroll_to_top()
- return self
-
- def scroll_to_bottom(self) -> 'BasePage':
- """滚动到页面底部"""
- self.page_helper.scroll_to_bottom()
- return self
-
- def scroll_to_element(self, selector: str) -> 'BasePage':
- """滚动到指定元素"""
- self.page_helper.scroll_to_element(selector)
- return self
-
- def take_screenshot(
- self,
- name: str,
- full_page: bool = False
- ) -> str:
- """截取截图"""
- return self.page_helper.take_screenshot(
- f"{name}_{self._get_timestamp()}.png",
- full_page=full_page
- )
-
- def execute_js(self, script: str, *args) -> Any:
- """执行JavaScript"""
- return self.page_helper.execute_javascript(script, *args)
-
- def _get_timestamp(self) -> str:
- """获取时间戳"""
- from datetime import datetime
- return datetime.now().strftime("%Y%m%d_%H%M%S")
-
- def _find(self, selector: str, timeout: Optional[int] = None) -> Locator:
- """查找元素"""
- resolved_selector = self._resolve_selector(selector)
- return self.element.find_element(resolved_selector, timeout)
-
- def _find_all(self, selector: str) -> List[Locator]:
- """查找所有匹配的元素"""
- resolved_selector = self._resolve_selector(selector)
- return self.element.find_elements(resolved_selector)
-
- def _click(self, selector: str, **kwargs) -> 'BasePage':
- """点击元素"""
- resolved_selector = self._resolve_selector(selector)
- self.element.click_element(resolved_selector, **kwargs)
- return self
-
- def _fill(self, selector: str, value: str, **kwargs) -> 'BasePage':
- """填充输入框"""
- resolved_selector = self._resolve_selector(selector)
- self.element.fill_input(resolved_selector, value, **kwargs)
- return self
-
- def _type(self, selector: str, text: str, **kwargs) -> 'BasePage':
- """输入文本"""
- resolved_selector = self._resolve_selector(selector)
- self.element.type_text(resolved_selector, text, **kwargs)
- return self
-
- def _get_text(self, selector: str, **kwargs) -> str:
- """获取元素文本"""
- resolved_selector = self._resolve_selector(selector)
- return self.element.get_element_text(resolved_selector, **kwargs)
-
- def _get_attr(self, selector: str, attribute: str, **kwargs) -> Optional[str]:
- """获取元素属性"""
- resolved_selector = self._resolve_selector(selector)
- return self.element.get_element_attribute(resolved_selector, attribute, **kwargs)
-
- def _is_visible(self, selector: str, **kwargs) -> bool:
- """检查元素是否可见"""
- resolved_selector = self._resolve_selector(selector)
- return self.element.is_element_visible(resolved_selector, **kwargs)
-
- def _is_enabled(self, selector: str, **kwargs) -> bool:
- """检查元素是否可用"""
- resolved_selector = self._resolve_selector(selector)
- return self.element.is_element_enabled(resolved_selector, **kwargs)
-
- # 断言方法
- def assert_title_contains(self, expected: str, message: Optional[str] = None) -> 'BasePage':
- """断言标题包含预期文本"""
- self.assertion.assert_page_title_contains(expected, message)
- return self
-
- def assert_url_contains(self, expected: str, message: Optional[str] = None) -> 'BasePage':
- """断言URL包含预期文本"""
- self.assertion.assert_url_contains(expected, message)
- return self
-
- def assert_url_equals(self, expected: str, message: Optional[str] = None) -> 'BasePage':
- """断言URL等于预期URL"""
- self.assertion.assert_url_equals(expected, message)
- return self
-
- def assert_element_visible(self, selector: str, **kwargs) -> 'BasePage':
- """断言元素可见"""
- resolved_selector = self._resolve_selector(selector)
- self.assertion.assert_element_visible(resolved_selector, **kwargs)
- return self
-
- def assert_element_hidden(self, selector: str, **kwargs) -> 'BasePage':
- """断言元素隐藏"""
- self.assertion.assert_element_hidden(selector, **kwargs)
- return self
-
- def assert_element_text_contains(
- self,
- selector: str,
- expected: str,
- **kwargs
- ) -> 'BasePage':
- """断言元素文本包含预期文本"""
- self.assertion.assert_element_text_contains(selector, expected, **kwargs)
- return self
-
- def assert_element_text_equals(
- self,
- selector: str,
- expected: str,
- **kwargs
- ) -> 'BasePage':
- """断言元素文本等于预期文本"""
- self.assertion.assert_element_text_equals(selector, expected, **kwargs)
- return self
-
- def assert_element_count(
- self,
- selector: str,
- expected: int,
- message: Optional[str] = None
- ) -> 'BasePage':
- """断言元素数量"""
- self.assertion.assert_element_count(selector, expected, message)
- return self
-
- def assert_element_attribute_equals(
- self,
- selector: str,
- attribute: str,
- expected: str,
- **kwargs
- ) -> 'BasePage':
- """断言元素属性等于预期值"""
- self.assertion.assert_element_attribute_equals(
- selector, attribute, expected, **kwargs
- )
- return self
-
- def should_have_url(self, url: str, **kwargs) -> 'BasePage':
- """检查URL"""
- self.assert_url_equals(url, **kwargs)
- return self
-
- def should_have_title(self, title: str, **kwargs) -> 'BasePage':
- """检查标题"""
- self.assert_title_contains(title, **kwargs)
- return self
-
- def should_contain_text(self, text: str, **kwargs) -> 'BasePage':
- """检查页面包含文本"""
- self.page.wait_for_load_state("domcontentloaded")
- content = self.page_helper.get_page_source()
- assert text in content, f"页面不包含文本: {text}"
- return self
-
- def __repr__(self) -> str:
- return f"<{self.__class__.__name__}: {self.path or 'unknown'}>"
-
-
-class PageRegistry:
- """页面注册表,用于管理页面对象"""
-
- _instance: Optional['PageRegistry'] = None
- _pages: Dict[str, BasePage] = {}
-
- def __new__(cls) -> 'PageRegistry':
- if cls._instance is None:
- cls._instance = super().__new__(cls)
- return cls._instance
-
- def register(self, name: str, page: BasePage) -> None:
- """注册页面"""
- self._pages[name] = page
-
- def get(self, name: str) -> Optional[BasePage]:
- """获取页面"""
- return self._pages.get(name)
-
- def clear(self) -> None:
- """清空注册表"""
- self._pages.clear()
-
-
-def get_page_registry() -> PageRegistry:
- """获取页面注册表"""
- return PageRegistry()
diff --git a/e2e-tests/pages/contact_page.py b/e2e-tests/pages/contact_page.py
deleted file mode 100644
index b7580ee..0000000
--- a/e2e-tests/pages/contact_page.py
+++ /dev/null
@@ -1,437 +0,0 @@
-"""
-联系页面测试模块
-提供联系页面功能测试
-"""
-
-from typing import Any, Dict, List, Optional
-from playwright.sync_api import Page, Locator, expect
-
-from pages.base_page import BasePage
-from config.settings import get_settings
-from utils.logger import get_logger
-from utils.helpers import ElementHelper, PageHelper, AssertionHelper
-
-
-class ContactPage(BasePage):
- """联系页面对象"""
-
- def __init__(self, page: Page, base_url: Optional[str] = None):
- """初始化联系页面"""
- super().__init__(page, base_url)
-
- self.path = "/contact"
- self.title = "联系我们"
-
- self.selectors = {
- # 页面标题
- "page_badge": "[class*='badge']",
- "page_title": "h1",
- "page_description": "p.text-gray-600",
-
- # 联系信息卡片 - 根据实际页面结构
- "contact_info_card": "[data-testid='contact-info']",
- "company_address": "[data-testid='address-text']",
- "company_phone": "[data-testid='phone-link']",
- "company_email": "[data-testid='email-link']",
- "working_hours": "h2:has-text('工作时间')",
-
- # 联系表单 - 使用 data-testid 选择器
- "contact_form": "form",
- "form_name_input": "[data-testid='name-input']",
- "form_phone_input": "[data-testid='phone-input']",
- "form_email_input": "[data-testid='email-input']",
- "form_subject_input": "[data-testid='subject-input']",
- "form_message_textarea": "[data-testid='message-input']",
- "form_submit_button": "[data-testid='submit-button']",
-
- # 表单字段标签
- "name_label": "label[for='name']",
- "phone_label": "label[for='phone']",
- "email_label": "label[for='email']",
- "subject_label": "label[for='subject']",
- "message_label": "label[for='message']",
-
- # 成功状态
- "success_message": "h4:has-text('消息已发送')",
- "success_icon": "svg[class*='CheckCircle']",
-
- # 加载状态
- "submitting_loader": "text=发送中",
-
- # 返回链接
- "back_link": "a:has-text('返回'), a.back"
- }
-
- self.logger = get_logger()
-
- def navigate(self, **kwargs) -> 'ContactPage':
- """导航到联系页面"""
- super().navigate(**kwargs)
- self.wait_for_load()
- # 等待客户端渲染完成
- self.page.wait_for_selector("form", timeout=15000)
- return self
-
- def verify_page_loaded(self) -> 'ContactPage':
- """验证页面加载完成"""
- self.logger.section("验证联系页面加载")
-
- self.assert_element_visible("page_title", timeout=15000)
- self.assert_element_visible("contact_form", timeout=15000)
-
- self.logger.info("✅ 联系页面加载验证通过")
- return self
-
- def verify_page_structure(self) -> 'ContactPage':
- """验证页面结构"""
- self.logger.section("验证页面结构")
-
- # 检查页面标题区域
- self.assert_element_visible("page_title")
-
- # 检查联系信息 - 使用更通用的选择器
- self._verify_contact_info_exists()
-
- # 检查表单
- self.assert_element_visible("contact_form")
-
- self.logger.info("✅ 页面结构验证通过")
- return self
-
- def _verify_contact_info_exists(self) -> bool:
- """验证联系信息存在"""
- # 检查是否包含联系信息文本
- page_text = self.page.content()
- has_address = "地址" in page_text
- has_phone = "电话" in page_text
- has_email = "邮箱" in page_text
-
- assert has_address, "未找到地址信息"
- assert has_phone, "未找到电话信息"
- assert has_email, "未找到邮箱信息"
-
- return True
-
- def verify_company_info(self) -> 'ContactPage':
- """验证公司信息"""
- self.logger.section("验证公司信息")
-
- # 获取页面内容
- page_content = self.page.content()
-
- # 验证信息存在
- assert "地址" in page_content, "未找到地址"
- assert "电话" in page_content, "未找到电话"
- assert "邮箱" in page_content, "未找到邮箱"
-
- self.logger.info("✅ 公司信息验证通过")
- return self
-
- def verify_form_fields(self) -> 'ContactPage':
- """验证表单字段"""
- self.logger.section("验证表单字段")
-
- required_fields = [
- ("form_name_input", "姓名"),
- ("form_email_input", "邮箱"),
- ("form_subject_input", "主题"),
- ("form_message_textarea", "消息")
- ]
-
- for selector, field_name in required_fields:
- self.assert_element_visible(selector, timeout=5000)
-
- # 检查必填标记
- label = self.page.locator(f"label[for='{selector.replace('#', '')}']")
- if label.count() > 0:
- label_text = label.text_content()
- if "*" in (label_text or ""):
- self.logger.info(f"{field_name} 为必填项")
-
- # 检查可选字段
- self.assert_element_visible("form_phone_input")
-
- self.logger.info("✅ 表单字段验证通过")
- return self
-
- def fill_contact_form(self, data: Dict[str, str]) -> 'ContactPage':
- """填充联系表单"""
- self.logger.section("填充联系表单")
-
- # 姓名
- if "name" in data:
- self._fill("form_name_input", data["name"])
- self.logger.log_action(f"填写姓名: {data['name']}")
-
- # 电话
- if "phone" in data:
- self._fill("form_phone_input", data["phone"])
- self.logger.log_action(f"填写电话: {data['phone']}")
-
- # 邮箱
- if "email" in data:
- self._fill("form_email_input", data["email"])
- self.logger.log_action(f"填写邮箱: {data['email']}")
-
- # 主题
- if "subject" in data:
- self._fill("form_subject_input", data["subject"])
- self.logger.log_action(f"填写主题: {data['subject']}")
-
- # 消息
- if "message" in data:
- self._fill("form_message_textarea", data["message"])
- self.logger.log_action(f"填写消息: {data['message'][:50]}...")
-
- return self
-
- def submit_form(self, wait_for_response: bool = True) -> 'ContactPage':
- """提交表单"""
- self.logger.log_action("提交联系表单")
-
- # 等待表单按钮可用
- submit_button = self._find("form_submit_button")
-
- # 点击提交
- submit_button.click()
-
- if wait_for_response:
- # 等待网络请求完成
- self.page.wait_for_load_state("networkidle", timeout=30000)
-
- # 等待一段时间让UI更新
- self.page.wait_for_timeout(2000)
-
- # 检查是否显示成功消息或表单消失
- try:
- # 尝试多种方式检测成功状态
- success_indicators = [
- "h4:has-text('消息已发送')",
- "text=消息已发送",
- "text=感谢您的留言",
- "[class*='success']"
- ]
-
- for indicator in success_indicators:
- try:
- element = self.page.locator(indicator)
- if element.count() > 0:
- self.logger.info("表单提交成功")
- return self
- except Exception:
- continue
-
- # 检查表单是否消失(表示提交成功)
- form = self.page.locator("form")
- if form.count() == 0:
- self.logger.info("表单已消失,提交可能成功")
- else:
- self.logger.warning("未检测到成功消息,可能提交失败或无反馈")
- except Exception as e:
- self.logger.warning(f"检测成功状态失败: {e}")
-
- return self
-
- def verify_form_submission_success(self) -> 'ContactPage':
- """验证表单提交成功"""
- self.logger.section("验证表单提交成功")
-
- # 尝试多种方式检测成功状态
- success_detected = False
- success_indicators = [
- ("success_message", "h4:has-text('消息已发送')"),
- ("success_text", "text=消息已发送"),
- ("thanks_text", "text=感谢您的留言"),
- ("check_icon", "svg[class*='CheckCircle']")
- ]
-
- for name, selector in success_indicators:
- try:
- element = self.page.locator(selector)
- if element.count() > 0:
- self.logger.info(f"检测到成功状态: {name}")
- success_detected = True
- break
- except Exception:
- continue
-
- # 如果没有检测到成功消息,检查表单是否消失
- if not success_detected:
- form = self.page.locator("form")
- if form.count() == 0:
- self.logger.info("表单已消失,提交可能成功")
- success_detected = True
-
- if not success_detected:
- self.logger.warning("未检测到明确的成功状态,但测试继续")
-
- self.logger.info("✅ 表单提交验证完成")
- return self
-
- def verify_form_validation(self) -> 'ContactPage':
- """验证表单验证"""
- self.logger.section("验证表单验证")
-
- # 尝试提交空表单
- self._click("form_submit_button")
-
- # 检查浏览器原生验证
- name_input = self._find("form_name_input")
- is_required = name_input.evaluate("el => el.required")
-
- if is_required:
- self.logger.info("姓名字段为必填项")
-
- # 验证邮箱格式
- self._fill("form_email_input", "invalid-email")
- self._click("form_subject_input")
-
- # 检查HTML5验证
- email_input = self._find("form_email_input")
- validity = email_input.evaluate("""
- el => ({
- valid: el.validity.valid,
- typeMismatch: el.validity.typeMismatch,
- valueMissing: el.validity.valueMissing
- })
- """)
-
- if not validity["valid"] and validity["typeMismatch"]:
- self.logger.info("邮箱格式验证正常工作")
-
- self.logger.info("✅ 表单验证验证通过")
- return self
-
- def verify_form_with_invalid_email(self, data: Dict[str, str]) -> 'ContactPage':
- """使用无效邮箱测试表单验证"""
- self.logger.section("测试无效邮箱")
-
- # 填写表单(使用无效邮箱)
- data["email"] = "invalid-email"
- self.fill_contact_form(data)
-
- # 尝试提交
- self._click("form_submit_button")
-
- # 检查是否被HTML5验证阻止
- email_input = self._find("form_email_input")
- is_valid = email_input.evaluate("el => el.validity.valid")
-
- if not is_valid:
- self.logger.info("无效邮箱被正确阻止")
- else:
- self.logger.warning("无效邮箱未被验证阻止,可能存在后端验证")
-
- return self
-
- def test_form_submission_performance(
- self,
- data: Dict[str, str],
- max_duration: float = 5.0
- ) -> Dict[str, Any]:
- """测试表单提交性能"""
- self.logger.section("表单提交性能测试")
-
- import time
-
- # 填充表单
- self.fill_contact_form(data)
-
- # 记录开始时间
- start_time = time.time()
-
- # 提交表单
- self._click("form_submit_button")
-
- # 等待成功消息
- try:
- self.assert_element_visible("success_message", timeout=10000)
- except Exception:
- pass
-
- # 记录结束时间
- end_time = time.time()
- duration = end_time - start_time
-
- # 验证性能
- if duration <= max_duration:
- self.logger.info(f"✅ 表单提交耗时 {duration:.2f}s,在阈值 {max_duration}s 内")
- else:
- self.logger.warning(f"⚠️ 表单提交耗时 {duration:.2f}s,超过阈值 {max_duration}s")
-
- return {
- "duration": duration,
- "passed": duration <= max_duration
- }
-
- def get_working_hours(self) -> Dict[str, str]:
- """获取工作时间"""
- # 从页面内容中提取工作时间
- page_text = self.page.content()
-
- hours = {}
-
- # 检查工作时间文本
- if "周一至周五" in page_text:
- hours["周一至周五"] = "9:00 - 18:00"
- if "周六" in page_text:
- hours["周六"] = "9:00 - 12:00"
- if "周日" in page_text:
- hours["周日"] = "休息"
-
- return hours
-
- def reset_form(self) -> 'ContactPage':
- """重置表单"""
- self.logger.log_action("重置表单")
-
- # 刷新页面
- self.reload()
- self.wait_for_load()
-
- return self
-
- def verify_responsive_layout(self, width: int) -> 'ContactPage':
- """验证响应式布局"""
- self.logger.section(f"响应式测试 ({width}px)")
-
- # 设置视口
- self.page.set_viewport_size({"width": width, "height": 800})
-
- # 导航到联系页面
- self.navigate()
-
- # 验证布局
- self.assert_element_visible("contact_form", timeout=15000)
-
- # 检查布局变化
- if width < 768:
- self.logger.info("移动端布局:单列布局")
- elif width < 1024:
- self.logger.info("平板端布局:双列布局")
- else:
- self.logger.info("桌面端布局:完整布局")
-
- self.logger.info(f"✅ {width}px 响应式测试通过")
- return self
-
- def extract_contact_details(self) -> Dict[str, str]:
- """提取联系详情"""
- details = {}
-
- # 从页面内容中提取
- page_content = self.page.content()
-
- # 公司地址
- if "公司地址" in page_content:
- details["address"] = "已找到地址信息"
-
- # 联系电话
- if "联系电话" in page_content:
- details["phone"] = "已找到电话信息"
-
- # 电子邮箱
- if "电子邮箱" in page_content:
- details["email"] = "已找到邮箱信息"
-
- return details
diff --git a/e2e-tests/pages/home_page.py b/e2e-tests/pages/home_page.py
deleted file mode 100644
index 6f852cd..0000000
--- a/e2e-tests/pages/home_page.py
+++ /dev/null
@@ -1,415 +0,0 @@
-"""
-首页测试模块
-提供首页功能测试
-"""
-
-from typing import Any, Dict, List, Optional
-from playwright.sync_api import Page, Locator
-
-from pages.base_page import BasePage
-from config.settings import get_settings
-from utils.logger import get_logger
-
-
-class HomePage(BasePage):
- """首页页面对象"""
-
- def __init__(self, page: Page, base_url: Optional[str] = None):
- """初始化首页"""
- super().__init__(page, base_url)
-
- self.path = "/"
- self.title = "四川睿新致远科技有限公司"
-
- self.selectors = {
- # 导航相关
- "header": "header",
- "logo": "header img[alt*='logo'], header a[href='/']",
- "navigation": "header nav, nav",
- "nav_links": "nav a, header nav a",
-
- # Hero区域 - 使用实际的 section ID
- "hero_section": "#home, section:first-of-type",
- "hero_title": "h1",
- "hero_subtitle": "#home p, section:first-of-type p",
- "hero_cta": "a[href*='/contact'], a:has-text('立即咨询')",
-
- # 关于我们区域 - 使用文本匹配
- "about_section": "#about, section:has(h2:has-text('关于'))",
- "about_title": "#about h2, h2:has-text('关于')",
- "about_content": "#about .content",
-
- # 核心业务区域
- "services_section": "#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))",
- "services_title": "#services h2, h2:has-text('业务'), h2:has-text('服务')",
- "services_cards": "#services .card, .service-card",
-
- # 产品服务区域
- "products_section": "#products, section:has(h2:has-text('产品'))",
- "products_title": "#products h2, h2:has-text('产品')",
- "products_grid": "#products .grid",
- "product_cards": "#products .card, .product-card",
-
- # 新闻动态区域
- "news_section": "#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))",
- "news_title": "#news h2, h2:has-text('新闻'), h2:has-text('动态')",
- "news_list": "#news .list, .news-list",
- "news_items": "#news .news-item, .news-item",
-
- # 联系我们区域 - 使用 /contact 页面
- "contact_section": "#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))",
- "contact_title": "#contact h2, h2:has-text('联系'), h2:has-text('联系方式')",
- "contact_form": "form",
-
- # 页脚
- "footer": "footer",
- "footer_content": "footer .content, footer .footer-content",
- "social_links": "footer .social-links, footer a[href*='weixin'], footer a[href*='weibo']"
- }
-
- self.logger = get_logger()
-
- def navigate(self, **kwargs) -> 'HomePage':
- """导航到首页"""
- super().navigate(**kwargs)
- self.wait_for_load()
- return self
-
- def verify_page_loaded(self) -> 'HomePage':
- """验证页面加载完成"""
- self.logger.section("验证首页加载")
-
- # 检查关键元素存在
- self.assert_element_visible("header", timeout=10000)
- self.assert_element_visible("main", timeout=10000)
- self.assert_element_visible("footer", timeout=10000)
-
- # 检查页面标题
- self.assert_title_contains("睿新致远")
-
- self.logger.info("✅ 首页加载验证通过")
- return self
-
- def verify_header(self) -> 'HomePage':
- """验证页头"""
- self.logger.section("验证页头")
-
- # 检查Logo
- if self._is_visible("logo"):
- self.logger.info("Logo存在")
-
- # 检查导航链接 - 桌面端和移动端各有导航项
- nav_links = self._find_all("nav_links")
- min_expected = 6 # 至少6个导航项
- assert len(nav_links) >= min_expected, f"导航链接数量不足: 预期至少{min_expected}个,实际{len(nav_links)}个"
-
- self.logger.info(f"✅ 页头验证通过,发现 {len(nav_links)} 个导航链接")
- return self
-
- def verify_hero_section(self) -> 'HomePage':
- """验证Hero区域"""
- self.logger.section("验证Hero区域")
-
- if self._is_visible("hero_section"):
- self.assert_element_visible("hero_title")
- self.assert_element_visible("hero_subtitle")
- self.logger.info("Hero区域完整")
-
- # 获取标题文本
- title = self._get_text("hero_title")
- self.logger.info(f"Hero标题: {title[:50]}...")
- else:
- self.logger.warning("未找到Hero区域")
-
- return self
-
- def verify_services_section(self) -> 'HomePage':
- """验证核心业务区域"""
- self.logger.section("验证核心业务区域")
-
- if self._is_visible("services_section"):
- self.assert_element_visible("services_title")
-
- # 检查业务卡片
- cards = self._find_all("services_cards")
- self.logger.info(f"发现 {len(cards)} 个服务卡片")
-
- if len(cards) > 0:
- self.logger.info("✅ 服务区域验证通过")
- else:
- self.logger.warning("未找到服务区域")
-
- return self
-
- def verify_products_section(self) -> 'HomePage':
- """验证产品服务区域"""
- self.logger.section("验证产品服务区域")
-
- if self._is_visible("products_section"):
- self.assert_element_visible("products_title")
-
- # 检查产品卡片
- cards = self._find_all("product_cards")
- self.logger.info(f"发现 {len(cards)} 个产品卡片")
-
- if len(cards) > 0:
- self.logger.info("✅ 产品区域验证通过")
- else:
- self.logger.warning("未找到产品区域")
-
- return self
-
- def verify_news_section(self) -> 'HomePage':
- """验证新闻动态区域"""
- self.logger.section("验证新闻动态区域")
-
- if self._is_visible("news_section"):
- self.assert_element_visible("news_title")
-
- # 检查新闻列表
- items = self._find_all("news_items")
- self.logger.info(f"发现 {len(items)} 条新闻")
-
- if len(items) > 0:
- self.logger.info("✅ 新闻区域验证通过")
- else:
- self.logger.warning("未找到新闻区域")
-
- return self
-
- def verify_contact_section(self) -> 'HomePage':
- """验证联系我们区域"""
- self.logger.section("验证联系我们区域")
-
- if self._is_visible("contact_section"):
- self.assert_element_visible("contact_title")
- self.assert_element_visible("contact_form")
- self.logger.info("联系区域包含表单")
-
- # 检查表单字段
- form_fields = ["name", "email", "subject", "message"]
- for field in form_fields:
- if self._is_visible(f"contact_form #{field}"):
- self.logger.info(f"表单字段 {field} 存在")
-
- self.logger.info("✅ 联系区域验证通过")
- else:
- self.logger.warning("未找到联系区域")
-
- return self
-
- def verify_footer(self) -> 'HomePage':
- """验证页脚"""
- self.logger.section("验证页脚")
-
- self.assert_element_visible("footer")
-
- # 检查版权信息
- footer_text = self._get_text("footer")
- if "睿新致远" in footer_text or "2026" in footer_text:
- self.logger.info("页脚包含版权信息")
-
- self.logger.info("✅ 页脚验证通过")
- return self
-
- def verify_all_sections(self) -> 'HomePage':
- """验证所有区域"""
- self.verify_header()
- self.verify_hero_section()
- self.verify_services_section()
- self.verify_products_section()
- self.verify_news_section()
- self.verify_contact_section()
- self.verify_footer()
-
- self.logger.info("✅ 首页所有区域验证完成")
- return self
-
- def scroll_to_section(self, section: str) -> 'HomePage':
- """滚动到指定区域"""
- self.logger.log_action(f"滚动到{section}区域")
-
- section_selectors = {
- "home": "#home, section:first-of-type",
- "about": "#about, section:has(h2:has-text('关于'))",
- "services": "#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))",
- "products": "#products, section:has(h2:has-text('产品'))",
- "news": "#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))",
- "contact": "#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))"
- }
-
- selector = section_selectors.get(section, f"#{section}")
-
- try:
- element = self.page.locator(selector).first
- if element.count() > 0:
- element.scroll_into_view_if_needed()
- self.logger.info(f"已滚动到{section}区域")
- else:
- self.logger.warning(f"未找到{section}区域")
- except Exception as e:
- self.logger.warning(f"滚动到{section}区域失败: {e}")
-
- return self
-
- def click_navigation_link(self, section: str) -> 'HomePage':
- """点击导航链接"""
- self.logger.log_action(f"点击{section}导航链接")
-
- nav_items = {
- "home": "首页",
- "about": "关于我们",
- "services": "核心业务",
- "products": "产品服务",
- "news": "新闻动态",
- "contact": "联系我们"
- }
-
- label = nav_items.get(section, section)
-
- # 查找包含指定文本的导航链接
- nav_link = self.page.locator(f"nav a:has-text('{label}'), header a:has-text('{label}')")
-
- if nav_link.count() > 0:
- nav_link.first.click()
- self.wait_for_load()
- self.logger.info(f"已点击{nav_items.get(section, section)}链接")
- else:
- self.logger.warning(f"未找到{nav_items.get(section, section)}链接")
-
- return self
-
- def get_company_info(self) -> Dict[str, str]:
- """获取公司信息"""
- info = {}
-
- # 从首页获取描述
- hero_text = ""
- if self._is_visible("hero_subtitle"):
- hero_text = self._get_text("hero_subtitle")
-
- # 如果无法从页面获取,使用默认值
- info["description"] = hero_text if hero_text else "专注科技创新,驱动智慧未来"
-
- # 从常量获取
- info["name"] = "四川睿新致远科技有限公司"
- info["slogan"] = "专注科技创新,驱动智慧未来"
-
- return info
-
- def get_statistics(self) -> Dict[str, int]:
- """获取统计数据"""
- stats = {}
-
- # 尝试从页面获取统计数据
- if self._is_visible("about_section"):
- # 这里需要根据实际页面结构调整
- pass
-
- # 默认值
- stats = {
- "customers": 50,
- "cases": 100,
- "projects": 200,
- "experience": 8
- }
-
- return stats
-
- def get_featured_services(self) -> List[Dict[str, str]]:
- """获取精选服务"""
- services = []
-
- if self._is_visible("services_cards"):
- cards = self._find_all("services_cards")[:4]
- for card in cards:
- title = card.locator("h3, .title").text_content() if card.locator("h3, .title").count() > 0 else ""
- description = card.locator("p, .description").text_content() if card.locator("p, .description").count() > 0 else ""
-
- services.append({
- "title": title.strip() if title else "",
- "description": description.strip() if description else ""
- })
-
- return services
-
- def get_latest_news(self) -> List[Dict[str, str]]:
- """获取最新新闻"""
- news = []
-
- if self._is_visible("news_items"):
- items = self._find_all("news_items")[:3]
- for item in items:
- title = item.locator("h3, .title, a").first.text_content() if item.locator("h3, .title, a").count() > 0 else ""
- date = item.locator(".date, time").first.text_content() if item.locator(".date, time").count() > 0 else ""
-
- news.append({
- "title": title.strip() if title else "",
- "date": date.strip() if date else ""
- })
-
- return news
-
- def verify_page_performance(self) -> Dict[str, float]:
- """验证页面性能指标"""
- self.logger.section("性能测试")
-
- performance_data = self.execute_js("""
- () => {
- const timing = performance.timing;
- const navigation = performance.getEntriesByType('navigation')[0];
-
- return {
- // 关键指标
- 'pageLoadTime': timing.loadEventEnd - timing.navigationStart,
- 'domContentLoaded': timing.domContentLoadedEventEnd - timing.navigationStart,
- 'firstPaint': timing.responseStart - timing.navigationStart,
- 'firstContentfulPaint': navigation ? navigation.firstContentfulPaint : 0,
- 'largestContentfulPaint': navigation ? navigation.largestContentfulPaint : 0,
- 'timeToInteractive': navigation ? navigation.interactive : 0,
-
- // 资源指标
- 'domainLookupTime': timing.domainLookupEnd - timing.domainLookupStart,
- 'serverResponseTime': timing.responseEnd - timing.requestStart,
- 'tcpConnectTime': timing.connectEnd - timing.connectStart,
- 'domInteractiveTime': timing.domInteractive - timing.domLoading
- };
- }
- """)
-
- # 记录性能指标
- for metric, value in performance_data.items():
- if value and value > 0:
- threshold = get_settings().performance_thresholds.__dict__.get(
- metric.replace("_", ""), 3000
- )
- self.logger.log_performance(metric, float(value), threshold)
-
- return performance_data
-
- def verify_responsive_design(self, width: int, height: int) -> 'HomePage':
- """验证响应式设计"""
- self.logger.section(f"响应式测试 ({width}x{height})")
-
- # 设置视口大小
- self.page.set_viewport_size({"width": width, "height": height})
- self.wait_for_load()
-
- # 验证关键元素
- self.assert_element_visible("header", timeout=5000)
- self.assert_element_visible("main", timeout=5000)
- self.assert_element_visible("footer", timeout=5000)
-
- # 根据屏幕大小调整验证逻辑
- if width < 768:
- self.logger.info(f"移动端 {width}px: 验证基础布局")
- # 移动端检查汉堡菜单
- mobile_menu = self.page.locator("button:has-text('菜单'), .mobile-menu, .menu-toggle")
- self.logger.info(f"发现 {mobile_menu.count()} 个移动端菜单元素")
- elif width < 1024:
- self.logger.info(f"平板端 {width}px: 验证平板布局")
- else:
- self.logger.info(f"桌面端 {width}px: 验证完整布局")
-
- self.logger.info(f"✅ {width}x{height} 响应式测试通过")
- return self
diff --git a/e2e-tests/pyproject.toml b/e2e-tests/pyproject.toml
deleted file mode 100644
index 4e873ad..0000000
--- a/e2e-tests/pyproject.toml
+++ /dev/null
@@ -1,48 +0,0 @@
-[tool.pytest.ini_options]
-testpaths = ["tests"]
-python_files = ["test_*.py"]
-python_classes = ["Test*"]
-python_functions = ["test_*"]
-addopts = [
- "--tb=short",
- "--strict-markers",
- "-v",
- "--html=reports/test_report.html",
- "--self-contained-html",
- "--cov=utils",
- "--cov-report=html",
- "--cov-report=term-missing"
-]
-markers = [
- "smoke: 冒烟测试,快速验证核心功能",
- "regression: 回归测试,完整功能验证",
- "performance: 性能测试,页面加载和响应时间",
- "responsive: 响应式测试,不同屏幕尺寸",
- "cross_browser: 跨浏览器测试",
- "form: 表单相关测试",
- "navigation: 导航测试",
- "interactive: 用户交互测试"
-]
-filterwarnings = [
- "ignore::DeprecationWarning",
- "ignore::PendingDeprecationWarning",
- "ignore::pytest.PytestUnraisableExceptionWarning"
-]
-
-[tool.pytest]
-# pytest-asyncio配置
-asyncio_mode = "auto"
-asyncio_default_fixture_loop_scope = "function"
-
-[tool.coverage.run]
-branch = true
-source = ["pages", "utils", "tests"]
-omit = ["tests/*", "utils/report_generator.py", "utils/data_generator.py"]
-
-[tool.coverage.report]
-exclude_lines = [
- "pragma: no cover",
- "def __repr__",
- "raise AssertionError",
- "raise NotImplementedError"
-]
diff --git a/e2e-tests/pytest.ini b/e2e-tests/pytest.ini
deleted file mode 100644
index 774fc5c..0000000
--- a/e2e-tests/pytest.ini
+++ /dev/null
@@ -1,24 +0,0 @@
-[pytest]
-# 配置文件
-testpaths = tests
-python_files = test_*.py
-python_classes = Test*
-python_functions = test_*
-addopts =
- --tb=short
- -v
-markers =
- smoke: 冒烟测试,快速验证核心功能
- regression: 回归测试,完整功能验证
- performance: 性能测试,页面加载和响应时间
- responsive: 响应式测试,不同屏幕尺寸
- cross_browser: 跨浏览器测试
- form: 表单相关测试
- navigation: 导航测试
- interactive: 用户交互测试
-
-[tool:pytest]
-filterwarnings =
- ignore::DeprecationWarning
- ignore::PendingDeprecationWarning
- ignore::pytest.PytestUnraisableExceptionWarning
diff --git a/e2e-tests/requirements.txt b/e2e-tests/requirements.txt
deleted file mode 100644
index 5f3f8c4..0000000
--- a/e2e-tests/requirements.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-# E2E测试框架依赖
-# Novalon Website 端到端测试解决方案
-
-playwright>=1.52.0
-pytest>=8.3.0
-pytest-html>=4.1.1
-pytest-xdist>=3.6.1
-pytest-timeout>=2.3.1
-pytest-rerunfailures>=14.0
-python-dotenv>=1.0.0
-requests>=2.31.0
-beautifulsoup4>=4.12.0
-lxml>=5.1.0
-jinja2>=3.1.0
-markdown>=3.5.0
-rich>=13.7.0
-tabulate>=0.9.0
-pillow>=10.2.0
-matplotlib>=3.8.0
-numpy>=1.26.0
-selenium>=4.18.0
diff --git a/e2e-tests/scripts/__init__.py b/e2e-tests/scripts/__init__.py
deleted file mode 100644
index a7fb4c3..0000000
--- a/e2e-tests/scripts/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Scripts模块
diff --git a/e2e-tests/scripts/ci_test.py b/e2e-tests/scripts/ci_test.py
deleted file mode 100644
index 6a0db1a..0000000
--- a/e2e-tests/scripts/ci_test.py
+++ /dev/null
@@ -1,281 +0,0 @@
-#!/usr/bin/env python3
-"""
-CI/CD 测试脚本
-用于持续集成环境中的测试执行
-"""
-
-import os
-import sys
-import json
-import time
-from pathlib import Path
-from datetime import datetime
-
-# 添加项目路径
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-from config.settings import get_settings
-from utils.logger import get_logger
-
-
-class CITestRunner:
- """CI测试运行器"""
-
- def __init__(self):
- self.logger = get_logger()
- self.settings = get_settings()
- self.results = {
- "timestamp": datetime.now().isoformat(),
- "environment": os.environ.get("ENVIRONMENT", "development"),
- "browser": os.environ.get("PLAYWRIGHT_BROWSER", "chromium"),
- "tests": [],
- "summary": {
- "total": 0,
- "passed": 0,
- "failed": 0,
- "skipped": 0,
- "error": 0,
- "duration": 0
- }
- }
-
- def run_smoke_tests(self) -> bool:
- """运行冒烟测试"""
- self.logger.section("运行冒烟测试")
-
- from playwright.sync_api import sync_playwright
- import pytest
-
- pytest_args = [
- "-c", str(project_root / "pytest.ini"),
- "-m", "smoke",
- "-v",
- "--tb=short",
- "--json-report",
- "--json-report-file=reports/smoke_test_results.json",
- "tests"
- ]
-
- try:
- exit_code = pytest.main(pytest_args)
- return exit_code == 0
- except Exception as e:
- self.logger.error(f"冒烟测试失败: {e}")
- return False
-
- def run_regression_tests(self) -> bool:
- """运行回归测试"""
- self.logger.section("运行回归测试")
-
- import pytest
-
- pytest_args = [
- "-c", str(project_root / "pytest.ini"),
- "-m", "regression",
- "-v",
- "--tb=short",
- "--json-report",
- "--json-report-file=reports/regression_test_results.json",
- "tests"
- ]
-
- try:
- exit_code = pytest.main(pytest_args)
- return exit_code == 0
- except Exception as e:
- self.logger.error(f"回归测试失败: {e}")
- return False
-
- def run_performance_tests(self) -> bool:
- """运行性能测试"""
- self.logger.section("运行性能测试")
-
- import pytest
-
- pytest_args = [
- "-c", str(project_root / "pytest.ini"),
- "-m", "performance",
- "-v",
- "--tb=short",
- "--json-report",
- "--json-report-file=reports/performance_test_results.json",
- "tests"
- ]
-
- try:
- exit_code = pytest.main(pytest_args)
- return exit_code == 0
- except Exception as e:
- self.logger.error(f"性能测试失败: {e}")
- return False
-
- def run_cross_browser_tests(self) -> dict:
- """运行跨浏览器测试"""
- self.logger.section("运行跨浏览器测试")
-
- import pytest
- browsers = ["chromium", "firefox", "webkit"]
- results = {}
-
- for browser in browsers:
- self.logger.info(f"测试浏览器: {browser}")
-
- os.environ["PLAYWRIGHT_BROWSER"] = browser
-
- pytest_args = [
- "-c", str(project_root / "pytest.ini"),
- "-m", "smoke",
- "-v",
- "--tb=short",
- f"--json-report=reports/{browser}_test_results.json",
- "tests"
- ]
-
- try:
- exit_code = pytest.main(pytest_args)
- results[browser] = exit_code == 0
- except Exception as e:
- self.logger.error(f"{browser} 测试失败: {e}")
- results[browser] = False
-
- return results
-
- def run_full_test_suite(self) -> bool:
- """运行完整测试套件"""
- self.logger.section("运行完整测试套件")
-
- import pytest
-
- pytest_args = [
- "-c", str(project_root / "pytest.ini"),
- "-v",
- "--tb=short",
- "--json-report",
- "--json-report-file=reports/full_test_results.json",
- "--html=reports/full_test_report.html",
- "--self-contained-html",
- "tests"
- ]
-
- try:
- exit_code = pytest.main(pytest_args)
- return exit_code == 0
- except Exception as e:
- self.logger.error(f"完整测试失败: {e}")
- return False
-
- def generate_ci_report(self, test_results: dict):
- """生成CI测试报告"""
- report_path = Path("reports/ci_test_report.json")
- report_path.parent.mkdir(parents=True, exist_ok=True)
-
- with open(report_path, "w", encoding="utf-8") as f:
- json.dump(test_results, f, indent=2, ensure_ascii=False)
-
- self.logger.info(f"CI报告已生成: {report_path}")
-
- def run_ci_tests(self, test_type: str = "full"):
- """运行CI测试"""
- start_time = time.time()
-
- self.logger.section("开始 CI 测试")
- self.logger.info(f"环境: {self.results['environment']}")
- self.logger.info(f"浏览器: {self.results['browser']}")
- self.logger.info(f"测试类型: {test_type}")
-
- success = False
-
- if test_type == "smoke":
- success = self.run_smoke_tests()
- elif test_type == "regression":
- success = self.run_regression_tests()
- elif test_type == "performance":
- success = self.run_performance_tests()
- elif test_type == "cross_browser":
- cross_results = self.run_cross_browser_tests()
- success = all(cross_results.values())
- self.results["cross_browser_results"] = cross_results
- elif test_type == "full":
- success = self.run_full_test_suite()
- else:
- self.logger.error(f"未知的测试类型: {test_type}")
- return False
-
- end_time = time.time()
- self.results["summary"]["duration"] = end_time - start_time
- self.results["success"] = success
-
- self.logger.section("CI 测试完成")
- self.logger.info(f"总耗时: {self.results['summary']['duration']:.2f}秒")
- self.logger.info(f"测试结果: {'成功' if success else '失败'}")
-
- # 生成报告
- self.generate_ci_report(self.results)
-
- return success
-
-
-def parse_ci_arguments():
- """解析CI参数"""
- parser = argparse.ArgumentParser(
- description="Novalon Website CI 测试运行器"
- )
-
- parser.add_argument(
- "--test-type",
- default="full",
- choices=["smoke", "regression", "performance", "cross_browser", "full"],
- help="测试类型 (默认: full)"
- )
-
- parser.add_argument(
- "--env",
- default="development",
- choices=["development", "staging", "production"],
- help="测试环境 (默认: development)"
- )
-
- parser.add_argument(
- "--browser",
- default="chromium",
- choices=["chromium", "firefox", "webkit", "all"],
- help="浏览器 (默认: chromium)"
- )
-
- parser.add_argument(
- "--report-dir",
- default="reports",
- help="报告目录 (默认: reports)"
- )
-
- return parser.parse_args()
-
-
-def main():
- """CI主函数"""
- args = parse_ci_arguments()
-
- # 设置环境变量
- os.environ["ENVIRONMENT"] = args.env
- os.environ["PLAYWRIGHT_BROWSER"] = args.browser
- os.environ["REPORT_DIR"] = args.report_dir
-
- # 确保报告目录存在
- Path(args.report_dir).mkdir(parents=True, exist_ok=True)
-
- # 运行测试
- runner = CITestRunner()
- success = runner.run_ci_tests(args.test_type)
-
- # 输出结果
- if success:
- print("\n✅ CI 测试通过")
- sys.exit(0)
- else:
- print("\n❌ CI 测试失败")
- sys.exit(1)
-
-
-if __name__ == "__main__":
- main()
diff --git a/e2e-tests/scripts/run_tests.py b/e2e-tests/scripts/run_tests.py
deleted file mode 100644
index 929e903..0000000
--- a/e2e-tests/scripts/run_tests.py
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/usr/bin/env python3
-"""
-测试运行脚本
-提供便捷的测试执行命令
-"""
-
-import os
-import sys
-import argparse
-from pathlib import Path
-
-# 添加项目路径
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-from config.settings import get_settings
-from utils.logger import get_logger
-
-
-def parse_arguments():
- """解析命令行参数"""
- parser = argparse.ArgumentParser(
- description="Novalon Website E2E 测试运行器"
- )
-
- parser.add_argument(
- "-b", "--browser",
- default="chromium",
- choices=["chromium", "firefox", "webkit", "all"],
- help="指定浏览器 (默认: chromium)"
- )
-
- parser.add_argument(
- "-h", "--headless",
- action="store_true",
- default=False,
- help="以无头模式运行 (默认: False)"
- )
-
- parser.add_argument(
- "-m", "--marker",
- default="",
- help="运行指定标记的测试 (例如: -m smoke)"
- )
-
- parser.add_argument(
- "-k", "--keyword",
- default="",
- help="运行包含关键字的测试 (例如: -k home)"
- )
-
- parser.add_argument(
- "-v", "--verbose",
- action="store_true",
- default=False,
- help="显示详细输出"
- )
-
- parser.add_argument(
- "--html",
- action="store_true",
- default=False,
- help="生成HTML测试报告"
- )
-
- parser.add_argument(
- "--video",
- action="store_true",
- default=False,
- help="录制测试视频"
- )
-
- parser.add_argument(
- "--screenshot",
- action="store_true",
- default=False,
- help="失败时截图"
- )
-
- parser.add_argument(
- "--parallel",
- action="store_true",
- default=False,
- help="并行执行测试"
- )
-
- parser.add_argument(
- "--workers",
- type=int,
- default=4,
- help="并行工作数 (默认: 4)"
- )
-
- parser.add_argument(
- "--report-dir",
- default="reports",
- help="报告目录 (默认: reports)"
- )
-
- parser.add_argument(
- "--env",
- default="development",
- choices=["development", "staging", "production"],
- help="测试环境 (默认: development)"
- )
-
- parser.add_argument(
- "test_paths",
- nargs="*",
- default=["tests"],
- help="测试路径 (默认: tests)"
- )
-
- return parser.parse_args()
-
-
-def build_pytest_args(args):
- """构建pytest参数"""
- pytest_args = []
-
- # 配置文件
- pytest_args.append("-c")
- pytest_args.append(str(project_root / "pytest.ini"))
-
- # 浏览器参数
- os.environ["PLAYWRIGHT_BROWSER"] = args.browser
-
- # 无头模式
- if args.headless:
- os.environ["PLAYWRIGHT_HEADLESS"] = "1"
-
- # 标记过滤
- if args.marker:
- pytest_args.append(f"-m={args.marker}")
-
- # 关键字过滤
- if args.keyword:
- pytest_args.append(f"-k={args.keyword}")
-
- # 详细输出
- if args.verbose:
- pytest_args.append("-v")
- pytest_args.append("--tb=short")
-
- # HTML报告
- if args.html:
- pytest_args.append("--html=reports/test_report.html")
- pytest_args.append("--self-contained-html")
-
- # 视频录制
- if args.video:
- os.environ["PLAYWRIGHT_VIDEO"] = "1"
-
- # 失败截图
- if args.screenshot:
- os.environ["PLAYWRIGHT_SCREENSHOT"] = "1"
-
- # 并行执行
- if args.parallel:
- pytest_args.append(f"-n={args.workers}")
- pytest_args.append("--dist=loadscope")
-
- # 报告目录
- if args.report_dir:
- pytest_args.append(f"--report-dir={args.report_dir}")
-
- # 测试路径
- pytest_args.extend(args.test_paths)
-
- # 覆盖率(可选)
- pytest_args.append("--cov=e2e-tests")
- pytest_args.append("--cov-report=term-missing")
-
- return pytest_args
-
-
-def run_tests(args):
- """运行测试"""
- logger = get_logger()
-
- logger.section("开始 E2E 测试")
- logger.info(f"浏览器: {args.browser}")
- logger.info(f"无头模式: {args.headless}")
- logger.info(f"测试路径: {args.test_paths}")
-
- if args.marker:
- logger.info(f"测试标记: {args.marker}")
-
- if args.keyword:
- logger.info(f"关键字过滤: {args.keyword}")
-
- # 构建pytest参数
- pytest_args = build_pytest_args(args)
-
- # 导入pytest
- import pytest
-
- # 运行测试
- exit_code = pytest.main(pytest_args)
-
- logger.section("测试运行完成")
-
- return exit_code
-
-
-def main():
- """主函数"""
- args = parse_arguments()
-
- # 设置环境
- os.environ["ENVIRONMENT"] = args.env
-
- try:
- exit_code = run_tests(args)
- sys.exit(exit_code)
- except KeyboardInterrupt:
- print("\n测试被用户中断")
- sys.exit(130)
- except Exception as e:
- print(f"测试运行出错: {e}")
- sys.exit(1)
-
-
-if __name__ == "__main__":
- main()
diff --git a/e2e-tests/tests/__init__.py b/e2e-tests/tests/__init__.py
deleted file mode 100644
index 5b433b9..0000000
--- a/e2e-tests/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Tests模块
diff --git a/e2e-tests/tests/__pycache__/__init__.cpython-313.pyc b/e2e-tests/tests/__pycache__/__init__.cpython-313.pyc
deleted file mode 100644
index a85b8fb..0000000
Binary files a/e2e-tests/tests/__pycache__/__init__.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/conftest.cpython-313-pytest-8.3.3.pyc b/e2e-tests/tests/__pycache__/conftest.cpython-313-pytest-8.3.3.pyc
deleted file mode 100644
index 9fb19e2..0000000
Binary files a/e2e-tests/tests/__pycache__/conftest.cpython-313-pytest-8.3.3.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/conftest.cpython-313.pyc b/e2e-tests/tests/__pycache__/conftest.cpython-313.pyc
deleted file mode 100644
index a130af8..0000000
Binary files a/e2e-tests/tests/__pycache__/conftest.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/test_contact_form.cpython-313-pytest-8.3.3.pyc b/e2e-tests/tests/__pycache__/test_contact_form.cpython-313-pytest-8.3.3.pyc
deleted file mode 100644
index f8823d0..0000000
Binary files a/e2e-tests/tests/__pycache__/test_contact_form.cpython-313-pytest-8.3.3.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/test_home_page.cpython-313-pytest-8.3.3.pyc b/e2e-tests/tests/__pycache__/test_home_page.cpython-313-pytest-8.3.3.pyc
deleted file mode 100644
index ce62f32..0000000
Binary files a/e2e-tests/tests/__pycache__/test_home_page.cpython-313-pytest-8.3.3.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/test_navigation.cpython-313-pytest-8.3.3.pyc b/e2e-tests/tests/__pycache__/test_navigation.cpython-313-pytest-8.3.3.pyc
deleted file mode 100644
index 2f41ec6..0000000
Binary files a/e2e-tests/tests/__pycache__/test_navigation.cpython-313-pytest-8.3.3.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/test_performance.cpython-313-pytest-8.3.3.pyc b/e2e-tests/tests/__pycache__/test_performance.cpython-313-pytest-8.3.3.pyc
deleted file mode 100644
index d5d2786..0000000
Binary files a/e2e-tests/tests/__pycache__/test_performance.cpython-313-pytest-8.3.3.pyc and /dev/null differ
diff --git a/e2e-tests/tests/__pycache__/test_responsive.cpython-313-pytest-8.3.3.pyc b/e2e-tests/tests/__pycache__/test_responsive.cpython-313-pytest-8.3.3.pyc
deleted file mode 100644
index dd13960..0000000
Binary files a/e2e-tests/tests/__pycache__/test_responsive.cpython-313-pytest-8.3.3.pyc and /dev/null differ
diff --git a/e2e-tests/tests/conftest.py b/e2e-tests/tests/conftest.py
deleted file mode 100644
index 3268aed..0000000
--- a/e2e-tests/tests/conftest.py
+++ /dev/null
@@ -1,342 +0,0 @@
-"""
-测试配置文件
-提供全局测试fixture和钩子函数
-"""
-
-import os
-import sys
-import time
-from pathlib import Path
-from typing import Generator, Optional
-import pytest
-from pytest import Config
-
-from playwright.sync_api import Browser, BrowserContext, Page, Playwright
-from playwright.sync_api import Error as PlaywrightError
-
-# 添加项目根目录到Python路径
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-from config.settings import get_settings
-from config.browsers import get_browser_factory, BrowserConfigManager
-from utils.logger import get_logger
-from utils.report_generator import get_report_manager, TestResult, TestStatus
-from utils.data_generator import get_test_data_generator
-
-
-@pytest.fixture(scope="session")
-def settings() -> Generator:
- """获取测试配置"""
- yield get_settings()
-
-
-@pytest.fixture(scope="session")
-def logger() -> Generator:
- """获取日志记录器"""
- yield get_logger()
-
-
-@pytest.fixture(scope="session")
-def test_data_generator() -> Generator:
- """获取测试数据生成器"""
- yield get_test_data_generator()
-
-
-@pytest.fixture(scope="session")
-def browser_factory() -> Generator:
- """获取浏览器工厂"""
- factory = get_browser_factory()
- yield factory
-
-
-@pytest.fixture(scope="session")
-def browser_context(
- browser_factory: BrowserConfigManager,
- settings
-) -> Generator:
- """创建浏览器上下文"""
- browser, context, page = browser_factory.create_browser_session(
- browser_name=settings.default_browser,
- headless=settings.headless_mode
- )
-
- yield context, page
-
- # 清理(在会话结束时)
- try:
- page.close()
- except Exception:
- pass
- try:
- context.close()
- except Exception:
- pass
- try:
- browser_factory.close_browser()
- except Exception:
- pass
-
-
-@pytest.fixture
-def page(browser_context, settings) -> Generator:
- """创建页面"""
- context, page = browser_context
-
- # 设置默认超时
- page.set_default_timeout(settings.page_load_timeout)
- page.set_default_navigation_timeout(settings.page_load_timeout)
-
- yield page
-
- # 截图(如果测试失败)
- if hasattr(page, "_test_failed") and page._test_failed:
- test_name = getattr(pytest, "_test_name", "unknown")
- screenshots_dir = Path(settings.screenshots_dir)
- screenshots_dir.mkdir(parents=True, exist_ok=True)
-
- screenshot_path = screenshots_dir / f"{test_name}_failed.png"
- try:
- page.screenshot(path=str(screenshot_path))
- logger = get_logger()
- logger.error(f"失败截图已保存: {screenshot_path}")
- except Exception as e:
- logger = get_logger()
- logger.error(f"保存失败截图时出错: {e}")
-
-
-@pytest.fixture
-def home_page(page: Page, settings) -> Generator:
- """创建首页对象"""
- from pages.home_page import HomePage
- home = HomePage(page, settings.get_base_url())
- yield home
-
-
-@pytest.fixture
-def contact_page(page: Page, settings) -> Generator:
- """创建联系页面对象"""
- from pages.contact_page import ContactPage
- contact = ContactPage(page, settings.get_base_url())
- yield contact
-
-
-@pytest.fixture(scope="session")
-def base_url(settings) -> str:
- """获取基础URL"""
- return settings.get_base_url()
-
-
-@pytest.fixture(scope="session")
-def test_results() -> Generator:
- """收集测试结果"""
- results = []
- yield results
-
-
-@pytest.fixture(scope="session")
-def report_manager() -> Generator:
- """获取报告管理器"""
- manager = get_report_manager()
- yield manager
-
-
-@pytest.fixture
-def track_test_result(report_manager, test_results):
- """跟踪测试结果fixture"""
- from datetime import datetime
- import pytest
-
- class TestTracker:
- def __init__(self):
- self.start_time = None
- self.current_result = None
-
- def start_track(self, test_name: str, test_class: str = "", test_file: str = ""):
- self.start_time = datetime.now()
- logger = get_logger()
- logger.log_test_start(test_name, test_class=test_class, test_file=test_file)
-
- def end_track(
- self,
- test_name: str,
- status: TestStatus,
- test_class: str = "",
- test_file: str = ""
- ):
- end_time = datetime.now()
- duration = (end_time - self.start_time).total_seconds()
-
- logger = get_logger()
- logger.log_test_end(test_name, status.value, duration)
-
- # 创建测试结果
- result = TestResult(
- test_id=f"{test_file}_{test_name}",
- test_name=test_name,
- test_file=test_file,
- test_class=test_class,
- status=status,
- start_time=self.start_time,
- end_time=end_time,
- duration=duration
- )
-
- # 添加到报告管理器
- report_manager.add_result(result)
- test_results.append(result)
-
- return result
-
- tracker = TestTracker()
- yield tracker
-
-
-# Pytest钩子函数
-
-def pytest_configure(config: Config):
- """pytest配置钩子"""
- # 设置标记
- config.addinivalue_line(
- "markers", "smoke: 冒烟测试,快速验证核心功能"
- )
- config.addinivalue_line(
- "markers", "regression: 回归测试,完整功能验证"
- )
- config.addinivalue_line(
- "markers", "performance: 性能测试,页面加载和响应时间"
- )
- config.addinivalue_line(
- "markers", "responsive: 响应式测试,不同屏幕尺寸"
- )
- config.addinivalue_line(
- "markers", "cross_browser: 跨浏览器测试"
- )
- config.addinivalue_line(
- "markers", "form: 表单相关测试"
- )
- config.addinivalue_line(
- "markers", "navigation: 导航测试"
- )
- config.addinivalue_line(
- "markers", "interactive: 用户交互测试"
- )
-
- # 创建必要的目录
- settings = get_settings()
- Path(settings.screenshots_dir).mkdir(parents=True, exist_ok=True)
- Path(settings.videos_dir).mkdir(parents=True, exist_ok=True)
- Path("reports").mkdir(parents=True, exist_ok=True)
-
-
-def pytest_sessionstart(session):
- """测试会话开始"""
- logger = get_logger()
- logger.section("开始E2E测试会话")
- logger.info(f"测试会话ID: {session.name}")
-
-
-def pytest_sessionfinish(session, exitstatus):
- """测试会话结束"""
- logger = get_logger()
- logger.section("E2E测试会话结束")
-
- # 生成报告
- if exitstatus == 0:
- logger.info("✅ 所有测试通过")
- else:
- logger.warning(f"⚠️ 测试完成,退出码: {exitstatus}")
-
-
-def pytest_runtest_setup(item):
- """测试运行前设置"""
- logger = get_logger()
- logger.divider()
- logger.log_test_start(item.name)
-
-
-def pytest_runtest_makereport(item, call):
- """生成测试报告"""
- if call.when == "call":
- if call.excinfo:
- item._test_failed = True
- else:
- item._test_failed = False
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_runtest_call(item):
- """测试运行钩子"""
- yield
- # 测试运行后处理
-
-
-def pytest_collection_modifyitems(config, items):
- """修改测试项目"""
- # 按标记排序
- marker_priority = {
- "smoke": 0,
- "performance": 1,
- "regression": 2,
- "responsive": 3,
- "cross_browser": 4,
- "form": 5,
- "navigation": 6,
- "interactive": 7
- }
-
- def get_priority(item):
- for marker in item.iter_markers():
- if marker.name in marker_priority:
- return marker_priority[marker.name]
- return 10
-
- items.sort(key=get_priority)
-
-
-# 自定义断言
-
-class E2EAssertions:
- """E2E测试断言类"""
-
- def __init__(self, page: Page):
- self.page = page
- self.logger = get_logger()
-
- def title_contains(self, expected: str, message: Optional[str] = None) -> None:
- """断言标题包含预期文本"""
- actual = self.page.title()
- assert expected in actual, message or f"标题不包含 '{expected}': {actual}"
- self.logger.log_assertion(f"标题包含 '{expected}'", True)
-
- def url_contains(self, expected: str, message: Optional[str] = None) -> None:
- """断言URL包含预期文本"""
- assert expected in self.page.url, message or f"URL不包含 '{expected}': {self.page.url}"
- self.logger.log_assertion(f"URL包含 '{expected}'", True)
-
- def element_exists(self, selector: str, timeout: int = 5000) -> None:
- """断言元素存在"""
- try:
- self.page.wait_for_selector(selector, timeout=timeout)
- self.logger.log_assertion(f"元素存在: {selector}", True)
- except Exception as e:
- self.logger.log_assertion(f"元素存在: {selector}", False)
- raise AssertionError(f"元素不存在: {selector}") from e
-
- def element_visible(self, selector: str, timeout: int = 5000) -> None:
- """断言元素可见"""
- element = self.page.locator(selector).first
- assert element.is_visible(timeout=timeout), f"元素不可见: {selector}"
- self.logger.log_assertion(f"元素可见: {selector}", True)
-
- def element_not_visible(self, selector: str, timeout: int = 5000) -> None:
- """断言元素不可见"""
- element = self.page.locator(selector).first
- assert not element.is_visible(timeout=timeout), f"元素应该不可见: {selector}"
- self.logger.log_assertion(f"元素不可见: {selector}", True)
-
-
-@pytest.fixture
-def assert_(page: Page) -> E2EAssertions:
- """断言fixture"""
- return E2EAssertions(page)
diff --git a/e2e-tests/tests/test_contact_form.py b/e2e-tests/tests/test_contact_form.py
deleted file mode 100644
index d172562..0000000
--- a/e2e-tests/tests/test_contact_form.py
+++ /dev/null
@@ -1,264 +0,0 @@
-"""
-联系表单测试模块
-测试联系表单的各项功能和验证
-"""
-
-import pytest
-from typing import Dict, Any
-
-from pages.contact_page import ContactPage
-
-
-class TestContactForm:
- """联系表单测试类"""
-
- @pytest.mark.smoke
- @pytest.mark.form
- def test_contact_page_loads(self, contact_page: ContactPage):
- """测试联系页面加载"""
- contact_page.navigate()
- contact_page.verify_page_loaded()
-
- @pytest.mark.smoke
- def test_contact_page_title(self, contact_page: ContactPage):
- """测试联系页面标题"""
- contact_page.navigate()
- contact_page.assert_title_contains("四川睿新致远")
-
- @pytest.mark.regression
- @pytest.mark.form
- def test_contact_page_structure(self, contact_page: ContactPage):
- """测试联系页面结构"""
- contact_page.navigate()
- contact_page.verify_page_structure()
-
- @pytest.mark.regression
- def test_contact_page_company_info(self, contact_page: ContactPage):
- """测试公司信息显示"""
- contact_page.navigate()
- contact_page.verify_company_info()
-
- @pytest.mark.regression
- def test_contact_page_form_fields(self, contact_page: ContactPage):
- """测试表单字段"""
- contact_page.navigate()
- contact_page.verify_form_fields()
-
- @pytest.mark.form
- def test_form_validation_required_fields(self, contact_page: ContactPage):
- """测试必填字段验证"""
- contact_page.navigate()
- contact_page.verify_form_validation()
-
- @pytest.mark.form
- def test_form_submission_success(self, contact_page: ContactPage, test_data_generator):
- """测试表单提交成功"""
- contact_page.navigate()
-
- # 生成测试数据
- data = test_data_generator.generate_contact_form_data(use_valid=True)
-
- # 填写并提交表单
- contact_page.fill_contact_form(data)
- contact_page.submit_form()
-
- # 验证成功
- contact_page.verify_form_submission_success()
-
- @pytest.mark.form
- def test_form_submission_with_minimal_data(self, contact_page: ContactPage):
- """测试表单提交(最小数据)"""
- contact_page.navigate()
-
- # 最小数据
- data = {
- "name": "测试用户",
- "email": "test@example.com",
- "subject": "测试主题",
- "message": "这是一条测试消息。"
- }
-
- # 填写并提交表单
- contact_page.fill_contact_form(data)
- contact_page.submit_form()
-
- # 验证成功
- contact_page.verify_form_submission_success()
-
- @pytest.mark.form
- def test_form_with_empty_name(self, contact_page: ContactPage):
- """测试姓名为空的表单验证"""
- contact_page.navigate()
-
- data = {
- "name": "",
- "email": "test@example.com",
- "subject": "测试主题",
- "message": "这是一条测试消息。"
- }
-
- contact_page.fill_contact_form(data)
-
- # 点击提交按钮
- contact_page._click("form_submit_button")
-
- # 应该显示验证错误
- try:
- contact_page.assert_element_visible("form_name_input:invalid", timeout=2000)
- except Exception:
- # 可能通过后端验证
- pass
-
- @pytest.mark.form
- def test_form_with_invalid_email(self, contact_page: ContactPage):
- """测试无效邮箱验证"""
- contact_page.navigate()
-
- data = {
- "name": "测试用户",
- "email": "invalid-email",
- "subject": "测试主题",
- "message": "这是一条测试消息。"
- }
-
- contact_page.fill_contact_form(data)
-
- # 检查邮箱字段
- email_input = contact_page._find("form_email_input")
- validity = email_input.evaluate("""
- el => ({
- valid: el.validity.valid,
- typeMismatch: el.validity.typeMismatch
- })
- """)
-
- # 验证邮箱格式
- assert not validity["valid"] or validity["typeMismatch"], \
- "无效邮箱应该被标记为无效"
-
- @pytest.mark.form
- def test_form_submission_performance(self, contact_page: ContactPage, test_data_generator):
- """测试表单提交性能"""
- contact_page.navigate()
-
- data = test_data_generator.generate_contact_form_data(use_valid=True)
-
- result = contact_page.test_form_submission_performance(data, max_duration=30.0)
-
- assert result["passed"], f"表单提交耗时 {result['duration']:.2f}s 超过30秒阈值"
-
- @pytest.mark.responsive
- def test_contact_page_mobile_layout(self, contact_page: ContactPage):
- """测试联系页面移动端布局"""
- contact_page.verify_responsive_layout(375)
-
- @pytest.mark.responsive
- def test_contact_page_tablet_layout(self, contact_page: ContactPage):
- """测试联系页面平板端布局"""
- contact_page.verify_responsive_layout(768)
-
- @pytest.mark.responsive
- def test_contact_page_desktop_layout(self, contact_page: ContactPage):
- """测试联系页面桌面端布局"""
- contact_page.verify_responsive_layout(1920)
-
- @pytest.mark.interactive
- def test_extract_contact_details(self, contact_page: ContactPage):
- """测试提取联系详情"""
- contact_page.navigate()
- details = contact_page.extract_contact_details()
-
- # 至少应该找到一种联系方式
- has_contact = "phone" in details or "email" in details or "address" in details
- assert has_contact or len(details) >= 0, "应该找到至少一种联系方式"
-
- @pytest.mark.interactive
- def test_get_working_hours(self, contact_page: ContactPage):
- """测试获取工作时间"""
- contact_page.navigate()
- hours = contact_page.get_working_hours()
-
- assert isinstance(hours, dict)
-
- @pytest.mark.regression
- def test_form_reset_after_submission(self, contact_page: ContactPage, test_data_generator):
- """测试提交后表单重置"""
- contact_page.navigate()
-
- data = test_data_generator.generate_contact_form_data(use_valid=True)
-
- # 第一次提交
- contact_page.fill_contact_form(data)
- contact_page.submit_form()
- contact_page.verify_form_submission_success()
-
- # 刷新页面后表单应该重置
- contact_page.reload()
- contact_page.assert_element_visible("contact_form", timeout=5000)
-
- @pytest.mark.form
- @pytest.mark.performance
- def test_form_typing_performance(self, contact_page: ContactPage, test_data_generator):
- """测试表单输入性能"""
- import time
-
- contact_page.navigate()
-
- data = test_data_generator.generate_contact_form_data(use_valid=True)
-
- # 测量填充时间
- start_time = time.time()
-
- contact_page.fill_contact_form(data)
-
- end_time = time.time()
- fill_time = (end_time - start_time) * 1000
-
- # 填充时间应该在5秒内
- assert fill_time < 5000, f"表单填充时间 {fill_time:.2f}ms 超过5秒阈值"
-
- @pytest.mark.regression
- def test_form_with_special_characters(self, contact_page: ContactPage):
- """测试包含特殊字符的表单提交"""
- contact_page.navigate()
-
- data = {
- "name": "测试用户-Name",
- "email": "test+special@example.com",
- "subject": "特殊字符测试: @#$%",
- "message": "这是一条包含特殊字符的消息!测试...end"
- }
-
- contact_page.fill_contact_form(data)
- contact_page.submit_form()
-
- # 验证成功
- try:
- contact_page.verify_form_submission_success()
- except Exception:
- # 可能需要等待
- contact_page.page.wait_for_timeout(2000)
-
- @pytest.mark.regression
- def test_form_with_long_content(self, contact_page: ContactPage):
- """测试长内容表单提交"""
- contact_page.navigate()
-
- # 生成长内容
- long_message = "这是一条很长的消息。" * 50
-
- data = {
- "name": "长内容测试用户",
- "email": "longtest@example.com",
- "subject": "长内容测试主题" * 10,
- "message": long_message
- }
-
- contact_page.fill_contact_form(data)
- contact_page.submit_form()
-
- # 验证成功
- try:
- contact_page.verify_form_submission_success()
- except Exception:
- contact_page.page.wait_for_timeout(2000)
diff --git a/e2e-tests/tests/test_home_page.py b/e2e-tests/tests/test_home_page.py
deleted file mode 100644
index d7e260f..0000000
--- a/e2e-tests/tests/test_home_page.py
+++ /dev/null
@@ -1,226 +0,0 @@
-"""
-首页测试模块
-测试首页的各项功能和特性
-"""
-
-import pytest
-from typing import Dict, Any
-
-from pages.home_page import HomePage
-
-
-class TestHomePage:
- """首页测试类"""
-
- @pytest.mark.smoke
- @pytest.mark.navigation
- def test_home_page_loads_successfully(self, home_page: HomePage):
- """测试首页正常加载"""
- home_page.navigate()
- home_page.verify_page_loaded()
-
- @pytest.mark.smoke
- def test_home_page_title(self, home_page: HomePage):
- """测试首页标题"""
- home_page.navigate()
- home_page.assert_title_contains("睿新致远")
-
- @pytest.mark.smoke
- def test_home_page_url(self, home_page: HomePage):
- """测试首页URL"""
- home_page.navigate()
- home_page.assert_url_equals(home_page._get_full_url("/"))
-
- @pytest.mark.regression
- def test_home_page_header(self, home_page: HomePage):
- """测试页头"""
- home_page.navigate()
- home_page.verify_header()
-
- @pytest.mark.regression
- def test_home_page_hero_section(self, home_page: HomePage):
- """测试Hero区域"""
- home_page.navigate()
- home_page.verify_hero_section()
-
- @pytest.mark.regression
- def test_home_page_services_section(self, home_page: HomePage):
- """测试服务区域"""
- home_page.navigate()
- home_page.verify_services_section()
-
- @pytest.mark.regression
- def test_home_page_products_section(self, home_page: HomePage):
- """测试产品区域"""
- home_page.navigate()
- home_page.verify_products_section()
-
- @pytest.mark.regression
- def test_home_page_news_section(self, home_page: HomePage):
- """测试新闻区域"""
- home_page.navigate()
- home_page.verify_news_section()
-
- @pytest.mark.regression
- def test_home_page_contact_section(self, home_page: HomePage):
- """测试联系区域"""
- home_page.navigate()
- home_page.verify_contact_section()
-
- @pytest.mark.regression
- def test_home_page_footer(self, home_page: HomePage):
- """测试页脚"""
- home_page.navigate()
- home_page.verify_footer()
-
- @pytest.mark.regression
- def test_home_page_all_sections(self, home_page: HomePage):
- """测试所有区域"""
- home_page.navigate()
- home_page.verify_all_sections()
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_scroll_to_about_section(self, home_page: HomePage):
- """测试滚动到关于区域"""
- home_page.navigate()
- home_page.scroll_to_section("about")
- home_page.assert_element_visible("#about, section:has(h2:has-text('关于'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_scroll_to_services_section(self, home_page: HomePage):
- """测试滚动到服务区域"""
- home_page.navigate()
- home_page.scroll_to_section("services")
- home_page.assert_element_visible("#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_scroll_to_products_section(self, home_page: HomePage):
- """测试滚动到产品区域"""
- home_page.navigate()
- home_page.scroll_to_section("products")
- home_page.assert_element_visible("#products, section:has(h2:has-text('产品'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_scroll_to_news_section(self, home_page: HomePage):
- """测试滚动到新闻区域"""
- home_page.navigate()
- home_page.scroll_to_section("news")
- home_page.assert_element_visible("#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_scroll_to_contact_section(self, home_page: HomePage):
- """测试滚动到联系区域"""
- home_page.navigate()
- home_page.scroll_to_section("contact")
- # 首页可能没有contact区域,跳过验证
- try:
- home_page.assert_element_visible("#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))", timeout=5000)
- except Exception:
- home_page.logger.warning("首页没有联系区域,跳过验证")
-
- @pytest.mark.performance
- def test_home_page_performance(self, home_page: HomePage):
- """测试首页性能"""
- home_page.navigate()
- performance = home_page.verify_page_performance()
-
- # 验证关键性能指标
- assert performance.get("pageLoadTime", 0) < 5000, "页面加载时间超过5秒"
- assert performance.get("domContentLoaded", 0) < 3000, "DOM内容加载时间超过3秒"
-
- @pytest.mark.performance
- def test_home_page_load_time(self, home_page: HomePage):
- """测试首页加载时间"""
- import time
-
- home_page.navigate()
-
- start_time = time.time()
- home_page.wait_for_load()
- end_time = time.time()
-
- load_time = (end_time - start_time) * 1000 # 转换为毫秒
-
- # 断言加载时间在阈值内
- assert load_time < 5000, f"首页加载时间 {load_time:.2f}ms 超过5秒阈值"
-
- @pytest.mark.responsive
- def test_home_page_mobile_layout(self, home_page: HomePage):
- """测试移动端布局"""
- home_page.verify_responsive_design(375, 667)
-
- @pytest.mark.responsive
- def test_home_page_tablet_layout(self, home_page: HomePage):
- """测试平板端布局"""
- home_page.verify_responsive_design(768, 1024)
-
- @pytest.mark.responsive
- def test_home_page_desktop_layout(self, home_page: HomePage):
- """测试桌面端布局"""
- home_page.verify_responsive_design(1920, 1080)
-
- @pytest.mark.responsive
- def test_home_page_wide_layout(self, home_page: HomePage):
- """测试宽屏布局"""
- home_page.verify_responsive_design(2560, 1440)
-
- @pytest.mark.interactive
- def test_get_company_info(self, home_page: HomePage):
- """测试获取公司信息"""
- home_page.navigate()
- info = home_page.get_company_info()
-
- assert "name" in info
- assert "slogan" in info
- assert "description" in info
-
- @pytest.mark.interactive
- def test_get_statistics(self, home_page: HomePage):
- """测试获取统计数据"""
- home_page.navigate()
- stats = home_page.get_statistics()
-
- assert "customers" in stats
- assert "cases" in stats
-
- @pytest.mark.interactive
- def test_get_featured_services(self, home_page: HomePage):
- """测试获取服务列表"""
- home_page.navigate()
- services = home_page.get_featured_services()
-
- assert isinstance(services, list)
- if len(services) > 0:
- assert "title" in services[0]
-
- @pytest.mark.interactive
- def test_get_latest_news(self, home_page: HomePage):
- """测试获取最新新闻"""
- home_page.navigate()
- news = home_page.get_latest_news()
-
- assert isinstance(news, list)
- if len(news) > 0:
- assert "title" in news[0]
-
- @pytest.mark.regression
- def test_page_refresh(self, home_page: HomePage):
- """测试页面刷新"""
- home_page.navigate()
- home_page.reload()
- home_page.verify_page_loaded()
-
- @pytest.mark.navigation
- def test_navigation_links_count(self, home_page: HomePage):
- """测试导航链接数量"""
- home_page.navigate()
-
- nav_links = home_page._find_all("nav a")
-
- # 应该有6个导航链接:首页、关于我们、核心业务、产品服务、新闻动态、联系我们
- assert len(nav_links) >= 5, f"导航链接数量不足,当前{len(nav_links)}个"
diff --git a/e2e-tests/tests/test_navigation.py b/e2e-tests/tests/test_navigation.py
deleted file mode 100644
index 32f13a9..0000000
--- a/e2e-tests/tests/test_navigation.py
+++ /dev/null
@@ -1,233 +0,0 @@
-"""
-导航测试模块
-测试网站导航功能
-"""
-
-import pytest
-from typing import Dict, Any
-
-from pages.home_page import HomePage
-
-
-class TestNavigation:
- """导航测试类"""
-
- @pytest.mark.navigation
- @pytest.mark.smoke
- def test_navigate_to_home(self, home_page: HomePage):
- """测试导航到首页"""
- home_page.navigate()
- home_page.assert_url_equals(home_page._get_full_url("/"))
-
- @pytest.mark.navigation
- @pytest.mark.smoke
- def test_navigate_to_contact_page(self, home_page: HomePage, contact_page):
- """测试导航到联系页面"""
- contact_page.navigate()
- contact_page.assert_url_equals(
- home_page._get_full_url("/contact")
- )
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_click_navigation_to_about(self, home_page: HomePage):
- """测试点击导航到关于区域"""
- home_page.navigate()
- home_page.click_navigation_link("about")
- home_page.assert_element_visible("#about, section:has(h2:has-text('关于'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_click_navigation_to_services(self, home_page: HomePage):
- """测试点击导航到服务区域"""
- home_page.navigate()
- home_page.click_navigation_link("services")
- home_page.assert_element_visible("#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_click_navigation_to_products(self, home_page: HomePage):
- """测试点击导航到产品区域"""
- home_page.navigate()
- home_page.click_navigation_link("products")
- home_page.assert_element_visible("#products, section:has(h2:has-text('产品'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_click_navigation_to_news(self, home_page: HomePage):
- """测试点击导航到新闻区域"""
- home_page.navigate()
- home_page.click_navigation_link("news")
- home_page.assert_element_visible("#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))", timeout=5000)
-
- @pytest.mark.navigation
- @pytest.mark.interactive
- def test_click_navigation_to_contact(self, home_page: HomePage):
- """测试点击导航到联系区域"""
- home_page.navigate()
- home_page.click_navigation_link("contact")
- home_page.assert_element_visible("#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))", timeout=5000)
-
- @pytest.mark.navigation
- def test_smooth_scroll_to_section(self, home_page: HomePage):
- """测试平滑滚动到区域"""
- home_page.navigate()
-
- # 先滚动到页面底部
- home_page.scroll_to_bottom()
-
- # 然后滚动到顶部
- home_page.scroll_to_top()
-
- # 验证
- home_page.assert_element_visible("header")
-
- @pytest.mark.navigation
- def test_scroll_to_each_section(self, home_page: HomePage):
- """测试滚动到每个区域"""
- home_page.navigate()
-
- sections = ["home", "about", "services", "products", "news", "contact"]
- section_selectors = {
- "home": "#home, section:first-of-type",
- "about": "#about, section:has(h2:has-text('关于'))",
- "services": "#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))",
- "products": "#products, section:has(h2:has-text('产品'))",
- "news": "#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))",
- "contact": "#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))"
- }
-
- for section in sections:
- home_page.scroll_to_section(section)
- selector = section_selectors.get(section, f"#{section}")
- try:
- home_page.assert_element_visible(selector, timeout=5000)
- except Exception:
- home_page.logger.warning(f"区域 {section} 未找到,跳过验证")
-
- @pytest.mark.navigation
- def test_page_back(self, home_page: HomePage, contact_page):
- """测试返回上一页"""
- # 先访问联系页面
- contact_page.navigate()
- contact_page.assert_url_equals(home_page._get_full_url("/contact"))
-
- # 返回首页
- home_page.go_back()
-
- # 验证
- home_page.assert_url_equals(home_page._get_full_url("/"))
-
- @pytest.mark.navigation
- def test_page_forward(self, home_page: HomePage, contact_page):
- """测试前进到下一页"""
- # 访问首页
- home_page.navigate()
-
- # 后退(此时没有上一页,应该保持在首页)
- home_page.go_back()
-
- # 前进(此时没有下一页,应该保持在首页)
- home_page.go_forward()
-
- home_page.assert_url_equals(home_page._get_full_url("/"))
-
- @pytest.mark.navigation
- def test_page_reload(self, home_page: HomePage):
- """测试页面刷新"""
- home_page.navigate()
- home_page.reload()
- home_page.verify_page_loaded()
-
- @pytest.mark.navigation
- def test_navigation_link_count(self, home_page: HomePage):
- """测试导航链接数量"""
- home_page.navigate()
-
- nav_links = home_page._find_all("nav a")
-
- # 应该有6个导航链接
- assert len(nav_links) >= 5, f"导航链接数量不足,当前{len(nav_links)}个"
-
- @pytest.mark.navigation
- def test_navigation_link_text(self, home_page: HomePage):
- """测试导航链接文本"""
- home_page.navigate()
-
- expected_links = ["首页", "关于我们", "核心业务", "产品服务", "新闻动态", "联系我们"]
-
- for link_text in expected_links:
- link = home_page.page.locator(f"nav a:has-text('{link_text}')")
- assert link.count() > 0, f"未找到导航链接: {link_text}"
-
- @pytest.mark.navigation
- @pytest.mark.responsive
- def test_navigation_mobile(self, home_page: HomePage):
- """测试移动端导航"""
- # 设置移动端视口
- home_page.page.set_viewport_size({"width": 375, "height": 667})
- home_page.navigate()
-
- # 移动端应该显示汉堡菜单
- menu_button = home_page.page.locator("button:has-text('菜单'), .mobile-menu")
-
- if menu_button.count() > 0:
- home_page.logger.info("移动端显示汉堡菜单")
- else:
- home_page.logger.info("移动端导航可能已内联显示")
-
- @pytest.mark.navigation
- def test_url_hash_navigation(self, home_page: HomePage):
- """测试URL哈希导航"""
- home_page.navigate()
-
- # 直接访问带哈希的URL - 验证页面加载即可
- home_page.navigate(path="/#about")
- home_page.wait_for_load()
-
- # 验证页面已加载
- home_page.assert_element_visible("header", timeout=5000)
-
- @pytest.mark.navigation
- def test_browser_back_button(self, home_page: HomePage, contact_page):
- """测试浏览器后退按钮"""
- # 访问首页
- home_page.navigate()
-
- # 访问联系页面
- contact_page.navigate()
-
- # 使用浏览器后退
- home_page.page.go_back()
-
- # 验证返回首页
- home_page.assert_url_equals(home_page._get_full_url("/"))
-
- @pytest.mark.interactive
- def test_cta_button_navigation(self, home_page: HomePage):
- """测试CTA按钮导航"""
- home_page.navigate()
-
- # 查找CTA按钮(如果有)
- cta_button = home_page.page.locator(
- "a[href*='contact'], a.cta, a.button:has-text('联系'), a:has-text('立即咨询')"
- )
-
- if cta_button.count() > 0:
- cta_button.first.click()
- home_page.wait_for_load()
-
- # 应该导航到联系页面或包含contact的URL
- current_url = home_page.page.url
- # 如果URL包含contact或页面有表单,则测试通过
- if "contact" in current_url:
- home_page.logger.info("✅ CTA按钮导航到联系页面")
- else:
- # 检查页面是否有联系表单
- form = home_page.page.locator("form")
- if form.count() > 0:
- home_page.logger.info("✅ CTA按钮导航到包含表单的页面")
- else:
- home_page.logger.warning(f"CTA按钮导航到: {current_url}")
- else:
- home_page.logger.warning("未找到CTA按钮,跳过测试")
diff --git a/e2e-tests/tests/test_performance.py b/e2e-tests/tests/test_performance.py
deleted file mode 100644
index 5dcb0c4..0000000
--- a/e2e-tests/tests/test_performance.py
+++ /dev/null
@@ -1,321 +0,0 @@
-"""
-性能测试模块
-测试网站性能指标
-"""
-
-import pytest
-import time
-from typing import Dict, Any
-from datetime import datetime
-
-from pages.home_page import HomePage
-from pages.contact_page import ContactPage
-from config.settings import get_settings
-
-
-class TestPerformance:
- """性能测试类"""
-
- @pytest.mark.performance
- @pytest.mark.smoke
- def test_home_page_load_time(self, home_page: HomePage):
- """测试首页加载时间"""
- home_page.navigate()
-
- start_time = time.time()
- home_page.wait_for_load()
- end_time = time.time()
-
- load_time = (end_time - start_time) * 1000 # 毫秒
-
- # 阈值:5秒
- assert load_time < 5000, f"首页加载时间 {load_time:.2f}ms 超过5秒阈值"
-
- @pytest.mark.performance
- @pytest.mark.smoke
- def test_contact_page_load_time(self, contact_page: ContactPage):
- """测试联系页面加载时间"""
- contact_page.navigate()
-
- start_time = time.time()
- contact_page.wait_for_load()
- end_time = time.time()
-
- load_time = (end_time - start_time) * 1000
-
- # 阈值:5秒
- assert load_time < 5000, f"联系页面加载时间 {load_time:.2f}ms 超过5秒阈值"
-
- @pytest.mark.performance
- def test_dom_content_loaded_time(self, home_page: HomePage):
- """测试DOM内容加载时间"""
- home_page.navigate()
-
- performance_data = home_page.execute_js("""
- () => {
- const timing = performance.timing;
- return {
- domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
- domInteractive: timing.domInteractive - timing.domLoading
- };
- }
- """)
-
- dom_loaded = performance_data.get("domContentLoaded", 0)
- assert dom_loaded < 3000, f"DOM内容加载时间 {dom_loaded}ms 超过3秒阈值"
-
- @pytest.mark.performance
- def test_first_paint_time(self, home_page: HomePage):
- """测试首次绘制时间"""
- home_page.navigate()
-
- first_paint = home_page.execute_js("""
- () => {
- const entries = performance.getEntriesByType('paint');
- const firstPaint = entries.find(e => e.name === 'first-paint');
- return firstPaint ? firstPaint.startTime : 0;
- }
- """)
-
- if first_paint:
- assert first_paint < 2000, f"首次绘制时间 {first_paint:.2f}ms 超过2秒阈值"
-
- @pytest.mark.performance
- def test_first_contentful_paint(self, home_page: HomePage):
- """测试首次内容绘制(FCP)"""
- home_page.navigate()
-
- fcp = home_page.execute_js("""
- () => {
- const navigation = performance.getEntriesByType('navigation')[0];
- return navigation ? navigation.firstContentfulPaint : 0;
- }
- """)
-
- if fcp:
- # 阈值:1.5秒
- assert fcp < 1500, f"首次内容绘制时间 {fcp:.2f}ms 超过1.5秒阈值"
-
- @pytest.mark.performance
- def test_largest_contentful_paint(self, home_page: HomePage):
- """测试最大内容绘制(LCP)"""
- home_page.navigate()
-
- lcp = home_page.execute_js("""
- () => {
- try {
- const navigation = performance.getEntriesByType('navigation')[0];
- return navigation ? navigation.largestContentfulPaint : 0;
- } catch (e) {
- return 0;
- }
- }
- """)
-
- if lcp:
- # 阈值:2.5秒
- assert lcp < 2500, f"最大内容绘制时间 {lcp:.2f}ms 超过2.5秒阈值"
-
- @pytest.mark.performance
- def test_time_to_interactive(self, home_page: HomePage):
- """测试可交互时间(TTI)"""
- home_page.navigate()
-
- tti = home_page.execute_js("""
- () => {
- try {
- const navigation = performance.getEntriesByType('navigation')[0];
- return navigation ? navigation.interactive : 0;
- } catch (e) {
- return 0;
- }
- }
- """)
-
- if tti:
- # 阈值:3秒
- assert tti < 3000, f"可交互时间 {tti:.2f}ms 超过3秒阈值"
-
- @pytest.mark.performance
- def test_page_load_performance_metrics(self, home_page: HomePage):
- """测试页面加载性能指标"""
- home_page.navigate()
- performance = home_page.verify_page_performance()
-
- # 验证关键指标
- if performance.get("pageLoadTime"):
- assert performance["pageLoadTime"] < 5000
- if performance.get("domContentLoaded"):
- assert performance["domContentLoaded"] < 3000
-
- @pytest.mark.performance
- def test_network_timing(self, home_page: HomePage):
- """测试网络时序"""
- home_page.navigate()
-
- timing = home_page.execute_js("""
- () => {
- const timing = performance.timing;
- return {
- dnsLookup: timing.domainLookupEnd - timing.domainLookupStart,
- tcpConnection: timing.connectEnd - timing.connectStart,
- serverResponse: timing.responseEnd - timing.requestStart,
- domProcessing: timing.domLoading - timing.responseEnd
- };
- }
- """)
-
- # DNS查询时间
- assert timing.get("dnsLookup", 0) < 500, \
- f"DNS查询时间 {timing.get('dnsLookup')}ms 超过500ms阈值"
-
- # TCP连接时间
- assert timing.get("tcpConnection", 0) < 500, \
- f"TCP连接时间 {timing.get('tcpConnection')}ms 超过500ms阈值"
-
- # 服务器响应时间
- assert timing.get("serverResponse", 0) < 1000, \
- f"服务器响应时间 {timing.get('serverResponse')}ms 超过1秒阈值"
-
- @pytest.mark.performance
- def test_resource_timing(self, home_page: HomePage):
- """测试资源加载时序"""
- home_page.navigate()
-
- resources = home_page.execute_js("""
- () => {
- const entries = performance.getEntriesByType('resource');
- const scripts = entries.filter(e => e.initiatorType === 'script');
- const styles = entries.filter(e => e.initiatorType === 'css');
-
- return {
- totalResources: entries.length,
- scriptCount: scripts.length,
- styleCount: styles.length,
- totalDuration: entries.reduce((sum, e) => sum + e.duration, 0)
- };
- }
- """)
-
- assert resources.get("totalResources", 0) > 0, "未检测到资源加载"
-
- home_page.logger.info(
- f"资源统计: 共{resources.get('totalResources')}个资源,"
- f"脚本{resources.get('scriptCount')}个,"
- f"样式{resources.get('styleCount')}个"
- )
-
- @pytest.mark.performance
- def test_form_submission_time(self, contact_page: ContactPage, test_data_generator):
- """测试表单提交时间"""
- contact_page.navigate()
-
- data = test_data_generator.generate_contact_form_data(use_valid=True)
-
- start_time = time.time()
-
- contact_page.fill_contact_form(data)
- contact_page.submit_form()
-
- try:
- contact_page.verify_form_submission_success()
- except Exception:
- pass
-
- end_time = time.time()
- duration = (end_time - start_time) * 1000
-
- # 阈值:30秒(开发环境可能较慢)
- assert duration < 30000, f"表单提交耗时 {duration:.2f}ms 超过30秒阈值"
-
- @pytest.mark.performance
- def test_scroll_performance(self, home_page: HomePage):
- """测试滚动性能"""
- home_page.navigate()
-
- # 执行多次滚动
- scroll_times = []
- for i in range(5):
- start_time = time.time()
- home_page.scroll_to_bottom()
- home_page.scroll_to_top()
- end_time = time.time()
- scroll_times.append((end_time - start_time) * 1000)
-
- avg_scroll_time = sum(scroll_times) / len(scroll_times)
-
- # 平均滚动时间应该在1秒内
- assert avg_scroll_time < 1000, f"平均滚动时间 {avg_scroll_time:.2f}ms 超过1秒阈值"
-
- @pytest.mark.performance
- def test_element_visibility_performance(self, home_page: HomePage):
- """测试元素可见性检查性能"""
- home_page.navigate()
-
- elements = [
- "header",
- "main",
- "footer",
- "h1",
- "nav",
- "section:first-of-type",
- "form",
- "button"
- ]
-
- check_times = []
- for element in elements:
- start_time = time.time()
- try:
- home_page._is_visible(element)
- except Exception:
- pass
- end_time = time.time()
- check_times.append((end_time - start_time) * 1000)
-
- avg_check_time = sum(check_times) / len(check_times)
-
- # 单个元素检查时间应该在3000ms内(开发环境)
- assert avg_check_time < 3000, f"平均元素检查时间 {avg_check_time:.2f}ms 超过3000ms阈值"
-
- @pytest.mark.performance
- def test_navigation_performance(self, home_page: HomePage, contact_page: ContactPage):
- """测试导航性能"""
- # 测量导航到联系页面的时间
- start_time = time.time()
- contact_page.navigate()
- contact_page.wait_for_load()
- end_time = time.time()
-
- nav_time = (end_time - start_time) * 1000
-
- # 阈值:3秒
- assert nav_time < 3000, f"导航时间 {nav_time:.2f}ms 超过3秒阈值"
-
- @pytest.mark.performance
- @pytest.mark.responsive
- def test_performance_across_viewports(self, home_page: HomePage):
- """测试不同视口下的性能"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- results = []
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
-
- start_time = time.time()
- home_page.navigate()
- home_page.wait_for_load()
- end_time = time.time()
-
- load_time = (end_time - start_time) * 1000
- results.append((name, load_time))
-
- home_page.logger.info(f"{name} ({width}x{height}): {load_time:.2f}ms")
-
- # 验证所有视口加载时间在阈值内
- for name, load_time in results:
- assert load_time < 5000, f"{name}加载时间 {load_time:.2f}ms 超过5秒阈值"
diff --git a/e2e-tests/tests/test_responsive.py b/e2e-tests/tests/test_responsive.py
deleted file mode 100644
index b8f42e3..0000000
--- a/e2e-tests/tests/test_responsive.py
+++ /dev/null
@@ -1,337 +0,0 @@
-"""
-响应式设计测试模块
-测试网站在不同屏幕尺寸下的响应式表现
-"""
-
-import pytest
-from typing import Dict, Any
-
-from pages.home_page import HomePage
-from pages.contact_page import ContactPage
-
-
-class TestResponsive:
- """响应式设计测试类"""
-
- @pytest.mark.responsive
- @pytest.mark.smoke
- def test_homepage_mobile_375(self, home_page: HomePage):
- """测试首页在iPhone SE尺寸下的响应式表现"""
- home_page.verify_responsive_design(375, 667)
-
- @pytest.mark.responsive
- @pytest.mark.smoke
- def test_homepage_mobile_414(self, home_page: HomePage):
- """测试首页在iPhone 8 Plus尺寸下的响应式表现"""
- home_page.verify_responsive_design(414, 896)
-
- @pytest.mark.responsive
- @pytest.mark.smoke
- def test_homepage_tablet_768(self, home_page: HomePage):
- """测试首页在iPad尺寸下的响应式表现"""
- home_page.verify_responsive_design(768, 1024)
-
- @pytest.mark.responsive
- @pytest.mark.smoke
- def test_homepage_desktop_1920(self, home_page: HomePage):
- """测试首页在桌面尺寸下的响应式表现"""
- home_page.verify_responsive_design(1920, 1080)
-
- @pytest.mark.responsive
- def test_contact_page_mobile_375(self, contact_page: ContactPage):
- """测试联系页面在移动端的响应式表现"""
- contact_page.verify_responsive_layout(375)
-
- @pytest.mark.responsive
- def test_contact_page_tablet_768(self, contact_page: ContactPage):
- """测试联系页面在平板端的响应式表现"""
- contact_page.verify_responsive_layout(768)
-
- @pytest.mark.responsive
- def test_contact_page_desktop_1920(self, contact_page: ContactPage):
- """测试联系页面在桌面端的响应式表现"""
- contact_page.verify_responsive_layout(1920)
-
- @pytest.mark.responsive
- def test_header_responsive_mobile(self, home_page: HomePage):
- """测试页头在移动端的响应式表现"""
- home_page.page.set_viewport_size({"width": 375, "height": 667})
- home_page.navigate()
-
- # 验证页头可见
- home_page.assert_element_visible("header", timeout=5000)
-
- # 移动端应该显示汉堡菜单
- menu_button = home_page.page.locator(
- "button:has-text('菜单'), .mobile-menu, .menu-toggle, button[aria-label*='menu']"
- )
-
- if menu_button.count() > 0:
- home_page.logger.info("✅ 移动端页头包含汉堡菜单")
- else:
- home_page.logger.info("ℹ️ 移动端页头可能内联显示所有链接")
-
- @pytest.mark.responsive
- def test_header_responsive_desktop(self, home_page: HomePage):
- """测试页头在桌面端的响应式表现"""
- home_page.page.set_viewport_size({"width": 1920, "height": 1080})
- home_page.navigate()
-
- # 验证页头可见
- home_page.assert_element_visible("header", timeout=5000)
-
- # 桌面端应该显示完整导航
- nav_links = home_page._find_all("nav a")
- assert len(nav_links) >= 5, f"桌面端导航链接不足,当前{len(nav_links)}个"
-
- @pytest.mark.responsive
- def test_navigation_responsive_mobile(self, home_page: HomePage):
- """测试导航在移动端的响应式表现"""
- home_page.page.set_viewport_size({"width": 375, "height": 667})
- home_page.navigate()
-
- # 检查导航是否可访问 - 移动端可能隐藏导航或使用汉堡菜单
- nav_visible = home_page._is_visible("nav")
- mobile_menu_visible = home_page._is_visible(".mobile-menu, .menu-toggle, button[aria-label*='menu']")
- header_visible = home_page._is_visible("header")
-
- # 只要页头可见,就认为导航可访问(导航可能在页头内)
- assert nav_visible or mobile_menu_visible or header_visible, "移动端导航不可访问"
- home_page.logger.info("✅ 移动端导航可访问")
-
- @pytest.mark.responsive
- def test_hero_section_responsive(self, home_page: HomePage):
- """测试Hero区域在不同尺寸下的表现"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
- home_page.navigate()
-
- # 验证Hero区域可见
- hero_visible = home_page._is_visible("section:first-of-type, [class*='hero']")
-
- if hero_visible:
- home_page.logger.info(f"✅ {name} Hero区域正常显示")
- else:
- home_page.logger.warning(f"⚠️ {name} Hero区域可能需要滚动才能显示")
-
- @pytest.mark.responsive
- def test_services_grid_responsive(self, home_page: HomePage):
- """测试服务卡片网格在不同尺寸下的响应式表现"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
- home_page.navigate()
- home_page.scroll_to_section("services")
-
- # 检查服务区域可见
- home_page.assert_element_visible("#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))", timeout=5000)
-
- home_page.logger.info(f"✅ {name} 服务区域正常显示")
-
- @pytest.mark.responsive
- def test_products_grid_responsive(self, home_page: HomePage):
- """测试产品卡片网格在不同尺寸下的响应式表现"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
- home_page.navigate()
- home_page.scroll_to_section("products")
-
- # 检查产品区域可见
- home_page.assert_element_visible("#products, section:has(h2:has-text('产品'))", timeout=5000)
-
- home_page.logger.info(f"✅ {name} 产品区域正常显示")
-
- @pytest.mark.responsive
- def test_news_list_responsive(self, home_page: HomePage):
- """测试新闻列表在不同尺寸下的响应式表现"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
- home_page.navigate()
- home_page.scroll_to_section("news")
-
- # 检查新闻区域可见
- home_page.assert_element_visible("#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))", timeout=5000)
-
- home_page.logger.info(f"✅ {name} 新闻区域正常显示")
-
- @pytest.mark.responsive
- def test_contact_form_responsive(self, home_page: HomePage):
- """测试联系表单在不同尺寸下的响应式表现"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
- home_page.navigate()
- home_page.scroll_to_section("contact")
-
- # 检查表单可见
- form_visible = home_page._is_visible("form")
-
- if form_visible:
- home_page.logger.info(f"✅ {name} 联系表单正常显示")
- else:
- home_page.logger.warning(f"⚠️ {name} 联系表单不可见")
-
- @pytest.mark.responsive
- def test_footer_responsive(self, home_page: HomePage):
- """测试页脚在不同尺寸下的响应式表现"""
- viewports = [
- (375, 667, "移动端"),
- (768, 1024, "平板端"),
- (1920, 1080, "桌面端")
- ]
-
- for width, height, name in viewports:
- home_page.page.set_viewport_size({"width": width, "height": height})
- home_page.navigate()
-
- # 检查页脚可见
- home_page.assert_element_visible("footer", timeout=5000)
-
- home_page.logger.info(f"✅ {name} 页脚正常显示")
-
- @pytest.mark.responsive
- def test_element_stacking_mobile(self, home_page: HomePage):
- """测试移动端元素堆叠"""
- home_page.page.set_viewport_size({"width": 375, "height": 667})
- home_page.navigate()
-
- # 滚动检查各个区域
- sections = [
- "#home, section:first-of-type",
- "#about, section:has(h2:has-text('关于'))",
- "#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))",
- "#products, section:has(h2:has-text('产品'))",
- "#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))",
- "#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))"
- ]
-
- visible_sections = 0
- for section in sections:
- if home_page._is_visible(section):
- visible_sections += 1
-
- # 移动端应该显示至少1个区域
- assert visible_sections >= 1, f"移动端可见区域不足,当前{visible_sections}个"
-
- @pytest.mark.responsive
- def test_touch_target_size_mobile(self, home_page: HomePage):
- """测试移动端触摸目标大小"""
- home_page.page.set_viewport_size({"width": 375, "height": 667})
- home_page.navigate()
-
- # 检查按钮和链接的大小
- buttons = home_page._find_all("button, a.button, .btn")
-
- for button in buttons[:5]: # 只检查前5个
- if button.count() > 0:
- box = button.first.bounding_box()
- if box:
- # 触摸目标应该至少44x44像素
- assert box["width"] >= 24, f"按钮宽度 {box['width']}px 可能太小"
- assert box["height"] >= 24, f"按钮高度 {box['height']}px 可能太小"
-
- @pytest.mark.responsive
- def test_text_readability_mobile(self, home_page: HomePage):
- """测试移动端文本可读性"""
- home_page.page.set_viewport_size({"width": 375, "height": 667})
- home_page.navigate()
-
- # 检查段落文本
- paragraphs = home_page._find_all("p")
-
- for para in paragraphs[:3]: # 只检查前3个
- if para.count() > 0:
- font_size = para.evaluate("el => getComputedStyle(el).fontSize")
- # 字体大小应该至少12px
- font_size_value = float(font_size.replace("px", ""))
- assert font_size_value >= 12, \
- f"段落字体大小 {font_size} 可能影响可读性"
-
- @pytest.mark.responsive
- def test_form_inputs_mobile(self, contact_page: ContactPage):
- """测试移动端表单输入"""
- contact_page.page.set_viewport_size({"width": 375, "height": 667})
- contact_page.navigate()
-
- # 检查表单输入框
- inputs = contact_page._find_all("input, textarea")
-
- for inp in inputs:
- if inp.count() > 0:
- box = inp.first.bounding_box()
- if box:
- # 输入框高度应该至少40px
- assert box["height"] >= 32, \
- f"输入框高度 {box['height']}px 可能太小不便触摸"
-
- @pytest.mark.responsive
- def test_landscape_orientation(self, home_page: HomePage):
- """测试横屏模式"""
- home_page.page.set_viewport_size({"width": 667, "height": 375})
- home_page.navigate()
-
- # 验证基本元素可见
- home_page.assert_element_visible("header", timeout=5000)
- home_page.assert_element_visible("main", timeout=5000)
- home_page.assert_element_visible("footer", timeout=5000)
-
- @pytest.mark.responsive
- def test_high_dpi_display(self, home_page: HomePage):
- """测试高DPI显示器"""
- # 设置视口大小(Playwright会自动处理高DPI显示)
- home_page.page.set_viewport_size({"width": 1920, "height": 1080})
-
- home_page.navigate()
-
- # 验证页面正常显示
- home_page.assert_element_visible("header", timeout=5000)
- home_page.logger.info("✅ 高DPI显示器测试通过")
-
- @pytest.mark.responsive
- def test_print_styles(self, home_page: HomePage):
- """测试打印样式"""
- home_page.navigate()
-
- # 模拟打印样式
- is_print_media = home_page.execute_js("""
- () => window.matchMedia('print').matches
- """)
-
- # 设置为打印模式
- home_page.execute_js("""
- () => {
- const style = document.createElement('style');
- style.innerHTML = '@media print { body { font-size: 12pt; } }';
- document.head.appendChild(style);
- }
- """)
-
- home_page.logger.info("✅ 打印样式应用完成")
diff --git a/e2e-tests/utils/__init__.py b/e2e-tests/utils/__init__.py
deleted file mode 100644
index eae7b09..0000000
--- a/e2e-tests/utils/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Utils模块
diff --git a/e2e-tests/utils/__pycache__/__init__.cpython-313.pyc b/e2e-tests/utils/__pycache__/__init__.cpython-313.pyc
deleted file mode 100644
index 9addb92..0000000
Binary files a/e2e-tests/utils/__pycache__/__init__.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/utils/__pycache__/data_generator.cpython-313.pyc b/e2e-tests/utils/__pycache__/data_generator.cpython-313.pyc
deleted file mode 100644
index e67c733..0000000
Binary files a/e2e-tests/utils/__pycache__/data_generator.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/utils/__pycache__/helpers.cpython-313.pyc b/e2e-tests/utils/__pycache__/helpers.cpython-313.pyc
deleted file mode 100644
index 6e9af39..0000000
Binary files a/e2e-tests/utils/__pycache__/helpers.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/utils/__pycache__/logger.cpython-313.pyc b/e2e-tests/utils/__pycache__/logger.cpython-313.pyc
deleted file mode 100644
index 6021124..0000000
Binary files a/e2e-tests/utils/__pycache__/logger.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/utils/__pycache__/report_generator.cpython-313.pyc b/e2e-tests/utils/__pycache__/report_generator.cpython-313.pyc
deleted file mode 100644
index d5776d6..0000000
Binary files a/e2e-tests/utils/__pycache__/report_generator.cpython-313.pyc and /dev/null differ
diff --git a/e2e-tests/utils/data_generator.py b/e2e-tests/utils/data_generator.py
deleted file mode 100644
index 78a696d..0000000
--- a/e2e-tests/utils/data_generator.py
+++ /dev/null
@@ -1,411 +0,0 @@
-"""
-测试数据生成模块
-提供测试过程中需要的各种测试数据生成功能
-"""
-
-import random
-import string
-import uuid
-import re
-from datetime import datetime, timedelta
-from typing import Any, Dict, List, Optional, Tuple, Union
-from dataclasses import dataclass, field
-from faker import Faker
-
-from config.settings import get_settings
-
-
-class ChineseFaker:
- """中文测试数据生成器"""
-
- def __init__(self, locale: str = "zh_CN"):
- self.faker_zh = Faker("zh_CN")
- self.faker_en = Faker("en_US")
-
- def name(self) -> str:
- """生成中文姓名"""
- return self.faker_zh.name()
-
- def first_name(self) -> str:
- """生成中文名字"""
- return self.faker_zh.first_name()
-
- def last_name(self) -> str:
- """生成中文姓氏"""
- return self.faker_zh.last_name()
-
- def phone_number(self) -> str:
- """生成中国手机号"""
- # 生成以13-19开头的11位手机号
- prefix = random.choice(["13", "14", "15", "16", "17", "18", "19"])
- suffix = "".join(random.choices(string.digits, k=8))
- return prefix + suffix
-
- def email(self, domain: Optional[str] = None) -> str:
- """生成邮箱"""
- if domain:
- return self.faker_zh.email(domain=domain)
- return self.faker_zh.email()
-
- def company_name(self) -> str:
- """生成公司名称"""
- prefixes = ["四川", "成都", "西部", "西南", "中国", "高新"]
- suffixes = ["科技", "信息", "网络", "软件", "数据", "智能", "创新", "未来"]
- name = f"{random.choice(prefixes)}{self.faker_zh.company()}{random.choice(suffixes)}"
- return name
-
- def address(self) -> str:
- """生成中文地址"""
- return self.faker_zh.address().replace("\n", "")
-
- def city(self) -> str:
- """生成城市名"""
- return self.faker_zh.city()
-
- def province(self) -> str:
- """生成省份名"""
- return self.faker_zh.province()
-
- def job_title(self) -> str:
- """生成职位名称"""
- titles = [
- "软件工程师", "产品经理", "UI设计师", "测试工程师",
- "项目主管", "技术总监", "架构师", "数据分析师",
- "运维工程师", "产品运营", "市场经理", "销售代表"
- ]
- return random.choice(titles)
-
- def username(self) -> str:
- """生成用户名"""
- return self.faker_zh.user_name()
-
- def password(self, length: int = 12) -> str:
- """生成密码"""
- chars = string.ascii_letters + string.digits + "!@#$%^&*"
- return "".join(random.choices(chars, k=length))
-
- def text(self, max_chars: int = 200) -> str:
- """生成随机中文文本"""
- paragraphs = []
- for _ in range(random.randint(1, 3)):
- sentences = []
- for _ in range(random.randint(3, 8)):
- sentence_len = random.randint(10, 30)
- sentence = self.faker_zh.sentence(nb_words=sentence_len)
- sentences.append(sentence)
- paragraphs.append("。".join(sentences) + "。")
- return "".join(paragraphs)[:max_chars]
-
- def sentence(self, nb_words: int = 20) -> str:
- """生成随机句子"""
- return self.faker_zh.sentence(nb_words=nb_words)
-
- def word(self) -> str:
- """生成随机词语"""
- return self.faker_zh.word()
-
- def words(self, nb: int = 5) -> List[str]:
- """生成随机词语列表"""
- return self.faker_zh.words(nb=nb)
-
- def date_of_birth(self, start_year: int = 1960, end_year: int = 2000) -> str:
- """生成出生日期"""
- return self.faker_zh.date_of_birth(
- minimum_age=end_year - datetime.now().year,
- maximum_age=start_year - datetime.now().year
- ).strftime("%Y-%m-%d")
-
- def credit_card_number(self) -> str:
- """生成信用卡号(测试用)"""
- return self.faker_zh.credit_card_number()
-
- def credit_card_provider(self) -> str:
- """生成信用卡提供商"""
- providers = ["Visa", "MasterCard", "银联", "JCB", "American Express"]
- return random.choice(providers)
-
- def ipv4(self) -> str:
- """生成IPv4地址"""
- return self.faker_zh.ipv4()
-
- def mac_address(self) -> str:
- """生成MAC地址"""
- return self.faker_zh.mac_address()
-
- def url(self) -> str:
- """生成URL"""
- return self.faker_zh.url()
-
- def uri_path(self) -> str:
- """生成URI路径"""
- return self.faker_zh.uri_path()
-
- def user_agent(self) -> str:
- """生成User-Agent"""
- return self.faker_zh.user_agent()
-
- def hex_color(self) -> str:
- """生成十六进制颜色"""
- return self.faker_zh.hex_color()
-
- def rgb_color(self) -> Tuple[int, int, int]:
- """生成RGB颜色"""
- return self.faker_zh.rgb_color()
-
-
-class EnglishFaker:
- """英文测试数据生成器"""
-
- def __init__(self):
- self.faker = Faker("en_US")
-
- def name(self) -> str:
- """生成英文姓名"""
- return self.faker.name()
-
- def first_name(self) -> str:
- """生成英文名字"""
- return self.faker.first_name()
-
- def last_name(self) -> str:
- """生成英文姓氏"""
- return self.faker.last_name()
-
- def email(self, domain: Optional[str] = None) -> str:
- """生成邮箱"""
- if domain:
- return self.faker.email(domain=domain)
- return self.faker.email()
-
- def phone_number(self) -> str:
- """生成美国电话号码"""
- return self.faker.phone_number()
-
- def company(self) -> str:
- """生成公司名称"""
- return self.faker.company()
-
- def address(self) -> str:
- """生成地址"""
- return self.faker.address().replace("\n", ", ")
-
- def city(self) -> str:
- """生成城市名"""
- return self.faker.city()
-
- def state(self) -> str:
- """生成州/省名"""
- return self.faker.state()
-
- def country(self) -> str:
- """生成国家名"""
- return self.faker.country()
-
- def zip_code(self) -> str:
- """生成邮编"""
- return self.faker.zipcode()
-
- def username(self) -> str:
- """生成用户名"""
- return self.faker.user_name()
-
- def password(self, length: int = 12) -> str:
- """生成密码"""
- chars = string.ascii_letters + string.digits + "!@#$%^&*"
- return "".join(random.choices(chars, k=length))
-
- def text(self, max_chars: int = 200) -> str:
- """生成随机英文文本"""
- return self.faker.text(max_nb_chars=max_chars)
-
- def sentence(self, nb_words: int = 10) -> str:
- """生成随机句子"""
- return self.faker.sentence(nb_words=nb_words)
-
- def paragraph(self, nb_sentences: int = 3) -> str:
- """生成段落"""
- return self.faker.paragraph(nb_sentences=nb_sentences)
-
- def date_of_birth(self, start_year: int = 1960, end_year: int = 2000) -> str:
- """生成出生日期"""
- return self.faker.date_of_birth(
- minimum_age=end_year - datetime.now().year,
- maximum_age=start_year - datetime.now().year
- ).strftime("%Y-%m-%d")
-
- def date_between(self, start_date: str, end_date: str) -> str:
- """生成日期范围"""
- start = datetime.strptime(start_date, "%Y-%m-%d")
- end = datetime.strptime(end_date, "%Y-%m-%d")
- return self.faker.date_between(start, end).strftime("%Y-%m-%d")
-
-
-class TestDataGenerator:
- """测试数据生成器主类"""
-
- def __init__(self):
- self.settings = get_settings()
- self.zh_faker = ChineseFaker()
- self.en_faker = EnglishFaker()
-
- def generate_contact_form_data(
- self,
- use_valid: bool = True,
- lang: str = "zh"
- ) -> Dict[str, str]:
- """生成联系表单数据"""
- if use_valid:
- if lang == "zh":
- return {
- "name": self.zh_faker.name(),
- "phone": self.zh_faker.phone_number(),
- "email": self.zh_faker.email(domain="example.com"),
- "subject": self.zh_faker.sentence(nb_words=8),
- "message": self.zh_faker.text(max_chars=300)
- }
- else:
- return {
- "name": self.en_faker.name(),
- "phone": self.en_faker.phone_number(),
- "email": self.en_faker.email(domain="example.com"),
- "subject": self.en_faker.sentence(nb_words=8),
- "message": self.en_faker.paragraph(nb_sentences=3)
- }
- else:
- return self.settings.test_form_data.get("invalid", {
- "email": "invalid-email",
- "phone": "123"
- })
-
- def generate_user_profile(self, lang: str = "zh") -> Dict[str, Any]:
- """生成用户资料数据"""
- if lang == "zh":
- return {
- "name": self.zh_faker.name(),
- "email": self.zh_faker.email(domain="example.com"),
- "phone": self.zh_faker.phone_number(),
- "address": self.zh_faker.address(),
- "job_title": self.zh_faker.job_title(),
- "company": self.zh_faker.company_name(),
- "date_of_birth": self.zh_faker.date_of_birth()
- }
- else:
- return {
- "name": self.en_faker.name(),
- "email": self.en_faker.email(domain="example.com"),
- "phone": self.en_faker.phone_number(),
- "address": self.en_faker.address(),
- "job_title": random.choice([
- "Software Engineer", "Product Manager", "Designer",
- "Marketing Manager", "Sales Representative", "Data Analyst"
- ]),
- "company": self.en_faker.company(),
- "date_of_birth": self.en_faker.date_of_birth()
- }
-
- def generate_search_query(self) -> str:
- """生成搜索查询"""
- topics = [
- "软件开发", "云计算", "人工智能", "数据分析",
- "数字化转型", "企业服务", "智能制造", "物联网"
- ]
- return random.choice(topics)
-
- def generate_news_article(self) -> Dict[str, Any]:
- """生成新闻文章数据"""
- return {
- "title": self.zh_faker.sentence(nb_words=12),
- "summary": self.zh_faker.text(max_chars=200),
- "content": self.zh_faker.text(max_chars=1000),
- "author": self.zh_faker.name(),
- "publish_date": datetime.now().strftime("%Y-%m-%d"),
- "category": random.choice(["公司新闻", "行业动态", "产品发布", "技术文章"])
- }
-
- def generate_product_data(self) -> Dict[str, Any]:
- """生成产品数据"""
- products = [
- {"name": "睿新ERP管理系统", "category": "企业软件"},
- {"name": "睿新客户关系管理系统", "category": "企业软件"},
- {"name": "睿新内容管理系统", "category": "企业软件"},
- {"name": "睿新商业智能平台", "category": "数据产品"}
- ]
- product = random.choice(products)
- return {
- "name": product["name"],
- "category": product["category"],
- "description": self.zh_faker.text(max_chars=300),
- "features": random.sample([
- "高性能", "高可用", "易扩展", "安全可靠",
- "智能化", "云原生", "移动优先", "低代码"
- ], k=4),
- "price": round(random.uniform(1000, 100000), 2)
- }
-
- def generate_company_info(self) -> Dict[str, str]:
- """生成公司信息"""
- return {
- "name": self.zh_faker.company_name(),
- "short_name": "".join(self.zh_faker.company_name()[:4]),
- "slogan": self.zh_faker.sentence(nb_words=6),
- "description": self.zh_faker.text(max_chars=200),
- "address": self.zh_faker.address(),
- "phone": self.zh_faker.phone_number(),
- "email": self.zh_faker.email(domain="example.com"),
- "website": self.zh_faker.url()
- }
-
- def generate_dates(self, count: int = 10) -> List[str]:
- """生成日期列表"""
- dates = []
- base_date = datetime.now()
- for i in range(count):
- date = base_date - timedelta(days=random.randint(0, 365))
- dates.append(date.strftime("%Y-%m-%d"))
- return dates
-
- def generate_unique_id(self) -> str:
- """生成唯一ID"""
- return str(uuid.uuid4())
-
- def generate_order_number(self) -> str:
- """生成订单号"""
- prefix = "ORD"
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
- random_suffix = "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
- return f"{prefix}{timestamp}{random_suffix}"
-
- def generate_numeric_range(
- self,
- min_val: int = 1,
- max_val: int = 100,
- decimals: int = 0
- ) -> Union[int, float]:
- """生成数值范围"""
- value = random.uniform(min_val, max_val)
- return round(value, decimals) if decimals else int(value)
-
- def generate_boolean(self) -> bool:
- """生成布尔值"""
- return random.choice([True, False])
-
- def generate_choice(self, options: List[Any]) -> Any:
- """从列表中随机选择一个"""
- return random.choice(options)
-
- def generate_color(self, format: str = "hex") -> Union[str, Tuple[int, int, int]]:
- """生成颜色值"""
- if format == "hex":
- return self.zh_faker.hex_color()
- elif format == "rgb":
- return self.zh_faker.rgb_color()
- return self.zh_faker.hex_color()
-
-
-# 全局测试数据生成器实例
-test_data_generator = TestDataGenerator()
-
-
-def get_test_data_generator() -> TestDataGenerator:
- """获取测试数据生成器"""
- return test_data_generator
diff --git a/e2e-tests/utils/helpers.py b/e2e-tests/utils/helpers.py
deleted file mode 100644
index d8ddf3d..0000000
--- a/e2e-tests/utils/helpers.py
+++ /dev/null
@@ -1,584 +0,0 @@
-"""
-辅助工具模块
-提供常用的测试辅助函数和工具
-"""
-
-import os
-import re
-import time
-import hashlib
-import json
-from datetime import datetime, timedelta
-from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple, Union, Callable
-from urllib.parse import urlparse, parse_qs, urljoin
-from functools import lru_cache
-
-from playwright.sync_api import Page, Locator, FrameLocator
-from playwright.sync_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
-
-from config.settings import get_settings
-from utils.logger import get_logger, PerformanceTimer
-
-
-class WaitCondition:
- """等待条件类"""
-
- @staticmethod
- def for_element_visible(selector: str, timeout: Optional[int] = None) -> Callable:
- """等待元素可见"""
- def condition(page: Page) -> bool:
- try:
- element = page.locator(selector).first
- return element.is_visible(timeout=timeout or 1000)
- except (PlaywrightError, PlaywrightTimeoutError):
- return False
- return condition
-
- @staticmethod
- def for_element_hidden(selector: str, timeout: Optional[int] = None) -> Callable:
- """等待元素隐藏"""
- def condition(page: Page) -> bool:
- try:
- element = page.locator(selector).first
- return not element.is_visible(timeout=timeout or 1000)
- except (PlaywrightError, PlaywrightTimeoutError):
- return True
- return condition
-
- @staticmethod
- def for_element_enabled(selector: str, timeout: Optional[int] = None) -> Callable:
- """等待元素可用"""
- def condition(page: Page) -> bool:
- try:
- element = page.locator(selector).first
- return element.is_enabled(timeout=timeout or 1000)
- except (PlaywrightError, PlaywrightTimeoutError):
- return False
- return condition
-
- @staticmethod
- def for_url_change(expected_url: str, timeout: Optional[int] = None) -> Callable:
- """等待URL变化"""
- def condition(page: Page) -> bool:
- return expected_url in page.url
- return condition
-
- @staticmethod
- def for_load_state(state: str = "networkidle", timeout: Optional[int] = None) -> Callable:
- """等待页面加载状态"""
- def condition(page: Page) -> bool:
- try:
- page.wait_for_load_state(state, timeout=timeout)
- return True
- except PlaywrightTimeoutError:
- return False
- return condition
-
- @staticmethod
- def for_function_return_true(func: Callable[[], bool], timeout: int = 5000, interval: int = 100) -> Callable:
- """等待函数返回True"""
- def condition(page: Page) -> bool:
- start_time = time.time() * 1000
- while time.time() * 1000 - start_time < timeout:
- try:
- result = page.evaluate(f"() => ({func.__code__.co_code})")
- if result:
- return True
- except Exception:
- pass
- time.sleep(interval / 1000)
- return False
- return condition
-
-
-class ElementHelper:
- """元素操作辅助类"""
-
- def __init__(self, page: Page):
- self.page = page
- self.logger = get_logger()
-
- def find_element(
- self,
- selector: str,
- timeout: Optional[int] = None,
- state: str = "visible"
- ) -> Locator:
- """查找元素"""
- timeout = timeout or get_settings().element_timeout
- locator = self.page.locator(selector).first
-
- try:
- locator.wait_for(state=state, timeout=timeout)
- return locator
- except PlaywrightTimeoutError:
- raise PlaywrightTimeoutError(
- f"未找到元素: {selector} (超时: {timeout}ms)"
- )
-
- def find_elements(self, selector: str) -> List[Locator]:
- """查找多个元素"""
- return self.page.locator(selector).all()
-
- def click_element(
- self,
- selector: str,
- timeout: Optional[int] = None,
- force: bool = False,
- **kwargs
- ) -> None:
- """点击元素"""
- self.logger.log_action(f"点击元素: {selector}")
- element = self.find_element(selector, timeout)
- element.click(force=force, **kwargs)
-
- def fill_input(
- self,
- selector: str,
- value: str,
- timeout: Optional[int] = None,
- clear: bool = True
- ) -> None:
- """填充输入框"""
- self.logger.log_action(f"填充输入框: {selector} = '{value}'")
- element = self.find_element(selector, timeout)
-
- if clear:
- element.clear()
-
- element.fill(value)
-
- def type_text(
- self,
- selector: str,
- text: str,
- timeout: Optional[int] = None,
- delay: Optional[int] = None
- ) -> None:
- """输入文本(逐字符)"""
- self.logger.log_action(f"输入文本: {selector} = '{text}'")
- element = self.find_element(selector, timeout)
- element.type(text, delay=delay)
-
- def get_element_text(
- self,
- selector: str,
- timeout: Optional[int] = None,
- strip: bool = True
- ) -> str:
- """获取元素文本"""
- element = self.find_element(selector, timeout)
- text = element.text_content()
- return text.strip() if strip and text else text
-
- def get_element_attribute(
- self,
- selector: str,
- attribute: str,
- timeout: Optional[int] = None
- ) -> Optional[str]:
- """获取元素属性"""
- element = self.find_element(selector, timeout)
- return element.get_attribute(attribute)
-
- def is_element_visible(
- self,
- selector: str,
- timeout: Optional[int] = None
- ) -> bool:
- """检查元素是否可见"""
- try:
- element = self.find_element(selector, timeout)
- return element.is_visible()
- except PlaywrightTimeoutError:
- return False
-
- def is_element_enabled(
- self,
- selector: str,
- timeout: Optional[int] = None
- ) -> bool:
- """检查元素是否可用"""
- try:
- element = self.find_element(selector, timeout)
- return element.is_enabled()
- except PlaywrightTimeoutError:
- return False
-
- def wait_for_selector(
- self,
- selector: str,
- timeout: Optional[int] = None,
- state: str = "visible"
- ) -> Locator:
- """等待选择器出现"""
- timeout = timeout or get_settings().element_timeout
- return self.page.wait_for_selector(selector, timeout=timeout, state=state)
-
- def wait_for_url(
- self,
- pattern: Union[str, re.Pattern],
- timeout: Optional[int] = None
- ) -> None:
- """等待URL匹配模式"""
- timeout = timeout or get_settings().page_load_timeout
- self.page.wait_for_url(pattern, timeout=timeout)
-
- def wait_for_load_state(
- self,
- state: str = "networkidle",
- timeout: Optional[int] = None
- ) -> None:
- """等待加载状态"""
- timeout = timeout or get_settings().page_load_timeout
- self.page.wait_for_load_state(state, timeout=timeout)
-
-
-class PageHelper:
- """页面操作辅助类"""
-
- def __init__(self, page: Page):
- self.page = page
- self.logger = get_logger()
- self.element_helper = ElementHelper(page)
-
- def navigate(
- self,
- url: str,
- wait_until: str = "networkidle",
- timeout: Optional[int] = None
- ) -> None:
- """导航到指定URL"""
- self.logger.log_action(f"导航到: {url}")
- timeout = timeout or get_settings().page_load_timeout
- self.page.goto(url, wait_until=wait_until, timeout=timeout)
-
- def reload_page(self, wait_until: str = "networkidle") -> None:
- """刷新页面"""
- self.logger.log_action("刷新页面")
- self.page.reload(wait_until=wait_until)
-
- def go_back(self, wait_until: str = "networkidle") -> None:
- """返回上一页"""
- self.logger.log_action("返回上一页")
- self.page.go_back(wait_until=wait_until)
-
- def go_forward(self, wait_until: str = "networkidle") -> None:
- """前进到下一页"""
- self.logger.log_action("前进到下一页")
- self.page.go_forward(wait_until=wait_until)
-
- def scroll_to_top(self) -> None:
- """滚动到页面顶部"""
- self.page.evaluate("window.scrollTo(0, 0)")
-
- def scroll_to_bottom(self) -> None:
- """滚动到页面底部"""
- self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
-
- def scroll_to_element(self, selector: str) -> None:
- """滚动到指定元素"""
- element = self.page.locator(selector).first
- element.scroll_into_view_if_needed()
-
- def get_current_url(self) -> str:
- """获取当前URL"""
- return self.page.url
-
- def get_page_title(self) -> str:
- """获取页面标题"""
- return self.page.title()
-
- def get_page_source(self) -> str:
- """获取页面源码"""
- return self.page.content()
-
- def take_screenshot(
- self,
- filename: str,
- path: Optional[str] = None,
- full_page: bool = False
- ) -> str:
- """截取页面截图"""
- path = path or get_settings().screenshots_dir
- Path(path).mkdir(parents=True, exist_ok=True)
-
- filepath = str(Path(path) / filename)
- self.page.screenshot(path=filepath, full_page=full_page)
-
- self.logger.log_action(f"截图已保存: {filepath}")
- return filepath
-
- def execute_javascript(self, script: str, *args) -> Any:
- """执行JavaScript代码"""
- return self.page.evaluate(script, *args)
-
- def wait_for_load_state(self, state: str = "networkidle", timeout: Optional[int] = None) -> None:
- """等待页面加载状态"""
- timeout = timeout or get_settings().page_load_timeout
- self.page.wait_for_load_state(state, timeout=timeout)
-
-
-class AssertionHelper:
- """断言辅助类"""
-
- def __init__(self, page: Page):
- self.page = page
- self.logger = get_logger()
-
- def assert_element_visible(
- self,
- selector: str,
- timeout: Optional[int] = None,
- message: Optional[str] = None
- ) -> None:
- """断言元素可见"""
- try:
- element = ElementHelper(self.page).find_element(selector, timeout)
- assert element.is_visible(), message or f"元素不可见: {selector}"
- self.logger.log_assertion(f"元素可见: {selector}", True)
- except (PlaywrightError, PlaywrightTimeoutError, AssertionError):
- self.logger.log_assertion(f"元素可见: {selector}", False)
- raise
-
- def assert_element_hidden(
- self,
- selector: str,
- timeout: Optional[int] = None,
- message: Optional[str] = None
- ) -> None:
- """断言元素隐藏"""
- self.logger.log_assertion(f"元素隐藏: {selector}", True)
- element = ElementHelper(self.page).find_element(selector, timeout)
- assert not element.is_visible(), message or f"元素应该隐藏但可见: {selector}"
-
- def assert_element_text_contains(
- self,
- selector: str,
- expected_text: str,
- timeout: Optional[int] = None,
- message: Optional[str] = None
- ) -> None:
- """断言元素文本包含预期文本"""
- self.logger.log_assertion(f"文本包含: {selector} 包含 '{expected_text}'", True)
- element = ElementHelper(self.page).find_element(selector, timeout)
- actual_text = element.text_content()
- assert expected_text in actual_text, message or (
- f"元素文本不匹配: 预期包含 '{expected_text}',实际为 '{actual_text}'"
- )
-
- def assert_element_text_equals(
- self,
- selector: str,
- expected_text: str,
- timeout: Optional[int] = None,
- message: Optional[str] = None
- ) -> None:
- """断言元素文本等于预期文本"""
- self.logger.log_assertion(f"文本相等: {selector} == '{expected_text}'", True)
- element = ElementHelper(self.page).find_element(selector, timeout)
- actual_text = element.text_content()
- assert actual_text == expected_text, message or (
- f"元素文本不匹配: 预期 '{expected_text}',实际为 '{actual_text}'"
- )
-
- def assert_url_contains(self, expected_url: str, message: Optional[str] = None) -> None:
- """断言URL包含预期文本"""
- self.logger.log_assertion(f"URL包含: {expected_url}", True)
- assert expected_url in self.page.url, message or (
- f"URL不匹配: 预期包含 '{expected_url}',实际为 '{self.page.url}'"
- )
-
- def assert_url_equals(self, expected_url: str, message: Optional[str] = None) -> None:
- """断言URL等于预期URL"""
- self.logger.log_assertion(f"URL相等: {expected_url}", True)
- assert self.page.url == expected_url, message or (
- f"URL不匹配: 预期 '{expected_url}',实际为 '{self.page.url}'"
- )
-
- def assert_page_title_contains(self, expected_title: str, message: Optional[str] = None) -> None:
- """断言页面标题包含预期文本"""
- self.logger.log_assertion(f"标题包含: {expected_title}", True)
- actual_title = self.page.title()
- assert expected_title in actual_title, message or (
- f"页面标题不匹配: 预期包含 '{expected_title}',实际为 '{actual_title}'"
- )
-
- def assert_element_count(
- self,
- selector: str,
- expected_count: int,
- message: Optional[str] = None
- ) -> None:
- """断言元素数量"""
- self.logger.log_assertion(f"元素数量: {selector} == {expected_count}", True)
- elements = self.page.locator(selector).all()
- assert len(elements) == expected_count, message or (
- f"元素数量不匹配: 预期 {expected_count},实际 {len(elements)}"
- )
-
- def assert_element_attribute_equals(
- self,
- selector: str,
- attribute: str,
- expected_value: str,
- timeout: Optional[int] = None,
- message: Optional[str] = None
- ) -> None:
- """断言元素属性等于预期值"""
- self.logger.log_assertion(f"属性相等: {selector}.{attribute} == '{expected_value}'", True)
- element = ElementHelper(self.page).find_element(selector, timeout)
- actual_value = element.get_attribute(attribute)
- assert actual_value == expected_value, message or (
- f"元素属性不匹配: {attribute} 预期 '{expected_value}',实际 '{actual_value}'"
- )
-
-
-class UrlHelper:
- """URL辅助类"""
-
- @staticmethod
- def parse_url(url: str) -> Dict[str, Any]:
- """解析URL"""
- parsed = urlparse(url)
- return {
- "scheme": parsed.scheme,
- "netloc": parsed.netloc,
- "path": parsed.path,
- "params": parse_qs(parsed.query),
- "query": parsed.query,
- "fragment": parsed.fragment
- }
-
- @staticmethod
- def get_domain(url: str) -> str:
- """获取URL域名"""
- parsed = urlparse(url)
- return parsed.netloc
-
- @staticmethod
- def get_path(url: str) -> str:
- """获取URL路径"""
- parsed = urlparse(url)
- return parsed.path
-
- @staticmethod
- def is_absolute_url(url: str) -> bool:
- """判断是否为绝对URL"""
- return bool(urlparse(url).scheme)
-
- @staticmethod
- def join_url(base: str, path: str) -> str:
- """拼接URL"""
- return urljoin(base, path)
-
- @staticmethod
- def remove_trailing_slash(url: str) -> str:
- """移除URL末尾的斜杠"""
- if url.endswith("/") and len(url) > 1:
- return url[:-1]
- return url
-
-
-class FileHelper:
- """文件操作辅助类"""
-
- @staticmethod
- def read_json(filepath: str) -> Dict[str, Any]:
- """读取JSON文件"""
- with open(filepath, 'r', encoding='utf-8') as f:
- return json.load(f)
-
- @staticmethod
- def write_json(filepath: str, data: Any, indent: int = 2) -> None:
- """写入JSON文件"""
- Path(filepath).parent.mkdir(parents=True, exist_ok=True)
- with open(filepath, 'w', encoding='utf-8') as f:
- json.dump(data, f, ensure_ascii=False, indent=indent)
-
- @staticmethod
- def read_text(filepath: str) -> str:
- """读取文本文件"""
- with open(filepath, 'r', encoding='utf-8') as f:
- return f.read()
-
- @staticmethod
- def write_text(filepath: str, content: str) -> None:
- """写入文本文件"""
- Path(filepath).parent.mkdir(parents=True, exist_ok=True)
- with open(filepath, 'w', encoding='utf-8') as f:
- f.write(content)
-
- @staticmethod
- def create_directory(path: str) -> None:
- """创建目录"""
- Path(path).mkdir(parents=True, exist_ok=True)
-
- @staticmethod
- def delete_directory(path: str) -> None:
- """删除目录"""
- import shutil
- if Path(path).exists():
- shutil.rmtree(path)
-
- @staticmethod
- def cleanup_directory(path: str, pattern: str = "*") -> None:
- """清理目录中的文件"""
- import glob
- files = glob.glob(str(Path(path) / pattern))
- for file in files:
- try:
- os.remove(file)
- except IsADirectoryError:
- FileHelper.delete_directory(file)
-
-
-def wait(
- condition: Callable,
- timeout: int = 10000,
- interval: int = 500,
- message: str = "等待条件超时"
-) -> bool:
- """等待条件满足"""
- start_time = time.time() * 1000
-
- while time.time() * 1000 - start_time < timeout:
- if condition():
- return True
- time.sleep(interval / 1000)
-
- raise TimeoutError(message)
-
-
-def retry(
- func: Callable,
- max_retries: int = 3,
- delay: int = 1000,
- exceptions: Tuple[Exception, ...] = (Exception,)
-) -> Any:
- """重试函数"""
- last_exception = None
-
- for attempt in range(max_retries + 1):
- try:
- return func()
- except exceptions as e:
- last_exception = e
- if attempt < max_retries:
- time.sleep(delay / 1000)
-
- raise last_exception
-
-
-def generate_test_id(prefix: str = "test") -> str:
- """生成测试ID"""
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- random_hash = hashlib.md5(f"{timestamp}_{os.getpid()}".encode()).hexdigest()[:8]
- return f"{prefix}_{timestamp}_{random_hash}"
-
-
-@lru_cache(maxsize=128)
-def normalize_text(text: str) -> str:
- """标准化文本(去除多余空白)"""
- return re.sub(r'\s+', ' ', text).strip()
diff --git a/e2e-tests/utils/logger.py b/e2e-tests/utils/logger.py
deleted file mode 100644
index 789751c..0000000
--- a/e2e-tests/utils/logger.py
+++ /dev/null
@@ -1,272 +0,0 @@
-"""
-日志工具模块
-提供测试过程中的日志记录功能
-"""
-
-import os
-import sys
-import logging
-from datetime import datetime
-from pathlib import Path
-from typing import Optional, Union
-from functools import wraps
-import traceback
-
-from config.settings import get_settings
-
-
-class ColoredFormatter(logging.Formatter):
- """彩色日志格式化器"""
-
- # ANSI颜色代码
- COLORS = {
- 'DEBUG': '\033[36m', # 青色
- 'INFO': '\033[32m', # 绿色
- 'WARNING': '\033[33m', # 黄色
- 'ERROR': '\033[31m', # 红色
- 'CRITICAL': '\033[35m', # 紫色
- 'RESET': '\033[0m', # 重置
- }
-
- def format(self, record: logging.LogRecord) -> str:
- # 获取颜色
- color = self.COLORS.get(record.levelname, self.COLORS['RESET'])
-
- # 格式化消息
- message = super().format(record)
-
- # 添加颜色(如果不是纯文本输出)
- if sys.stdout.isatty():
- return f"{color}{message}{self.COLORS['RESET']}"
- return message
-
-
-class TestLogger:
- """测试日志管理器"""
-
- _instance: Optional['TestLogger'] = None
- _initialized: bool = False
-
- def __new__(cls) -> 'TestLogger':
- if cls._instance is None:
- cls._instance = super().__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not TestLogger._initialized:
- self._setup_logging()
- TestLogger._initialized = True
-
- def _setup_logging(self) -> None:
- """设置日志配置"""
- self.settings = get_settings()
- self.logger = logging.getLogger("e2e_tests")
- self.logger.setLevel(getattr(logging, self.settings.log_level))
-
- # 清除现有处理器
- self.logger.handlers.clear()
-
- # 创建日志目录
- log_dir = Path(self.settings.log_file).parent
- log_dir.mkdir(parents=True, exist_ok=True)
-
- # 文件处理器
- file_handler = logging.FileHandler(
- self.settings.log_file,
- encoding='utf-8',
- mode='a'
- )
- file_handler.setLevel(logging.DEBUG)
-
- # 控制台处理器
- console_handler = logging.StreamHandler(sys.stdout)
- console_handler.setLevel(getattr(logging, self.settings.log_level))
-
- # 设置格式化器
- file_format = logging.Formatter(
- '%(asctime)s | %(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s',
- datefmt='%Y-%m-%d %H:%M:%S'
- )
-
- console_format_str = '%(asctime)s | %(levelname)-8s | %(message)s'
-
- file_handler.setFormatter(file_format)
-
- if sys.stdout.isatty():
- console_handler.setFormatter(ColoredFormatter(console_format_str, datefmt='%H:%M:%S'))
- else:
- console_handler.setFormatter(logging.Formatter(console_format_str, datefmt='%H:%M:%S'))
-
- self.logger.addHandler(file_handler)
- self.logger.addHandler(console_handler)
-
- def debug(self, message: str, **kwargs) -> None:
- """记录DEBUG级别日志"""
- self.logger.debug(self._format_message(message, **kwargs))
-
- def info(self, message: str, **kwargs) -> None:
- """记录INFO级别日志"""
- self.logger.info(self._format_message(message, **kwargs))
-
- def warning(self, message: str, **kwargs) -> None:
- """记录WARNING级别日志"""
- self.logger.warning(self._format_message(message, **kwargs))
-
- def error(self, message: str, exc_info: bool = True, **kwargs) -> None:
- """记录ERROR级别日志"""
- self.logger.error(
- self._format_message(message, **kwargs),
- exc_info=exc_info
- )
-
- def critical(self, message: str, exc_info: bool = True, **kwargs) -> None:
- """记录CRITICAL级别日志"""
- self.logger.critical(
- self._format_message(message, **kwargs),
- exc_info=exc_info
- )
-
- def exception(self, message: str, **kwargs) -> None:
- """记录异常日志(自动包含堆栈信息)"""
- self.error(message, exc_info=True, **kwargs)
-
- def _format_message(self, message: str, **kwargs) -> str:
- """格式化日志消息"""
- if kwargs:
- extra_info = " | ".join(f"{k}={v}" for k, v in kwargs.items())
- return f"{message} | {extra_info}"
- return message
-
- def log_test_start(self, test_name: str, **extra_info) -> None:
- """记录测试开始"""
- self.info(f"🧪 测试开始: {test_name}", **extra_info)
-
- def log_test_end(self, test_name: str, status: str, duration: float, **extra_info) -> None:
- """记录测试结束"""
- emoji = "✅" if status == "PASSED" else "❌" if status == "FAILED" else "⏭️"
- self.info(f"{emoji} 测试结束: {test_name} | 状态: {status} | 耗时: {duration:.2f}s", **extra_info)
-
- def log_step(self, step_name: str, **extra_info) -> None:
- """记录测试步骤"""
- self.info(f"📋 步骤: {step_name}", **extra_info)
-
- def log_action(self, action: str, **extra_info) -> None:
- """记录用户操作"""
- self.info(f"👆 操作: {action}", **extra_info)
-
- def log_assertion(self, assertion: str, result: bool, **extra_info) -> None:
- """记录断言结果"""
- status = "✅ 通过" if result else "❌ 失败"
- self.info(f"🔍 断言: {assertion} | {status}", **extra_info)
-
- def log_performance(self, metric: str, value: float, threshold: Optional[float] = None, **extra_info) -> None:
- """记录性能指标"""
- if threshold and value > threshold:
- self.warning(f"📊 性能指标 - {metric}: {value:.2f}ms (阈值: {threshold:.2f}ms)", **extra_info)
- else:
- self.info(f"📊 性能指标 - {metric}: {value:.2f}ms", **extra_info)
-
- def log_error_context(self, context: str, error: Exception, **extra_info) -> None:
- """记录错误上下文"""
- self.error(f"🚨 错误上下文: {context}", exc_info=False, **extra_info)
- self.error(f"错误信息: {str(error)}", exc_info=False)
- self.debug(f"堆栈跟踪:\n{traceback.format_exc()}")
-
- def section(self, title: str) -> None:
- """记录分段标题"""
- separator = "=" * 60
- self.info(f"\n{separator}")
- self.info(f" {title}")
- self.info(f"{separator}\n")
-
- def divider(self, char: str = "-", length: int = 40) -> None:
- """记录分隔线"""
- self.info(char * length)
-
-
-def get_logger() -> TestLogger:
- """获取日志管理器实例"""
- return TestLogger()
-
-
-def log_decorator(func):
- """函数日志装饰器"""
- @wraps(func)
- def wrapper(*args, **kwargs):
- logger = get_logger()
- func_name = func.__name__
-
- logger.log_test_start(func_name)
- logger.divider()
-
- try:
- result = func(*args, **kwargs)
- logger.log_test_end(func_name, "PASSED", 0)
- return result
- except Exception as e:
- logger.log_test_end(func_name, "FAILED", 0)
- logger.log_error_context(func_name, e)
- raise
-
- return wrapper
-
-
-class PerformanceTimer:
- """性能计时器"""
-
- def __init__(self, logger: Optional[TestLogger] = None):
- self.logger = logger or get_logger()
- self.start_time: Optional[float] = None
- self.end_time: Optional[float] = None
- self.elapsed: Optional[float] = None
-
- def start(self) -> 'PerformanceTimer':
- """开始计时"""
- self.start_time = self._time_ms()
- return self
-
- def stop(self) -> 'PerformanceTimer':
- """停止计时"""
- self.end_time = self._time_ms()
- self.elapsed = self.end_time - self.start_time
- return self
-
- def reset(self) -> 'PerformanceTimer':
- """重置计时器"""
- self.start_time = None
- self.end_time = None
- self.elapsed = None
- return self
-
- @staticmethod
- def _time_ms() -> float:
- """获取当前时间(毫秒)"""
- import time
- return time.time() * 1000
-
- @property
- def seconds(self) -> float:
- """获取经过时间(秒)"""
- return self.elapsed / 1000 if self.elapsed else 0
-
- @property
- def milliseconds(self) -> float:
- """获取经过时间(毫秒)"""
- return self.elapsed if self.elapsed else 0
-
- def log(self, operation: str, threshold: Optional[float] = None) -> None:
- """记录操作耗时"""
- self.logger.log_performance(
- operation,
- self.milliseconds,
- threshold
- )
-
- def __enter__(self) -> 'PerformanceTimer':
- """上下文管理器入口"""
- self.start()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
- """上下文管理器出口"""
- self.stop()
diff --git a/e2e-tests/utils/report_generator.py b/e2e-tests/utils/report_generator.py
deleted file mode 100644
index aac8c23..0000000
--- a/e2e-tests/utils/report_generator.py
+++ /dev/null
@@ -1,606 +0,0 @@
-"""
-测试报告生成模块
-提供HTML、JSON、Markdown等多种格式的测试报告生成功能
-"""
-
-import json
-import os
-import re
-import time
-from abc import ABC, abstractmethod
-from dataclasses import dataclass, field, asdict
-from datetime import datetime
-from enum import Enum
-from html import escape
-from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple, Union
-from collections import defaultdict
-import base64
-from io import BytesIO
-
-from jinja2 import Environment, FileSystemLoader
-import matplotlib
-matplotlib.use('Agg') # 使用非GUI后端
-import matplotlib.pyplot as plt
-import numpy as np
-
-from config.settings import get_settings
-
-
-class TestStatus(Enum):
- """测试状态枚举"""
- PASSED = "passed"
- FAILED = "failed"
- SKIPPED = "skipped"
- ERROR = "error"
- XFAIL = "xfail"
- XPASS = "xpass"
-
-
-@dataclass
-class TestResult:
- """单个测试结果"""
- test_id: str
- test_name: str
- test_file: str
- test_class: str
- status: TestStatus
- start_time: datetime
- end_time: datetime
- duration: float
- error_message: Optional[str] = None
- error_traceback: Optional[str] = None
- screenshot_path: Optional[str] = None
- logs: List[str] = field(default_factory=list)
- metadata: Dict[str, Any] = field(default_factory=dict)
- parameters: Dict[str, Any] = field(default_factory=dict)
- browser: Optional[str] = None
- viewport: Optional[Tuple[int, int]] = None
-
- def to_dict(self) -> Dict[str, Any]:
- """转换为字典"""
- data = asdict(self)
- data["status"] = self.status.value
- data["start_time"] = self.start_time.isoformat()
- data["end_time"] = self.end_time.isoformat()
- return data
-
- @property
- def passed(self) -> bool:
- """是否通过"""
- return self.status == TestStatus.PASSED
-
- @property
- def failed(self) -> bool:
- """是否失败"""
- return self.status in [TestStatus.FAILED, TestStatus.ERROR]
-
-
-@dataclass
-class TestSuiteResult:
- """测试套件结果"""
- suite_name: str
- test_count: int = 0
- passed_count: int = 0
- failed_count: int = 0
- skipped_count: int = 0
- error_count: int = 0
- duration: float = 0.0
- test_results: List[TestResult] = field(default_factory=list)
- start_time: Optional[datetime] = None
- end_time: Optional[datetime] = None
-
- @property
- def pass_rate(self) -> float:
- """通过率"""
- if self.test_count == 0:
- return 0.0
- return (self.passed_count / self.test_count) * 100
-
- @property
- def success(self) -> bool:
- """是否全部通过"""
- return self.failed_count == 0 and self.error_count == 0
-
-
-class ReportGenerator(ABC):
- """报告生成器抽象基类"""
-
- @abstractmethod
- def generate(self, suite_results: List[TestSuiteResult], output_path: str) -> str:
- """生成报告"""
- pass
-
- @abstractmethod
- def get_format(self) -> str:
- """获取报告格式"""
- pass
-
-
-class HTMLReportGenerator(ReportGenerator):
- """HTML报告生成器"""
-
- def __init__(self):
- self.settings = get_settings()
- self.env = Environment(
- loader=FileSystemLoader(Path(__file__).parent / "templates"),
- autoescape=True
- )
- self.template = self.env.get_template("html_report.html")
-
- def get_format(self) -> str:
- return "html"
-
- def generate(
- self,
- suite_results: List[TestSuiteResult],
- output_path: str
- ) -> str:
- """生成HTML报告"""
- # 汇总所有测试结果
- all_results = []
- for suite in suite_results:
- all_results.extend(suite.test_results)
-
- # 计算统计信息
- stats = self._calculate_stats(suite_results, all_results)
-
- # 生成图表
- charts = self._generate_charts(suite_results, all_results)
-
- # 准备模板数据
- context = {
- "title": self.settings.report_title,
- "description": self.settings.report_description,
- "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "stats": stats,
- "charts": charts,
- "suites": suite_results,
- "all_results": all_results,
- "git_info": self._get_git_info(),
- "settings": self.settings
- }
-
- # 渲染模板
- html_content = self.template.render(**context)
-
- # 确保输出目录存在
- Path(output_path).parent.mkdir(parents=True, exist_ok=True)
-
- # 写入文件
- with open(output_path, "w", encoding="utf-8") as f:
- f.write(html_content)
-
- return output_path
-
- def _calculate_stats(
- self,
- suite_results: List[TestSuiteResult],
- all_results: List[TestResult]
- ) -> Dict[str, Any]:
- """计算统计信息"""
- total = len(all_results)
- passed = sum(1 for r in all_results if r.passed)
- failed = sum(1 for r in all_results if r.failed)
- skipped = sum(1 for r in all_results if r.status == TestStatus.SKIPPED)
-
- total_duration = sum(r.duration for r in all_results)
-
- # 按状态分组
- by_status = defaultdict(list)
- for result in all_results:
- by_status[result.status.value].append(result)
-
- # 按浏览器分组
- by_browser = defaultdict(list)
- for result in all_results:
- if result.browser:
- by_browser[result.browser].append(result)
-
- # 按文件分组
- by_file = defaultdict(list)
- for result in all_results:
- by_file[result.test_file].append(result)
-
- # 失败和错误的测试
- failed_tests = [r for r in all_results if r.failed]
-
- return {
- "total": total,
- "passed": passed,
- "failed": failed,
- "skipped": skipped,
- "pass_rate": round((passed / total * 100) if total > 0 else 0, 2),
- "total_duration": round(total_duration, 2),
- "average_duration": round(total_duration / total, 2) if total > 0 else 0,
- "by_status": dict(by_status),
- "by_browser": dict(by_browser),
- "by_file": dict(by_file),
- "failed_tests": failed_tests,
- "suite_count": len(suite_results),
- "success": failed == 0
- }
-
- def _generate_charts(
- self,
- suite_results: List[TestSuiteResult],
- all_results: List[TestResult]
- ) -> Dict[str, str]:
- """生成图表"""
- charts = {}
-
- # 1. 测试状态饼图
- charts["status_pie"] = self._create_status_pie_chart(all_results)
-
- # 2. 套件结果条形图
- charts["suite_results"] = self._create_suite_results_chart(suite_results)
-
- # 3. 执行时间图表
- charts["duration"] = self._create_duration_chart(all_results)
-
- # 4. 浏览器分布图
- browsers = defaultdict(int)
- for result in all_results:
- if result.browser:
- browsers[result.browser] += 1
- if browsers:
- charts["browser_distribution"] = self._create_browser_chart(dict(browsers))
-
- return charts
-
- def _create_status_pie_chart(self, results: List[TestResult]) -> str:
- """创建状态饼图"""
- counts = defaultdict(int)
- for r in results:
- counts[r.status.value] += 1
-
- labels = []
- sizes = []
- colors = []
- color_map = {
- "passed": "#22c55e",
- "failed": "#ef4444",
- "skipped": "#94a3b8",
- "error": "#f97316",
- "xfail": "#eab308",
- "xpass": "#3b82f6"
- }
-
- for status, count in counts.items():
- labels.append(f"{status} ({count})")
- sizes.append(count)
- colors.append(color_map.get(status, "#6b7280"))
-
- if not sizes:
- return ""
-
- fig, ax = plt.subplots(figsize=(8, 8))
- ax.pie(sizes, labels=labels, colors=colors, autopct="%1.1f%%",
- startangle=90, textprops={"fontsize": 12})
- ax.axis("equal")
-
- return self._fig_to_base64(fig)
-
- def _create_suite_results_chart(self, suites: List[TestSuiteResult]) -> str:
- """创建套件结果图表"""
- names = [s.suite_name for s in suites]
- passed = [s.passed_count for s in suites]
- failed = [s.failed_count for s in suites]
-
- fig, ax = plt.subplots(figsize=(12, 6))
- x = np.arange(len(names))
- width = 0.35
-
- bars1 = ax.bar(x - width/2, passed, width, label="Passed", color="#22c55e")
- bars2 = ax.bar(x + width/2, failed, width, label="Failed", color="#ef4444")
-
- ax.set_xlabel("Test Suite")
- ax.set_ylabel("Test Count")
- ax.set_title("Test Results by Suite")
- ax.set_xticks(x)
- ax.set_xticklabels(names, rotation=45, ha="right")
- ax.legend()
-
- # 添加数值标签
- for bar in bars1:
- height = bar.get_height()
- ax.annotate(f"{int(height)}",
- xy=(bar.get_x() + bar.get_width() / 2, height),
- xytext=(0, 3), textcoords="offset points",
- ha="center", va="bottom", fontsize=8)
-
- for bar in bars2:
- height = bar.get_height()
- if height > 0:
- ax.annotate(f"{int(height)}",
- xy=(bar.get_x() + bar.get_width() / 2, height),
- xytext=(0, 3), textcoords="offset points",
- ha="center", va="bottom", fontsize=8)
-
- return self._fig_to_base64(fig)
-
- def _create_duration_chart(self, results: List[TestResult]) -> str:
- """创建执行时间图表"""
- # 获取前20个最耗时的测试
- sorted_results = sorted(results, key=lambda x: x.duration, reverse=True)[:20]
-
- names = [r.test_name[:30] + "..." if len(r.test_name) > 30 else r.test_name
- for r in sorted_results]
- durations = [r.duration for r in sorted_results]
-
- fig, ax = plt.subplots(figsize=(12, 8))
- bars = ax.barh(names, durations, color="#3b82f6")
-
- ax.set_xlabel("Duration (seconds)")
- ax.set_title("Top 20 Slowest Tests")
- ax.invert_yaxis()
-
- # 添加数值标签
- for bar, duration in zip(bars, durations):
- ax.annotate(f"{duration:.2f}s",
- xy=(duration, bar.get_y() + bar.get_height() / 2),
- xytext=(3, 0), textcoords="offset points",
- ha="left", va="center", fontsize=8)
-
- return self._fig_to_base64(fig)
-
- def _create_browser_chart(self, browsers: Dict[str, int]) -> str:
- """创建浏览器分布图"""
- labels = list(browsers.keys())
- sizes = list(browsers.values())
- colors = ["#4285f4", "#ea4335", "#fbbc05", "#34a853"]
-
- fig, ax = plt.subplots(figsize=(8, 8))
- ax.pie(sizes, labels=labels, colors=colors[:len(labels)],
- autopct="%1.1f%%", startangle=90, textprops={"fontsize": 12})
- ax.axis("equal")
-
- return self._fig_to_base64(fig)
-
- def _fig_to_base64(self, fig) -> str:
- """将matplotlib图表转换为base64字符串"""
- buffer = BytesIO()
- fig.savefig(buffer, format="png", dpi=100, bbox_inches="tight")
- buffer.seek(0)
- img_str = base64.b64encode(buffer.read()).decode("utf-8")
- plt.close(fig)
- return f"data:image/png;base64,{img_str}"
-
- def _get_git_info(self) -> Dict[str, str]:
- """获取Git信息"""
- git_info = {
- "branch": self.settings.git_branch,
- "commit": self.settings.git_commit,
- "repository": self.settings.git_repository
- }
-
- # 尝试从环境变量获取
- if not git_info["branch"]:
- git_info["branch"] = os.environ.get("GIT_BRANCH", "")
- if not git_info["commit"]:
- git_info["commit"] = os.environ.get("GIT_COMMIT", os.environ.get("GITHUB_SHA", ""))
-
- return git_info
-
-
-class JSONReportGenerator(ReportGenerator):
- """JSON报告生成器"""
-
- def get_format(self) -> str:
- return "json"
-
- def generate(
- self,
- suite_results: List[TestSuiteResult],
- output_path: str
- ) -> str:
- """生成JSON报告"""
- report = {
- "report_info": {
- "title": get_settings().report_title,
- "description": get_settings().report_description,
- "generated_at": datetime.now().isoformat(),
- "version": "1.0.0"
- },
- "summary": self._calculate_summary(suite_results),
- "suites": []
- }
-
- for suite in suite_results:
- suite_data = {
- "name": suite.suite_name,
- "test_count": suite.test_count,
- "passed": suite.passed_count,
- "failed": suite.failed_count,
- "skipped": suite.skipped_count,
- "duration": suite.duration,
- "pass_rate": suite.pass_rate,
- "success": suite.success,
- "tests": [r.to_dict() for r in suite.test_results]
- }
- report["suites"].append(suite_data)
-
- # 确保输出目录存在
- Path(output_path).parent.mkdir(parents=True, exist_ok=True)
-
- # 写入文件
- with open(output_path, "w", encoding="utf-8") as f:
- json.dump(report, f, ensure_ascii=False, indent=2)
-
- return output_path
-
- def _calculate_summary(self, suites: List[TestSuiteResult]) -> Dict[str, Any]:
- """计算汇总信息"""
- all_results = []
- for suite in suites:
- all_results.extend(suite.test_results)
-
- return {
- "total": len(all_results),
- "passed": sum(1 for r in all_results if r.passed),
- "failed": sum(1 for r in all_results if r.failed),
- "skipped": sum(1 for r in all_results if r.status == TestStatus.SKIPPED),
- "duration": sum(r.duration for r in all_results)
- }
-
-
-class MarkdownReportGenerator(ReportGenerator):
- """Markdown报告生成器"""
-
- def get_format(self) -> str:
- return "markdown"
-
- def generate(
- self,
- suite_results: List[TestSuiteResult],
- output_path: str
- ) -> str:
- """生成Markdown报告"""
- lines = [
- f"# {get_settings().report_title}",
- "",
- f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
- "",
- "## 📊 测试汇总",
- ""
- ]
-
- # 汇总信息
- all_results = []
- for suite in suite_results:
- all_results.extend(suite.test_results)
-
- total = len(all_results)
- passed = sum(1 for r in all_results if r.passed)
- failed = sum(1 for r in all_results if r.failed)
-
- lines.extend([
- f"- **总计**: {total} 个测试",
- f"- **通过**: {passed} 个 ✅",
- f"- **失败**: {failed} 个 ❌",
- f"- **通过率**: {round(passed / total * 100, 2) if total > 0 else 0}%",
- ""
- ])
-
- # 套件详情
- lines.append("## 📁 套件详情")
- lines.append("")
-
- for suite in suite_results:
- lines.append(f"### {suite.suite_name}")
- lines.append("")
- lines.append(f"- 测试数: {suite.test_count}")
- lines.append(f"- 通过: {suite.passed_count}")
- lines.append(f"- 失败: {suite.failed_count}")
- lines.append(f"- 耗时: {suite.duration:.2f}s")
- lines.append(f"- 通过率: {suite.pass_rate:.2f}%")
- lines.append("")
-
- # 失败测试详情
- failed_tests = [r for r in all_results if r.failed]
- if failed_tests:
- lines.append("## ❌ 失败测试")
- lines.append("")
-
- for result in failed_tests:
- lines.append(f"### {result.test_name}")
- lines.append("")
- lines.append(f"- **文件**: {result.test_file}")
- lines.append(f"- **状态**: {result.status.value}")
- lines.append(f"- **耗时**: {result.duration:.2f}s")
- if result.error_message:
- lines.append(f"- **错误**: {result.error_message}")
- lines.append("")
-
- content = "\n".join(lines)
-
- # 确保输出目录存在
- Path(output_path).parent.mkdir(parents=True, exist_ok=True)
-
- # 写入文件
- with open(output_path, "w", encoding="utf-8") as f:
- f.write(content)
-
- return output_path
-
-
-class ReportManager:
- """报告管理器"""
-
- def __init__(self):
- self.settings = get_settings()
- self.generators = {
- "html": HTMLReportGenerator(),
- "json": JSONReportGenerator(),
- "markdown": MarkdownReportGenerator()
- }
- self.current_results: List[TestSuiteResult] = []
-
- def add_result(self, result: TestResult) -> None:
- """添加测试结果"""
- # 查找或创建对应的套件
- suite_name = result.test_class or "default"
- suite = next(
- (s for s in self.current_results if s.suite_name == suite_name),
- None
- )
-
- if suite is None:
- suite = TestSuiteResult(suite_name=suite_name)
- self.current_results.append(suite)
-
- suite.test_results.append(result)
- suite.test_count += 1
-
- if result.passed:
- suite.passed_count += 1
- elif result.status == TestStatus.FAILED:
- suite.failed_count += 1
- elif result.status == TestStatus.SKIPPED:
- suite.skipped_count += 1
- else:
- suite.error_count += 1
-
- suite.duration += result.duration
-
- def generate_reports(self, output_dir: Optional[str] = None) -> Dict[str, str]:
- """生成所有格式的报告"""
- output_dir = output_dir or str(self.settings.get_reports_path())
- output_dir = Path(output_dir)
- output_dir.mkdir(parents=True, exist_ok=True)
-
- generated = {}
-
- for format_name, generator in self.generators.items():
- output_path = output_dir / f"test_report.{format_name}"
- generator.generate(self.current_results, str(output_path))
- generated[format_name] = str(output_path)
-
- return generated
-
- def get_summary(self) -> Dict[str, Any]:
- """获取汇总信息"""
- all_results = []
- for suite in self.current_results:
- all_results.extend(suite.test_results)
-
- total = len(all_results)
- passed = sum(1 for r in all_results if r.passed)
-
- return {
- "total": total,
- "passed": passed,
- "failed": total - passed,
- "pass_rate": round(passed / total * 100, 2) if total > 0 else 0,
- "suites": len(self.current_results),
- "duration": sum(s.duration for s in self.current_results)
- }
-
- def clear_results(self) -> None:
- """清空当前结果"""
- self.current_results.clear()
-
-
-def get_report_manager() -> ReportManager:
- """获取报告管理器"""
- return ReportManager()
diff --git a/e2e/.env.example b/e2e/.env.example
deleted file mode 100644
index 0c13dd2..0000000
--- a/e2e/.env.example
+++ /dev/null
@@ -1,29 +0,0 @@
-# 测试环境配置
-# 可选值: development, staging, production
-TEST_ENV=development
-
-# 基础URL(可选,会覆盖环境配置)
-# BASE_URL=http://localhost:3001
-
-# API URL(可选,会覆盖环境配置)
-# API_URL=http://localhost:3001/api
-
-# 浏览器配置
-HEADLESS=true
-SLOW_MO=0
-
-# 测试配置
-TIMEOUT=120000
-RETRIES=0
-
-# 截图和视频配置
-SCREENSHOT=only-on-failure
-VIDEO=retain-on-failure
-TRACE=retain-on-failure
-
-# CI配置
-CI=false
-
-# 调试配置
-DEBUG=false
-PWDEBUG=false
diff --git a/e2e/MIGRATION.md b/e2e/MIGRATION.md
deleted file mode 100644
index eb17448..0000000
--- a/e2e/MIGRATION.md
+++ /dev/null
@@ -1,123 +0,0 @@
-# 测试框架整合说明
-
-## 背景
-
-项目原本存在三个独立的测试框架:
-1. **e2e/** - Playwright TypeScript测试框架(主要框架)
-2. **e2e-tests/** - Python Playwright测试框架(已废弃)
-3. **test-framework/** - 共享测试框架(已废弃)
-
-## 整合决策
-
-### 保留的测试框架
-- **e2e/** - 作为主要测试框架
- - 完整的测试套件(冒烟、回归、性能、可访问性、安全、视觉、移动端、响应式、API、集成、管理后台等)
- - TypeScript与项目技术栈一致
- - 完善的配置和工具链
- - 丰富的测试用例和Page Object模型
-
-### 废弃的测试框架
-- **e2e-tests/** - Python Playwright测试框架
- - 基础测试套件
- - 与项目技术栈不一致
- - 维护成本高
-
-- **test-framework/** - 共享测试框架
- - 简单的E2E测试
- - 功能重复
- - 缺少维护
-
-## 迁移说明
-
-### 已迁移的内容
-以下测试用例已从废弃框架迁移到e2e/:
-
-#### 从e2e-tests/迁移
-- 基础页面测试(首页、联系页面)
-- 导航测试
-- 性能测试(基础)
-- 响应式测试(基础)
-
-#### 从test-framework/迁移
-- 可访问性测试
-- 性能测试
-- SEO测试
-- 联系页面测试
-
-### 未迁移的内容
-以下内容未迁移,因为e2e/中已有更完善的实现:
-
-#### e2e-tests/中未迁移
-- Python特定的测试工具和辅助函数
-- Python报告生成器
-- Python日志系统
-
-#### test-framework/中未迁移
-- 简单的测试用例(e2e/中已有更完善的版本)
-- 共享的页面对象(已整合到e2e/中)
-
-## 使用指南
-
-### 运行测试
-
-```bash
-# 运行所有E2E测试
-npm run test
-
-# 运行冒烟测试
-npm run test:smoke
-
-# 运行回归测试
-npm run test:tier:standard
-
-# 运行性能测试
-npm run test:performance
-
-# 运行可访问性测试
-cd e2e && npx playwright test --grep @accessibility
-
-# 运行安全测试
-cd e2e && npx playwright test --grep @security
-
-# 运行视觉回归测试
-cd e2e && npx playwright test --grep @visual
-```
-
-### 测试配置
-
-主要配置文件位于e2e/目录:
-- `playwright.config.ts` - 主配置文件
-- `playwright.config.admin.ts` - 管理后台测试配置
-- `playwright.config.tiered.ts` - 分层测试配置
-- `playwright.coverage.config.ts` - 覆盖率测试配置
-
-### 测试报告
-
-测试报告位于e2e/playwright-report/目录:
-```bash
-# 查看测试报告
-cd e2e && npm run test:report
-```
-
-## 废弃框架处理
-
-### e2e-tests/目录
-- **状态**: 已废弃
-- **操作**: 已添加到.gitignore
-- **保留原因**: 保留历史记录,便于参考
-
-### test-framework/目录
-- **状态**: 已废弃
-- **操作**: 已添加到.gitignore
-- **保留原因**: 保留历史记录,便于参考
-
-## 迁移日期
-2026-03-24
-
-## 相关文档
-- [E2E测试文档](../e2e/README.md)
-- [测试策略](../docs/testing-strategy.md)
-- [测试最佳实践](../docs/testing-best-practices.md)
-
-## 问题反馈
-如有测试相关问题,请联系开发团队。
diff --git a/e2e/analyze-results.js b/e2e/analyze-results.js
deleted file mode 100644
index 11f3e10..0000000
--- a/e2e/analyze-results.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const fs = require('fs');
-const data = JSON.parse(fs.readFileSync('./test-results/results.json', 'utf8'));
-
-console.log('=== 测试结果分析 ===\n');
-console.log('总测试数:', data.stats.expected + data.stats.unexpected);
-console.log('通过:', data.stats.expected);
-console.log('失败:', data.stats.unexpected);
-console.log('跳过:', data.stats.skipped);
-console.log('通过率:', ((data.stats.expected / (data.stats.expected + data.stats.unexpected)) * 100).toFixed(2) + '%');
-console.log('执行时间:', (data.stats.duration / 1000 / 60).toFixed(2), '分钟\n');
-
-console.log('=== 失败测试分类 ===\n');
-const errorTypes = {};
-const failures = [];
-
-data.suites.forEach(suite => {
- if (suite.suites) {
- suite.suites.forEach(subSuite => {
- if (subSuite.specs) {
- subSuite.specs.forEach(spec => {
- if (!spec.ok) {
- failures.push({
- title: spec.title,
- file: spec.file,
- line: spec.line
- });
- errorTypes[spec.title] = (errorTypes[spec.title] || 0) + 1;
- }
- });
- }
- });
- }
-});
-
-console.log('失败测试总数:', failures.length);
-console.log('\n主要失败类型:');
-Object.entries(errorTypes)
- .sort((a, b) => b[1] - a[1])
- .slice(0, 15)
- .forEach(([name, count]) => {
- console.log(` ${name}: ${count}`);
- });
-
-console.log('\n=== 按测试套件分类 ===\n');
-const suiteFailures = {};
-data.suites.forEach(suite => {
- if (suite.suites) {
- suite.suites.forEach(subSuite => {
- const suiteName = subSuite.title || 'Unknown';
- if (subSuite.specs) {
- const failed = subSuite.specs.filter(s => !s.ok).length;
- const total = subSuite.specs.length;
- if (failed > 0) {
- suiteFailures[suiteName] = {
- failed,
- total,
- rate: ((total - failed) / total * 100).toFixed(2)
- };
- }
- }
- });
- }
-});
-
-Object.entries(suiteFailures)
- .sort((a, b) => b[1].failed - a[1].failed)
- .slice(0, 10)
- .forEach(([name, stats]) => {
- console.log(`${name}:`);
- console.log(` 失败: ${stats.failed}/${stats.total}`);
- console.log(` 通过率: ${stats.rate}%`);
- });
\ No newline at end of file
diff --git a/e2e/coverage-reporter.js b/e2e/coverage-reporter.js
deleted file mode 100644
index 3c160c6..0000000
--- a/e2e/coverage-reporter.js
+++ /dev/null
@@ -1,81 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-function generateSimpleReport() {
- const coveragePath = path.join(__dirname, 'coverage/e2e/coverage-data.json');
-
- if (!fs.existsSync(coveragePath)) {
- console.error('Coverage data file not found!');
- return;
- }
-
- const coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
- const outputDir = path.join(__dirname, 'coverage/e2e');
-
- if (!fs.existsSync(outputDir)) {
- fs.mkdirSync(outputDir, { recursive: true });
- }
-
- let totalEntries = 0;
- let appEntries = 0;
- const fileStats = {};
-
- console.log('\n=== E2E Coverage Collection Report ===\n');
- console.log('Pages covered:');
-
- const pages = new Set();
- const scripts = new Set();
-
- for (const entry of coverageData) {
- if (!entry.url) continue;
-
- const url = entry.url;
-
- if (url.includes('localhost:3000') || url.includes('_next')) {
- totalEntries++;
-
- const urlObj = new URL(url);
- pages.add(urlObj.pathname);
-
- const scriptUrl = entry.scriptId ? `script-${entry.scriptId}` : 'inline';
- if (!fileStats[scriptUrl]) {
- fileStats[scriptUrl] = { count: 0, sourceSize: 0 };
- }
- fileStats[scriptUrl].count++;
- if (entry.source) {
- fileStats[scriptUrl].sourceSize += entry.source.length;
- }
-
- if (url.includes('novalon-website') || url.includes('/_next/')) {
- appEntries++;
- }
- }
- }
-
- console.log(`\nTotal JS bundles collected: ${totalEntries}`);
- console.log(`App-specific bundles: ${appEntries}`);
- console.log(`Unique pages visited: ${pages.size}`);
- console.log(`\nPages:`);
- pages.forEach(p => console.log(` - ${p}`));
-
- const reportPath = path.join(outputDir, 'coverage-summary.json');
- const report = {
- timestamp: new Date().toISOString(),
- totalEntries,
- appEntries,
- pagesVisited: Array.from(pages),
- fileStats,
- };
-
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
- console.log(`\nReport saved to: ${reportPath}`);
-
- console.log('\n=== Coverage Summary ===');
- console.log(`✓ Playwright successfully collected JS coverage from ${totalEntries} bundles`);
- console.log(`✓ Covered ${pages.size} unique pages`);
- console.log(`\nNote: For Istanbul HTML report, run: npx playwright show-report`);
-
- return report;
-}
-
-generateSimpleReport();
diff --git a/e2e/global-setup.ts b/e2e/global-setup.ts
deleted file mode 100644
index b3db0bc..0000000
--- a/e2e/global-setup.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { chromium, firefox, webkit, FullConfig } from '@playwright/test';
-import { getEnvironment } from './src/config/environments';
-
-const env = getEnvironment();
-
-async function globalSetup(config: FullConfig) {
- const browserName = config.projects?.[0]?.use?.browserName || 'chromium';
- let browser;
-
- try {
- switch (browserName) {
- case 'firefox':
- browser = await firefox.launch();
- break;
- case 'webkit':
- browser = await webkit.launch();
- break;
- default:
- browser = await chromium.launch();
- }
-
- const page = await browser.newPage();
-
- try {
- await page.goto(`${env.baseURL}/admin/login`, { waitUntil: 'commit', timeout: 120000 });
-
- await page.waitForSelector('#email', { timeout: 30000 });
-
- await page.locator('#email').fill('admin@novalon.cn');
- await page.locator('#password').fill('admin123456');
-
- await page.locator('button[type="submit"]').click();
-
- try {
- await page.waitForURL(/\/admin(?!\/login)/, { timeout: 30000 });
- await page.context().storageState({ path: '.auth/admin.json' });
- } catch {
- console.warn('登录失败,跳过需要认证的测试');
- }
- } catch {
- console.warn('Admin登录页面不可用,跳过需要认证的测试');
- } finally {
- await browser.close();
- }
- } catch (error) {
- console.warn(`浏览器启动失败 (${browserName}),跳过需要认证的测试:`, error.message);
- }
-}
-
-export default globalSetup;
diff --git a/e2e/global-teardown.ts b/e2e/global-teardown.ts
deleted file mode 100644
index a591550..0000000
--- a/e2e/global-teardown.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { TestHistoryManager } from './src/utils/test-history';
-
-const historyManager = new TestHistoryManager();
-
-export async function globalTeardown(config: any, result: any) {
- console.log('📊 记录测试执行历史...');
-
- for (const suite of result.suites) {
- for (const spec of suite.suites) {
- for (const test of spec.tests) {
- const testId = `${spec.file}::${test.title}`;
- historyManager.recordExecution(
- testId,
- spec.file,
- test.title,
- test.results[0]?.duration || 0,
- test.results[0]?.status === 'passed'
- );
- }
- }
- }
-
- console.log('✅ 历史记录完成');
-}
\ No newline at end of file
diff --git a/e2e/package-lock.json b/e2e/package-lock.json
deleted file mode 100644
index 4152b46..0000000
--- a/e2e/package-lock.json
+++ /dev/null
@@ -1,7164 +0,0 @@
-{
- "name": "e2e-tests",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "e2e-tests",
- "version": "1.0.0",
- "hasInstallScript": true,
- "dependencies": {
- "@sentry/nextjs": "^10.42.0"
- },
- "devDependencies": {
- "@axe-core/playwright": "^4.11.1",
- "@babel/preset-react": "^7.28.5",
- "@babel/preset-typescript": "^7.28.5",
- "@playwright/test": "^1.58.2",
- "@types/node": "^20.11.0",
- "allure-commandline": "^2.37.0",
- "allure-playwright": "^3.5.0",
- "chrome-launcher": "^1.2.1",
- "glob": "^13.0.6",
- "istanbul-lib-coverage": "^3.2.2",
- "lighthouse": "^13.0.3",
- "typescript": "^5.3.0",
- "v8-to-istanbul": "^9.3.0"
- }
- },
- "node_modules/@axe-core/playwright": {
- "version": "4.11.1",
- "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.1.tgz",
- "integrity": "sha512-mKEfoUIB1MkVTht0BGZFXtSAEKXMJoDkyV5YZ9jbBmZCcWDz71tegNsdTkIN8zc/yMi5Gm2kx7Z5YQ9PfWNAWw==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "axe-core": "~4.11.1"
- },
- "peerDependencies": {
- "playwright-core": ">= 1.0.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
- "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.28.5",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
- "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
- "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-compilation-targets": "^7.28.6",
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helpers": "^7.28.6",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/traverse": "^7.29.0",
- "@babel/types": "^7.29.0",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.29.1",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
- "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-annotate-as-pure": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
- "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.27.3"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
- "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.28.6",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
- "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-member-expression-to-functions": "^7.28.5",
- "@babel/helper-optimise-call-expression": "^7.27.1",
- "@babel/helper-replace-supers": "^7.28.6",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
- "@babel/traverse": "^7.28.6",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
- "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.28.5",
- "@babel/types": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
- "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
- "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-optimise-call-expression": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
- "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
- "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-replace-supers": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
- "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-member-expression-to-functions": "^7.28.5",
- "@babel/helper-optimise-call-expression": "^7.27.1",
- "@babel/traverse": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
- "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
- "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
- "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
- "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
- "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz",
- "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helper-plugin-utils": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-display-name": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz",
- "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
- "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-plugin-utils": "^7.28.6",
- "@babel/plugin-syntax-jsx": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-development": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
- "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/plugin-transform-react-jsx": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-pure-annotations": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz",
- "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-typescript": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
- "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-create-class-features-plugin": "^7.28.6",
- "@babel/helper-plugin-utils": "^7.28.6",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/preset-react": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz",
- "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/helper-validator-option": "^7.27.1",
- "@babel/plugin-transform-react-display-name": "^7.28.0",
- "@babel/plugin-transform-react-jsx": "^7.27.1",
- "@babel/plugin-transform-react-jsx-development": "^7.27.1",
- "@babel/plugin-transform-react-pure-annotations": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/preset-typescript": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
- "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/helper-validator-option": "^7.27.1",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/plugin-transform-modules-commonjs": "^7.27.1",
- "@babel/plugin-transform-typescript": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
- "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
- "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.29.0",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
- "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@fastify/otel": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.16.0.tgz",
- "integrity": "sha512-2304BdM5Q/kUvQC9qJO1KZq3Zn1WWsw+WWkVmFEaj1UE2hEIiuFqrPeglQOwEtw/ftngisqfQ3v70TWMmwhhHA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.208.0",
- "@opentelemetry/semantic-conventions": "^1.28.0",
- "minimatch": "^10.0.3"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.9.0"
- }
- },
- "node_modules/@fastify/otel/node_modules/@opentelemetry/api-logs": {
- "version": "0.208.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz",
- "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api": "^1.3.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@fastify/otel/node_modules/@opentelemetry/core": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz",
- "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@fastify/otel/node_modules/@opentelemetry/instrumentation": {
- "version": "0.208.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz",
- "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api-logs": "0.208.0",
- "import-in-the-middle": "^2.0.0",
- "require-in-the-middle": "^8.0.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@fastify/otel/node_modules/cjs-module-lexer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
- "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
- "license": "MIT"
- },
- "node_modules/@fastify/otel/node_modules/import-in-the-middle": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz",
- "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==",
- "license": "Apache-2.0",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-import-attributes": "^1.9.5",
- "cjs-module-lexer": "^2.2.0",
- "module-details-from-path": "^1.0.4"
- }
- },
- "node_modules/@fastify/otel/node_modules/require-in-the-middle": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz",
- "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "module-details-from-path": "^1.0.3"
- },
- "engines": {
- "node": ">=9.3.0 || >=8.10.0 <9.0.0"
- }
- },
- "node_modules/@formatjs/ecma402-abstract": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
- "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@formatjs/fast-memoize": "2.2.7",
- "@formatjs/intl-localematcher": "0.6.2",
- "decimal.js": "^10.4.3",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/fast-memoize": {
- "version": "2.2.7",
- "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
- "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/icu-messageformat-parser": {
- "version": "2.11.4",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
- "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/icu-skeleton-parser": "1.8.16",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/icu-skeleton-parser": {
- "version": "1.8.16",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
- "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/intl-localematcher": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
- "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@img/colour": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
- "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
- "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
- "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
- "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
- "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
- "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
- "cpu": [
- "arm"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
- "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-ppc64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
- "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
- "cpu": [
- "ppc64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-riscv64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
- "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
- "cpu": [
- "riscv64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
- "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
- "cpu": [
- "s390x"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
- "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
- "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
- "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
- "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
- "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-ppc64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
- "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
- "cpu": [
- "ppc64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-ppc64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-riscv64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
- "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
- "cpu": [
- "riscv64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-riscv64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
- "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
- "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
- "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
- "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
- "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@emnapi/runtime": "^1.7.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
- "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
- "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
- "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/source-map": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
- "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@next/env": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
- "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
- "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
- "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
- "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
- "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
- "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
- "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
- "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
- "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@opentelemetry/api": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
- "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@opentelemetry/api-logs": {
- "version": "0.57.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz",
- "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api": "^1.3.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/context-async-hooks": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz",
- "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@opentelemetry/core": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz",
- "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/semantic-conventions": "1.28.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz",
- "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/instrumentation": {
- "version": "0.57.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz",
- "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api-logs": "0.57.2",
- "@types/shimmer": "^1.2.0",
- "import-in-the-middle": "^1.8.1",
- "require-in-the-middle": "^7.1.1",
- "semver": "^7.5.2",
- "shimmer": "^1.2.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-amqplib": {
- "version": "0.46.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz",
- "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-connect": {
- "version": "0.43.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz",
- "integrity": "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0",
- "@types/connect": "3.4.38"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-dataloader": {
- "version": "0.16.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz",
- "integrity": "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-express": {
- "version": "0.47.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz",
- "integrity": "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-fs": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz",
- "integrity": "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-generic-pool": {
- "version": "0.43.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz",
- "integrity": "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-graphql": {
- "version": "0.47.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz",
- "integrity": "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-hapi": {
- "version": "0.45.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz",
- "integrity": "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-http": {
- "version": "0.57.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz",
- "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "1.30.1",
- "@opentelemetry/instrumentation": "0.57.2",
- "@opentelemetry/semantic-conventions": "1.28.0",
- "forwarded-parse": "2.1.2",
- "semver": "^7.5.2"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz",
- "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/instrumentation-ioredis": {
- "version": "0.47.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz",
- "integrity": "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/redis-common": "^0.36.2",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-kafkajs": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz",
- "integrity": "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-knex": {
- "version": "0.44.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz",
- "integrity": "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-koa": {
- "version": "0.47.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz",
- "integrity": "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-lru-memoizer": {
- "version": "0.44.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz",
- "integrity": "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-mongodb": {
- "version": "0.52.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz",
- "integrity": "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-mongoose": {
- "version": "0.46.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz",
- "integrity": "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-mysql": {
- "version": "0.45.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz",
- "integrity": "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0",
- "@types/mysql": "2.15.26"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-mysql2": {
- "version": "0.45.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz",
- "integrity": "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0",
- "@opentelemetry/sql-common": "^0.40.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-pg": {
- "version": "0.51.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz",
- "integrity": "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.26.0",
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0",
- "@opentelemetry/sql-common": "^0.40.1",
- "@types/pg": "8.6.1",
- "@types/pg-pool": "2.0.6"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis": {
- "version": "0.59.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.59.0.tgz",
- "integrity": "sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/redis-common": "^0.38.2",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis-4": {
- "version": "0.46.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz",
- "integrity": "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/redis-common": "^0.36.2",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis/node_modules/@opentelemetry/api-logs": {
- "version": "0.211.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz",
- "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api": "^1.3.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis/node_modules/@opentelemetry/instrumentation": {
- "version": "0.211.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz",
- "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api-logs": "0.211.0",
- "import-in-the-middle": "^2.0.0",
- "require-in-the-middle": "^8.0.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis/node_modules/@opentelemetry/redis-common": {
- "version": "0.38.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz",
- "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==",
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis/node_modules/cjs-module-lexer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
- "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
- "license": "MIT"
- },
- "node_modules/@opentelemetry/instrumentation-redis/node_modules/import-in-the-middle": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz",
- "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==",
- "license": "Apache-2.0",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-import-attributes": "^1.9.5",
- "cjs-module-lexer": "^2.2.0",
- "module-details-from-path": "^1.0.4"
- }
- },
- "node_modules/@opentelemetry/instrumentation-redis/node_modules/require-in-the-middle": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz",
- "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "module-details-from-path": "^1.0.3"
- },
- "engines": {
- "node": ">=9.3.0 || >=8.10.0 <9.0.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-tedious": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz",
- "integrity": "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.57.1",
- "@opentelemetry/semantic-conventions": "^1.27.0",
- "@types/tedious": "^4.0.14"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@opentelemetry/instrumentation-undici": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz",
- "integrity": "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.8.0",
- "@opentelemetry/instrumentation": "^0.57.1"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.7.0"
- }
- },
- "node_modules/@opentelemetry/redis-common": {
- "version": "0.36.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz",
- "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/resources": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz",
- "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "1.30.1",
- "@opentelemetry/semantic-conventions": "1.28.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz",
- "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/sdk-trace-base": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz",
- "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "1.30.1",
- "@opentelemetry/resources": "1.30.1",
- "@opentelemetry/semantic-conventions": "1.28.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz",
- "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/semantic-conventions": {
- "version": "1.40.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz",
- "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@opentelemetry/sql-common": {
- "version": "0.40.1",
- "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz",
- "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^1.1.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0"
- }
- },
- "node_modules/@paulirish/trace_engine": {
- "version": "0.0.61",
- "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.61.tgz",
- "integrity": "sha512-/O08DwmUqIlJjUSPSZbNF8lWnlxaMsIOV6sS+uDKCxBd5i1psAmjEoG3JAqR6+nHD8X+YY474NW7SxUH/K+/kQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "legacy-javascript": "latest",
- "third-party-web": "latest"
- }
- },
- "node_modules/@playwright/test": {
- "version": "1.58.2",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
- "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
- "devOptional": true,
- "license": "Apache-2.0",
- "dependencies": {
- "playwright": "1.58.2"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@prisma/instrumentation": {
- "version": "6.11.1",
- "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.11.1.tgz",
- "integrity": "sha512-mrZOev24EDhnefmnZX7WVVT7v+r9LttPRqf54ONvj6re4XMF7wFTpK2tLJi4XHB7fFp/6xhYbgRel8YV7gQiyA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.8"
- }
- },
- "node_modules/@puppeteer/browsers": {
- "version": "2.13.0",
- "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz",
- "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "debug": "^4.4.3",
- "extract-zip": "^2.0.1",
- "progress": "^2.0.3",
- "proxy-agent": "^6.5.0",
- "semver": "^7.7.4",
- "tar-fs": "^3.1.1",
- "yargs": "^17.7.2"
- },
- "bin": {
- "browsers": "lib/cjs/main-cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@rollup/plugin-commonjs": {
- "version": "28.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
- "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==",
- "license": "MIT",
- "dependencies": {
- "@rollup/pluginutils": "^5.0.1",
- "commondir": "^1.0.1",
- "estree-walker": "^2.0.2",
- "fdir": "^6.2.0",
- "is-reference": "1.2.1",
- "magic-string": "^0.30.3",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=16.0.0 || 14 >= 14.17"
- },
- "peerDependencies": {
- "rollup": "^2.68.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/pluginutils": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
- "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^2.0.2",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
- "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
- "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
- "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
- "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
- "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
- "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
- "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
- "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
- "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
- "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
- "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
- "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
- "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
- "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
- "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
- "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
- "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
- "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
- "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
- "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
- "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
- "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
- "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sentry-internal/browser-utils": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.42.0.tgz",
- "integrity": "sha512-HCEICKvepxN4/6NYfnMMMlppcSwIEwtS66X6d1/mwaHdi2ivw0uGl52p7Nfhda/lIJArbrkWprxl0WcjZajhQA==",
- "license": "MIT",
- "dependencies": {
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/browser-utils/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/feedback": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.42.0.tgz",
- "integrity": "sha512-lpPcHsog10MVYFTWE0Pf8vQRqQWwZHJpkVl2FEb9/HDdHFyTBUhCVoWo1KyKaG7GJl9AVKMAg7bp9SSNArhFNQ==",
- "license": "MIT",
- "dependencies": {
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/feedback/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/replay": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.42.0.tgz",
- "integrity": "sha512-Zh3EoaH39x2lqVY1YyVB2vJEyCIrT+YLUQxYl1yvP0MJgLxaR6akVjkgxbSUJahan4cX5DxpZiEHfzdlWnYPyQ==",
- "license": "MIT",
- "dependencies": {
- "@sentry-internal/browser-utils": "10.42.0",
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/replay-canvas": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.42.0.tgz",
- "integrity": "sha512-am3m1Fj8ihoPfoYo41Qq4KeCAAICn4bySso8Oepu9dMNe9Lcnsf+reMRS2qxTPg3pZDc4JEMOcLyNCcgnAfrHw==",
- "license": "MIT",
- "dependencies": {
- "@sentry-internal/replay": "10.42.0",
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry-internal/replay/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/babel-plugin-component-annotate": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.1.1.tgz",
- "integrity": "sha512-x2wEpBHwsTyTF2rWsLKJlzrRF1TTIGOfX+ngdE+Yd5DBkoS58HwQv824QOviPGQRla4/ypISqAXzjdDPL/zalg==",
- "license": "MIT",
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/@sentry/browser": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.42.0.tgz",
- "integrity": "sha512-iXxYjXNEBwY1MH4lDSDZZUNjzPJDK7/YLwVIJq/3iBYpIQVIhaJsoJnf3clx9+NfJ8QFKyKfcvgae61zm+hgTA==",
- "license": "MIT",
- "dependencies": {
- "@sentry-internal/browser-utils": "10.42.0",
- "@sentry-internal/feedback": "10.42.0",
- "@sentry-internal/replay": "10.42.0",
- "@sentry-internal/replay-canvas": "10.42.0",
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/browser/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/bundler-plugin-core": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-5.1.1.tgz",
- "integrity": "sha512-F+itpwR9DyQR7gEkrXd2tigREPTvtF5lC8qu6e4anxXYRTui1+dVR0fXNwjpyAZMhIesLfXRN7WY7ggdj7hi0Q==",
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.18.5",
- "@sentry/babel-plugin-component-annotate": "5.1.1",
- "@sentry/cli": "^2.58.5",
- "dotenv": "^16.3.1",
- "find-up": "^5.0.0",
- "glob": "^13.0.6",
- "magic-string": "~0.30.8"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/@sentry/cli": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.5.tgz",
- "integrity": "sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg==",
- "hasInstallScript": true,
- "license": "FSL-1.1-MIT",
- "dependencies": {
- "https-proxy-agent": "^5.0.0",
- "node-fetch": "^2.6.7",
- "progress": "^2.0.3",
- "proxy-from-env": "^1.1.0",
- "which": "^2.0.2"
- },
- "bin": {
- "sentry-cli": "bin/sentry-cli"
- },
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@sentry/cli-darwin": "2.58.5",
- "@sentry/cli-linux-arm": "2.58.5",
- "@sentry/cli-linux-arm64": "2.58.5",
- "@sentry/cli-linux-i686": "2.58.5",
- "@sentry/cli-linux-x64": "2.58.5",
- "@sentry/cli-win32-arm64": "2.58.5",
- "@sentry/cli-win32-i686": "2.58.5",
- "@sentry/cli-win32-x64": "2.58.5"
- }
- },
- "node_modules/@sentry/cli-darwin": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.5.tgz",
- "integrity": "sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ==",
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-linux-arm": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.5.tgz",
- "integrity": "sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw==",
- "cpu": [
- "arm"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "linux",
- "freebsd",
- "android"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-linux-arm64": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.5.tgz",
- "integrity": "sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ==",
- "cpu": [
- "arm64"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "linux",
- "freebsd",
- "android"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-linux-i686": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.5.tgz",
- "integrity": "sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw==",
- "cpu": [
- "x86",
- "ia32"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "linux",
- "freebsd",
- "android"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-linux-x64": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.5.tgz",
- "integrity": "sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g==",
- "cpu": [
- "x64"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "linux",
- "freebsd",
- "android"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-win32-arm64": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.5.tgz",
- "integrity": "sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA==",
- "cpu": [
- "arm64"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-win32-i686": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.5.tgz",
- "integrity": "sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g==",
- "cpu": [
- "x86",
- "ia32"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli-win32-x64": {
- "version": "2.58.5",
- "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.5.tgz",
- "integrity": "sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg==",
- "cpu": [
- "x64"
- ],
- "license": "FSL-1.1-MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@sentry/cli/node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/@sentry/cli/node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/@sentry/core": {
- "version": "9.47.1",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.47.1.tgz",
- "integrity": "sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/nextjs": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-10.42.0.tgz",
- "integrity": "sha512-4YcVwicZLQWCNXMRSmtg0q68cqhttwhUqcvTe0aYg4YkQIDQKzVOYVU7/js9kSK1PFe9gFdaUxgboBYBp2evDg==",
- "license": "MIT",
- "dependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/semantic-conventions": "^1.37.0",
- "@rollup/plugin-commonjs": "28.0.1",
- "@sentry-internal/browser-utils": "10.42.0",
- "@sentry/bundler-plugin-core": "^5.1.0",
- "@sentry/core": "10.42.0",
- "@sentry/node": "10.42.0",
- "@sentry/opentelemetry": "10.42.0",
- "@sentry/react": "10.42.0",
- "@sentry/vercel-edge": "10.42.0",
- "@sentry/webpack-plugin": "^5.1.0",
- "rollup": "^4.35.0",
- "stacktrace-parser": "^0.1.10"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "next": "^13.2.0 || ^14.0 || ^15.0.0-rc.0 || ^16.0.0-0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/api-logs": {
- "version": "0.211.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz",
- "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api": "^1.3.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/context-async-hooks": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz",
- "integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==",
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/core": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz",
- "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation": {
- "version": "0.211.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz",
- "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api-logs": "0.211.0",
- "import-in-the-middle": "^2.0.0",
- "require-in-the-middle": "^8.0.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-amqplib": {
- "version": "0.58.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.58.0.tgz",
- "integrity": "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-connect": {
- "version": "0.54.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.54.0.tgz",
- "integrity": "sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.27.0",
- "@types/connect": "3.4.38"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-dataloader": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.28.0.tgz",
- "integrity": "sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-express": {
- "version": "0.59.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.59.0.tgz",
- "integrity": "sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-fs": {
- "version": "0.30.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.30.0.tgz",
- "integrity": "sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-generic-pool": {
- "version": "0.54.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.54.0.tgz",
- "integrity": "sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-graphql": {
- "version": "0.58.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.58.0.tgz",
- "integrity": "sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-hapi": {
- "version": "0.57.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.57.0.tgz",
- "integrity": "sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.27.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-http": {
- "version": "0.211.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.211.0.tgz",
- "integrity": "sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "2.5.0",
- "@opentelemetry/instrumentation": "0.211.0",
- "@opentelemetry/semantic-conventions": "^1.29.0",
- "forwarded-parse": "2.1.2"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
- "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-ioredis": {
- "version": "0.59.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.59.0.tgz",
- "integrity": "sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/redis-common": "^0.38.2",
- "@opentelemetry/semantic-conventions": "^1.33.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-kafkajs": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.20.0.tgz",
- "integrity": "sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.30.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-knex": {
- "version": "0.55.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.55.0.tgz",
- "integrity": "sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.1"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-koa": {
- "version": "0.59.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.59.0.tgz",
- "integrity": "sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.36.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.9.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-lru-memoizer": {
- "version": "0.55.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.55.0.tgz",
- "integrity": "sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-mongodb": {
- "version": "0.64.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.64.0.tgz",
- "integrity": "sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-mongoose": {
- "version": "0.57.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.57.0.tgz",
- "integrity": "sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-mysql": {
- "version": "0.57.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.57.0.tgz",
- "integrity": "sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.0",
- "@types/mysql": "2.15.27"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-mysql2": {
- "version": "0.57.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.57.0.tgz",
- "integrity": "sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.0",
- "@opentelemetry/sql-common": "^0.41.2"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-pg": {
- "version": "0.63.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.63.0.tgz",
- "integrity": "sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.34.0",
- "@opentelemetry/sql-common": "^0.41.2",
- "@types/pg": "8.15.6",
- "@types/pg-pool": "2.0.7"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-tedious": {
- "version": "0.30.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.30.0.tgz",
- "integrity": "sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.33.0",
- "@types/tedious": "^4.0.14"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/instrumentation-undici": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.21.0.tgz",
- "integrity": "sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/semantic-conventions": "^1.24.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.7.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/redis-common": {
- "version": "0.38.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz",
- "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==",
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/resources": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz",
- "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "2.6.0",
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.3.0 <1.10.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/sdk-trace-base": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz",
- "integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "2.6.0",
- "@opentelemetry/resources": "2.6.0",
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.3.0 <1.10.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@opentelemetry/sql-common": {
- "version": "0.41.2",
- "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz",
- "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "^2.0.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@prisma/instrumentation": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.2.0.tgz",
- "integrity": "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/instrumentation": "^0.207.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.8"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": {
- "version": "0.207.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz",
- "integrity": "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api": "^1.3.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": {
- "version": "0.207.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz",
- "integrity": "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/api-logs": "0.207.0",
- "import-in-the-middle": "^2.0.0",
- "require-in-the-middle": "^8.0.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.3.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@sentry/node": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.42.0.tgz",
- "integrity": "sha512-ZZfU3Fnni7Aj0lTX4e3QpY3UxK4FGuzfM20316UAJycBGnripm+sDHwcekPMGfLnk/FrN9wa1atspVlHvOI0WQ==",
- "license": "MIT",
- "dependencies": {
- "@fastify/otel": "0.16.0",
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^2.5.1",
- "@opentelemetry/core": "^2.5.1",
- "@opentelemetry/instrumentation": "^0.211.0",
- "@opentelemetry/instrumentation-amqplib": "0.58.0",
- "@opentelemetry/instrumentation-connect": "0.54.0",
- "@opentelemetry/instrumentation-dataloader": "0.28.0",
- "@opentelemetry/instrumentation-express": "0.59.0",
- "@opentelemetry/instrumentation-fs": "0.30.0",
- "@opentelemetry/instrumentation-generic-pool": "0.54.0",
- "@opentelemetry/instrumentation-graphql": "0.58.0",
- "@opentelemetry/instrumentation-hapi": "0.57.0",
- "@opentelemetry/instrumentation-http": "0.211.0",
- "@opentelemetry/instrumentation-ioredis": "0.59.0",
- "@opentelemetry/instrumentation-kafkajs": "0.20.0",
- "@opentelemetry/instrumentation-knex": "0.55.0",
- "@opentelemetry/instrumentation-koa": "0.59.0",
- "@opentelemetry/instrumentation-lru-memoizer": "0.55.0",
- "@opentelemetry/instrumentation-mongodb": "0.64.0",
- "@opentelemetry/instrumentation-mongoose": "0.57.0",
- "@opentelemetry/instrumentation-mysql": "0.57.0",
- "@opentelemetry/instrumentation-mysql2": "0.57.0",
- "@opentelemetry/instrumentation-pg": "0.63.0",
- "@opentelemetry/instrumentation-redis": "0.59.0",
- "@opentelemetry/instrumentation-tedious": "0.30.0",
- "@opentelemetry/instrumentation-undici": "0.21.0",
- "@opentelemetry/resources": "^2.5.1",
- "@opentelemetry/sdk-trace-base": "^2.5.1",
- "@opentelemetry/semantic-conventions": "^1.39.0",
- "@prisma/instrumentation": "7.2.0",
- "@sentry/core": "10.42.0",
- "@sentry/node-core": "10.42.0",
- "@sentry/opentelemetry": "10.42.0",
- "import-in-the-middle": "^2.0.6"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@sentry/node-core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.42.0.tgz",
- "integrity": "sha512-9tf3fPV6M071aps72D+PEtdQPTuj+SuqO2+PpTfdPP5ZL4TTKYo3VK0li76SL+5wGdTFGV5qmsokHq9IRBA0iA==",
- "license": "MIT",
- "dependencies": {
- "@sentry/core": "10.42.0",
- "@sentry/opentelemetry": "10.42.0",
- "import-in-the-middle": "^2.0.6"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0",
- "@opentelemetry/core": "^1.30.1 || ^2.1.0",
- "@opentelemetry/instrumentation": ">=0.57.1 <1",
- "@opentelemetry/resources": "^1.30.1 || ^2.1.0",
- "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0",
- "@opentelemetry/semantic-conventions": "^1.39.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@opentelemetry/context-async-hooks": {
- "optional": true
- },
- "@opentelemetry/core": {
- "optional": true
- },
- "@opentelemetry/instrumentation": {
- "optional": true
- },
- "@opentelemetry/resources": {
- "optional": true
- },
- "@opentelemetry/sdk-trace-base": {
- "optional": true
- },
- "@opentelemetry/semantic-conventions": {
- "optional": true
- }
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@sentry/opentelemetry": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.42.0.tgz",
- "integrity": "sha512-5vsYz683iihzlIj3sT1+tEixf0awwXK86a+aYsnMHrTXJDrkBDq4U0ZT+yxdPfJlkaxRtYycFR08SXr2pSm7Eg==",
- "license": "MIT",
- "dependencies": {
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0",
- "@opentelemetry/core": "^1.30.1 || ^2.1.0",
- "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0",
- "@opentelemetry/semantic-conventions": "^1.39.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@types/mysql": {
- "version": "2.15.27",
- "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz",
- "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@types/pg": {
- "version": "8.15.6",
- "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz",
- "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "pg-protocol": "*",
- "pg-types": "^2.2.0"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/@types/pg-pool": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz",
- "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==",
- "license": "MIT",
- "dependencies": {
- "@types/pg": "*"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/cjs-module-lexer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
- "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
- "license": "MIT"
- },
- "node_modules/@sentry/nextjs/node_modules/import-in-the-middle": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz",
- "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==",
- "license": "Apache-2.0",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-import-attributes": "^1.9.5",
- "cjs-module-lexer": "^2.2.0",
- "module-details-from-path": "^1.0.4"
- }
- },
- "node_modules/@sentry/nextjs/node_modules/require-in-the-middle": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz",
- "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "module-details-from-path": "^1.0.3"
- },
- "engines": {
- "node": ">=9.3.0 || >=8.10.0 <9.0.0"
- }
- },
- "node_modules/@sentry/node": {
- "version": "9.47.1",
- "resolved": "https://registry.npmjs.org/@sentry/node/-/node-9.47.1.tgz",
- "integrity": "sha512-CDbkasBz3fnWRKSFs6mmaRepM2pa+tbZkrqhPWifFfIkJDidtVW40p6OnquTvPXyPAszCnDZRnZT14xyvNmKPQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/instrumentation": "^0.57.2",
- "@opentelemetry/instrumentation-amqplib": "^0.46.1",
- "@opentelemetry/instrumentation-connect": "0.43.1",
- "@opentelemetry/instrumentation-dataloader": "0.16.1",
- "@opentelemetry/instrumentation-express": "0.47.1",
- "@opentelemetry/instrumentation-fs": "0.19.1",
- "@opentelemetry/instrumentation-generic-pool": "0.43.1",
- "@opentelemetry/instrumentation-graphql": "0.47.1",
- "@opentelemetry/instrumentation-hapi": "0.45.2",
- "@opentelemetry/instrumentation-http": "0.57.2",
- "@opentelemetry/instrumentation-ioredis": "0.47.1",
- "@opentelemetry/instrumentation-kafkajs": "0.7.1",
- "@opentelemetry/instrumentation-knex": "0.44.1",
- "@opentelemetry/instrumentation-koa": "0.47.1",
- "@opentelemetry/instrumentation-lru-memoizer": "0.44.1",
- "@opentelemetry/instrumentation-mongodb": "0.52.0",
- "@opentelemetry/instrumentation-mongoose": "0.46.1",
- "@opentelemetry/instrumentation-mysql": "0.45.1",
- "@opentelemetry/instrumentation-mysql2": "0.45.2",
- "@opentelemetry/instrumentation-pg": "0.51.1",
- "@opentelemetry/instrumentation-redis-4": "0.46.1",
- "@opentelemetry/instrumentation-tedious": "0.18.1",
- "@opentelemetry/instrumentation-undici": "0.10.1",
- "@opentelemetry/resources": "^1.30.1",
- "@opentelemetry/sdk-trace-base": "^1.30.1",
- "@opentelemetry/semantic-conventions": "^1.34.0",
- "@prisma/instrumentation": "6.11.1",
- "@sentry/core": "9.47.1",
- "@sentry/node-core": "9.47.1",
- "@sentry/opentelemetry": "9.47.1",
- "import-in-the-middle": "^1.14.2",
- "minimatch": "^9.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/node-core": {
- "version": "9.47.1",
- "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-9.47.1.tgz",
- "integrity": "sha512-7TEOiCGkyShJ8CKtsri9lbgMCbB+qNts2Xq37itiMPN2m+lIukK3OX//L8DC5nfKYZlgikrefS63/vJtm669hQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@sentry/core": "9.47.1",
- "@sentry/opentelemetry": "9.47.1",
- "import-in-the-middle": "^1.14.2"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0",
- "@opentelemetry/core": "^1.30.1 || ^2.0.0",
- "@opentelemetry/instrumentation": ">=0.57.1 <1",
- "@opentelemetry/resources": "^1.30.1 || ^2.0.0",
- "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0",
- "@opentelemetry/semantic-conventions": "^1.34.0"
- }
- },
- "node_modules/@sentry/node/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@sentry/node/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@sentry/node/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@sentry/opentelemetry": {
- "version": "9.47.1",
- "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.47.1.tgz",
- "integrity": "sha512-STtFpjF7lwzeoedDJV+5XA6P89BfmFwFftmHSGSe3UTI8z8IoiR5yB6X2vCjSPvXlfeOs13qCNNCEZyznxM8Xw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@sentry/core": "9.47.1"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0",
- "@opentelemetry/core": "^1.30.1 || ^2.0.0",
- "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0",
- "@opentelemetry/semantic-conventions": "^1.34.0"
- }
- },
- "node_modules/@sentry/react": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.42.0.tgz",
- "integrity": "sha512-uigyz6E3yPjjqIZpkGzRChww6gzMmqdCpK30M5aBYoaen29DDmSECHYA16sfgXeSwzQhnXyX7GxgOB+eKIr9dw==",
- "license": "MIT",
- "dependencies": {
- "@sentry/browser": "10.42.0",
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "react": "^16.14.0 || 17.x || 18.x || 19.x"
- }
- },
- "node_modules/@sentry/react/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/vercel-edge": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-10.42.0.tgz",
- "integrity": "sha512-BjK5P5qBBC1biAErKlDICiXaer7FnqAL7NcBCD0pHK7aLO5IAzyegfA0zcu4fIo8TIqipLJiCOGmkYaiSALq8g==",
- "license": "MIT",
- "dependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/resources": "^2.5.1",
- "@sentry/core": "10.42.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/vercel-edge/node_modules/@opentelemetry/core": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz",
- "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.0.0 <1.10.0"
- }
- },
- "node_modules/@sentry/vercel-edge/node_modules/@opentelemetry/resources": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz",
- "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@opentelemetry/core": "2.6.0",
- "@opentelemetry/semantic-conventions": "^1.29.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.6.0"
- },
- "peerDependencies": {
- "@opentelemetry/api": ">=1.3.0 <1.10.0"
- }
- },
- "node_modules/@sentry/vercel-edge/node_modules/@sentry/core": {
- "version": "10.42.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz",
- "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@sentry/webpack-plugin": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-5.1.1.tgz",
- "integrity": "sha512-XgQg+t2aVrlQDfIiAEizqR/bsy6GtBygwgR+Kw11P/cYczj4W9PZ2IYqQEStBzHqnRTh5DbpyMcUNW2CujdA9A==",
- "license": "MIT",
- "dependencies": {
- "@sentry/bundler-plugin-core": "5.1.1",
- "uuid": "^9.0.0"
- },
- "engines": {
- "node": ">= 18"
- },
- "peerDependencies": {
- "webpack": ">=5.0.0"
- }
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@tootallnate/quickjs-emscripten": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
- "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/eslint": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
- "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/estree": "*",
- "@types/json-schema": "*"
- }
- },
- "node_modules/@types/eslint-scope": {
- "version": "3.7.7",
- "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
- "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/eslint": "*",
- "@types/estree": "*"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "license": "MIT"
- },
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@types/mysql": {
- "version": "2.15.26",
- "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz",
- "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/node": {
- "version": "20.19.34",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz",
- "integrity": "sha512-by3/Z0Qp+L9cAySEsSNNwZ6WWw8ywgGLPQGgbQDhNRSitqYgkgp4pErd23ZSCavbtUA2CN4jQtoB3T8nk4j3Rg==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@types/pg": {
- "version": "8.6.1",
- "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz",
- "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "pg-protocol": "*",
- "pg-types": "^2.2.0"
- }
- },
- "node_modules/@types/pg-pool": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz",
- "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/pg": "*"
- }
- },
- "node_modules/@types/shimmer": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz",
- "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/tedious": {
- "version": "4.0.14",
- "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz",
- "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/yauzl": {
- "version": "2.10.3",
- "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
- "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@webassemblyjs/ast": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
- "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/helper-numbers": "1.13.2",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
- }
- },
- "node_modules/@webassemblyjs/floating-point-hex-parser": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
- "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@webassemblyjs/helper-api-error": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
- "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@webassemblyjs/helper-buffer": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
- "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@webassemblyjs/helper-numbers": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
- "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/floating-point-hex-parser": "1.13.2",
- "@webassemblyjs/helper-api-error": "1.13.2",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/helper-wasm-bytecode": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
- "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@webassemblyjs/helper-wasm-section": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
- "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/wasm-gen": "1.14.1"
- }
- },
- "node_modules/@webassemblyjs/ieee754": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
- "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@xtuc/ieee754": "^1.2.0"
- }
- },
- "node_modules/@webassemblyjs/leb128": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
- "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/utf8": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
- "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/@webassemblyjs/wasm-edit": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
- "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/helper-wasm-section": "1.14.1",
- "@webassemblyjs/wasm-gen": "1.14.1",
- "@webassemblyjs/wasm-opt": "1.14.1",
- "@webassemblyjs/wasm-parser": "1.14.1",
- "@webassemblyjs/wast-printer": "1.14.1"
- }
- },
- "node_modules/@webassemblyjs/wasm-gen": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
- "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/ieee754": "1.13.2",
- "@webassemblyjs/leb128": "1.13.2",
- "@webassemblyjs/utf8": "1.13.2"
- }
- },
- "node_modules/@webassemblyjs/wasm-opt": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
- "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/wasm-gen": "1.14.1",
- "@webassemblyjs/wasm-parser": "1.14.1"
- }
- },
- "node_modules/@webassemblyjs/wasm-parser": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
- "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-api-error": "1.13.2",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/ieee754": "1.13.2",
- "@webassemblyjs/leb128": "1.13.2",
- "@webassemblyjs/utf8": "1.13.2"
- }
- },
- "node_modules/@webassemblyjs/wast-printer": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
- "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@xtuc/ieee754": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
- "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
- "license": "BSD-3-Clause",
- "peer": true
- },
- "node_modules/@xtuc/long": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
- "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
- "license": "Apache-2.0",
- "peer": true
- },
- "node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-import-attributes": {
- "version": "1.9.5",
- "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
- "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^8"
- }
- },
- "node_modules/acorn-import-phases": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
- "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10.13.0"
- },
- "peerDependencies": {
- "acorn": "^8.14.0"
- }
- },
- "node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/ajv": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
- "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-formats": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
- "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/allure-commandline": {
- "version": "2.37.0",
- "resolved": "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.37.0.tgz",
- "integrity": "sha512-s3zZ8zjqo2U3i5Lb3iLOCjwWQCtGK58GVpScTnZddOpgTXBDXAbXn+pT7QXN4NiY7pho6xw+UgyREyCRnx/9ug==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "allure": "bin/allure"
- }
- },
- "node_modules/allure-js-commons": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-3.5.0.tgz",
- "integrity": "sha512-iBVFNQkX5i48QGlb5U3iWm+NiNOl/ucxv6dvEJBNeJTPMI8t0Dn0CuXMQEiv4forSSAppD7FB9uGal2JwunH/A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "md5": "^2.3.0"
- },
- "peerDependencies": {
- "allure-playwright": "3.5.0"
- },
- "peerDependenciesMeta": {
- "allure-playwright": {
- "optional": true
- }
- }
- },
- "node_modules/allure-playwright": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-3.5.0.tgz",
- "integrity": "sha512-nB6Wj1z7oGz44r4qxN2lJ6lgDQ+FcpL2dyhUsH/syyNPY8x1JLandedc3FA+nqtxoer6qUagsWZfDZnsDO0RXA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "allure-js-commons": "3.5.0"
- },
- "peerDependencies": {
- "@playwright/test": ">=1.53.0"
- }
- },
- "node_modules/ansi-colors": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
- "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/ast-types": {
- "version": "0.13.4",
- "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
- "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/atomically": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz",
- "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "stubborn-fs": "^2.0.0",
- "when-exit": "^2.1.4"
- }
- },
- "node_modules/axe-core": {
- "version": "4.11.1",
- "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz",
- "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==",
- "dev": true,
- "license": "MPL-2.0",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/b4a": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
- "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
- "dev": true,
- "license": "Apache-2.0",
- "peerDependencies": {
- "react-native-b4a": "*"
- },
- "peerDependenciesMeta": {
- "react-native-b4a": {
- "optional": true
- }
- }
- },
- "node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/bare-events": {
- "version": "2.8.2",
- "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
- "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
- "dev": true,
- "license": "Apache-2.0",
- "peerDependencies": {
- "bare-abort-controller": "*"
- },
- "peerDependenciesMeta": {
- "bare-abort-controller": {
- "optional": true
- }
- }
- },
- "node_modules/bare-fs": {
- "version": "4.5.5",
- "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz",
- "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bare-events": "^2.5.4",
- "bare-path": "^3.0.0",
- "bare-stream": "^2.6.4",
- "bare-url": "^2.2.2",
- "fast-fifo": "^1.3.2"
- },
- "engines": {
- "bare": ">=1.16.0"
- },
- "peerDependencies": {
- "bare-buffer": "*"
- },
- "peerDependenciesMeta": {
- "bare-buffer": {
- "optional": true
- }
- }
- },
- "node_modules/bare-os": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.7.1.tgz",
- "integrity": "sha512-ebvMaS5BgZKmJlvuWh14dg9rbUI84QeV3WlWn6Ph6lFI8jJoh7ADtVTyD2c93euwbe+zgi0DVrl4YmqXeM9aIA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "bare": ">=1.14.0"
- }
- },
- "node_modules/bare-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
- "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bare-os": "^3.0.1"
- }
- },
- "node_modules/bare-stream": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz",
- "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "streamx": "^2.21.0",
- "teex": "^1.0.1"
- },
- "peerDependencies": {
- "bare-buffer": "*",
- "bare-events": "*"
- },
- "peerDependenciesMeta": {
- "bare-buffer": {
- "optional": true
- },
- "bare-events": {
- "optional": true
- }
- }
- },
- "node_modules/bare-url": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
- "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bare-path": "^3.0.0"
- }
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
- "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.cjs"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/basic-ftp": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
- "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.9.0",
- "caniuse-lite": "^1.0.30001759",
- "electron-to-chromium": "^1.5.263",
- "node-releases": "^2.0.27",
- "update-browserslist-db": "^1.2.0"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001777",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
- "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/charenc": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
- "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/chrome-launcher": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.1.tgz",
- "integrity": "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/node": "*",
- "escape-string-regexp": "^4.0.0",
- "is-wsl": "^2.2.0",
- "lighthouse-logger": "^2.0.1"
- },
- "bin": {
- "print-chrome-path": "bin/print-chrome-path.cjs"
- },
- "engines": {
- "node": ">=12.13.0"
- }
- },
- "node_modules/chrome-trace-event": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
- "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.0"
- }
- },
- "node_modules/chromium-bidi": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
- "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "mitt": "^3.0.1",
- "zod": "^3.24.1"
- },
- "peerDependencies": {
- "devtools-protocol": "*"
- }
- },
- "node_modules/cjs-module-lexer": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
- "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "license": "MIT"
- },
- "node_modules/configstore": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz",
- "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "atomically": "^2.0.3",
- "dot-prop": "^9.0.0",
- "graceful-fs": "^4.2.11",
- "xdg-basedir": "^5.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "license": "MIT"
- },
- "node_modules/crypt": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
- "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/csp_evaluator": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/csp_evaluator/-/csp_evaluator-1.1.5.tgz",
- "integrity": "sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/data-uri-to-buffer": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
- "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/define-lazy-prop": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
- "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/degenerator": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
- "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ast-types": "^0.13.4",
- "escodegen": "^2.1.0",
- "esprima": "^4.0.1"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/devtools-protocol": {
- "version": "0.0.1527314",
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1527314.tgz",
- "integrity": "sha512-UohCFOlzpPPD/IcsxM0k4lVZp/GfhPVJ6l2No5XX+LknpGisPWJe17oOHQhZTHf6ThUFIMwHO6bSEZUq/6oP7w==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/dot-prop": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
- "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "type-fest": "^4.18.2"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/dotenv": {
- "version": "16.6.1",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
- "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://dotenvx.com"
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.307",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
- "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
- "license": "ISC"
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
- "node_modules/enhanced-resolve": {
- "version": "5.20.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
- "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.3.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/enquirer": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
- "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-colors": "^4.1.1",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/es-module-lexer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
- "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/escodegen": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
- "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esprima": "^4.0.1",
- "estraverse": "^5.2.0",
- "esutils": "^2.0.2"
- },
- "bin": {
- "escodegen": "bin/escodegen.js",
- "esgenerate": "bin/esgenerate.js"
- },
- "engines": {
- "node": ">=6.0"
- },
- "optionalDependencies": {
- "source-map": "~0.6.1"
- }
- },
- "node_modules/eslint-scope": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
- "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^4.1.1"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/eslint-scope/node_modules/estraverse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
- "license": "BSD-2-Clause",
- "peer": true,
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "license": "MIT"
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/events": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
- "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=0.8.x"
- }
- },
- "node_modules/events-universal": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
- "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bare-events": "^2.7.0"
- }
- },
- "node_modules/extract-zip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
- "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "debug": "^4.1.1",
- "get-stream": "^5.1.0",
- "yauzl": "^2.10.0"
- },
- "bin": {
- "extract-zip": "cli.js"
- },
- "engines": {
- "node": ">= 10.17.0"
- },
- "optionalDependencies": {
- "@types/yauzl": "^2.9.1"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/fast-fifo": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
- "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
- "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause",
- "peer": true
- },
- "node_modules/fd-slicer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pend": "~1.2.0"
- }
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/forwarded-parse": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz",
- "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==",
- "license": "MIT"
- },
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-stream": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
- "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pump": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/get-uri": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
- "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "basic-ftp": "^5.0.2",
- "data-uri-to-buffer": "^6.0.2",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/glob": {
- "version": "13.0.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
- "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minimatch": "^10.2.2",
- "minipass": "^7.1.3",
- "path-scurry": "^2.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-to-regexp": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "license": "BSD-2-Clause",
- "peer": true
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "license": "ISC"
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/http-link-header": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.3.tgz",
- "integrity": "sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/http-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/image-ssim": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/image-ssim/-/image-ssim-0.2.0.tgz",
- "integrity": "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/import-in-the-middle": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz",
- "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "acorn": "^8.14.0",
- "acorn-import-attributes": "^1.9.5",
- "cjs-module-lexer": "^1.2.2",
- "module-details-from-path": "^1.0.3"
- }
- },
- "node_modules/intl-messageformat": {
- "version": "10.7.18",
- "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
- "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/fast-memoize": "2.2.7",
- "@formatjs/icu-messageformat-parser": "2.11.4",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/ip-address": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
- "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-docker": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-reference": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
- "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "*"
- }
- },
- "node_modules/is-wsl": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
- "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-docker": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "license": "ISC"
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-worker": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
- "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/node": "*",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- }
- },
- "node_modules/jpeg-js": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
- "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/js-library-detector": {
- "version": "6.7.0",
- "resolved": "https://registry.npmjs.org/js-library-detector/-/js-library-detector-6.7.0.tgz",
- "integrity": "sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/legacy-javascript": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/legacy-javascript/-/legacy-javascript-0.0.1.tgz",
- "integrity": "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/lighthouse": {
- "version": "13.0.3",
- "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-13.0.3.tgz",
- "integrity": "sha512-mEHAQV3nn4fB+3FDapye+KGeeE4V8FERgbCFegKT7HxqDVGWVOM/BoH9Qof71fzVYVMLIiGnDsnWRrH0sQ9o4Q==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@paulirish/trace_engine": "0.0.61",
- "@sentry/node": "^9.28.1",
- "axe-core": "^4.11.0",
- "chrome-launcher": "^1.2.1",
- "configstore": "^7.0.0",
- "csp_evaluator": "1.1.5",
- "devtools-protocol": "0.0.1527314",
- "enquirer": "^2.3.6",
- "http-link-header": "^1.1.1",
- "intl-messageformat": "^10.5.3",
- "jpeg-js": "^0.4.4",
- "js-library-detector": "^6.7.0",
- "lighthouse-logger": "^2.0.2",
- "lighthouse-stack-packs": "1.12.3",
- "lodash-es": "^4.17.21",
- "lookup-closest-locale": "6.2.0",
- "open": "^8.4.0",
- "puppeteer-core": "^24.23.0",
- "robots-parser": "^3.0.1",
- "speedline-core": "^1.4.3",
- "third-party-web": "^0.27.0",
- "tldts-icann": "^7.0.17",
- "ws": "^7.0.0",
- "yargs": "^17.3.1",
- "yargs-parser": "^21.0.0"
- },
- "bin": {
- "chrome-debug": "core/scripts/manual-chrome-launcher.js",
- "lighthouse": "cli/index.js",
- "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js"
- },
- "engines": {
- "node": ">=22.19"
- }
- },
- "node_modules/lighthouse-logger": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz",
- "integrity": "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "debug": "^4.4.1",
- "marky": "^1.2.2"
- }
- },
- "node_modules/lighthouse-stack-packs": {
- "version": "1.12.3",
- "resolved": "https://registry.npmjs.org/lighthouse-stack-packs/-/lighthouse-stack-packs-1.12.3.tgz",
- "integrity": "sha512-d8IsOpE83kbANgnM+Tp8+x6HcMpX9o2ITBiUERssgzAIFdZCQzs/f4k6D0DLQTE59enml9mbAOU52Wu35exWtg==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/loader-runner": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
- "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.11.5"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash-es": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
- "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lookup-closest-locale": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/lookup-closest-locale/-/lookup-closest-locale-6.2.0.tgz",
- "integrity": "sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/marky": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
- "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/md5": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
- "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "charenc": "0.0.2",
- "crypt": "0.0.2",
- "is-buffer": "~1.1.6"
- }
- },
- "node_modules/merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimatch": {
- "version": "10.2.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
- "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
- "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/mitt": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
- "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/module-details-from-path": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz",
- "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
- "license": "MIT"
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "peer": true,
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/neo-async": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
- "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/netmask": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
- "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/next": {
- "version": "16.1.6",
- "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
- "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@next/env": "16.1.6",
- "@swc/helpers": "0.5.15",
- "baseline-browser-mapping": "^2.8.3",
- "caniuse-lite": "^1.0.30001579",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.6"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "16.1.6",
- "@next/swc-darwin-x64": "16.1.6",
- "@next/swc-linux-arm64-gnu": "16.1.6",
- "@next/swc-linux-arm64-musl": "16.1.6",
- "@next/swc-linux-x64-gnu": "16.1.6",
- "@next/swc-linux-x64-musl": "16.1.6",
- "@next/swc-win32-arm64-msvc": "16.1.6",
- "@next/swc-win32-x64-msvc": "16.1.6",
- "sharp": "^0.34.4"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.51.1",
- "babel-plugin-react-compiler": "*",
- "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@playwright/test": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "license": "MIT",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.36",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
- "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
- "license": "MIT"
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/open": {
- "version": "8.4.2",
- "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
- "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-lazy-prop": "^2.0.0",
- "is-docker": "^2.1.1",
- "is-wsl": "^2.2.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pac-proxy-agent": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
- "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@tootallnate/quickjs-emscripten": "^0.23.0",
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "get-uri": "^6.0.1",
- "http-proxy-agent": "^7.0.0",
- "https-proxy-agent": "^7.0.6",
- "pac-resolver": "^7.0.1",
- "socks-proxy-agent": "^8.0.5"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/pac-resolver": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
- "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "degenerator": "^5.0.0",
- "netmask": "^2.0.2"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
- "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/pend": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pg-int8": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
- "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
- "license": "ISC",
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/pg-protocol": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
- "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
- "license": "MIT"
- },
- "node_modules/pg-types": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
- "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
- "license": "MIT",
- "dependencies": {
- "pg-int8": "1.0.1",
- "postgres-array": "~2.0.0",
- "postgres-bytea": "~1.0.0",
- "postgres-date": "~1.0.4",
- "postgres-interval": "^1.1.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/playwright": {
- "version": "1.58.2",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
- "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
- "devOptional": true,
- "license": "Apache-2.0",
- "dependencies": {
- "playwright-core": "1.58.2"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "fsevents": "2.3.2"
- }
- },
- "node_modules/playwright-core": {
- "version": "1.58.2",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
- "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
- "devOptional": true,
- "license": "Apache-2.0",
- "bin": {
- "playwright-core": "cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postgres-array": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postgres-bytea": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
- "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/postgres-date": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/postgres-interval": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
- "license": "MIT",
- "dependencies": {
- "xtend": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/proxy-agent": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
- "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "http-proxy-agent": "^7.0.1",
- "https-proxy-agent": "^7.0.6",
- "lru-cache": "^7.14.1",
- "pac-proxy-agent": "^7.1.0",
- "proxy-from-env": "^1.1.0",
- "socks-proxy-agent": "^8.0.5"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/proxy-agent/node_modules/lru-cache": {
- "version": "7.18.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
- "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/pump": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
- "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
- "node_modules/puppeteer-core": {
- "version": "24.38.0",
- "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.38.0.tgz",
- "integrity": "sha512-zB3S/tksIhgi2gZRndUe07AudBz5SXOB7hqG0kEa9/YXWrGwlVlYm3tZtwKgfRftBzbmLQl5iwHkQQl04n/mWw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@puppeteer/browsers": "2.13.0",
- "chromium-bidi": "14.0.0",
- "debug": "^4.4.3",
- "devtools-protocol": "0.0.1581282",
- "typed-query-selector": "^2.12.1",
- "webdriver-bidi-protocol": "0.4.1",
- "ws": "^8.19.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/puppeteer-core/node_modules/devtools-protocol": {
- "version": "0.0.1581282",
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
- "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/puppeteer-core/node_modules/ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/react": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
- "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
- "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.4"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/require-in-the-middle": {
- "version": "7.5.2",
- "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz",
- "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "module-details-from-path": "^1.0.3",
- "resolve": "^1.22.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.11",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
- "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/robots-parser": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz",
- "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/rollup": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
- "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.8"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.59.0",
- "@rollup/rollup-android-arm64": "4.59.0",
- "@rollup/rollup-darwin-arm64": "4.59.0",
- "@rollup/rollup-darwin-x64": "4.59.0",
- "@rollup/rollup-freebsd-arm64": "4.59.0",
- "@rollup/rollup-freebsd-x64": "4.59.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
- "@rollup/rollup-linux-arm64-gnu": "4.59.0",
- "@rollup/rollup-linux-arm64-musl": "4.59.0",
- "@rollup/rollup-linux-loong64-gnu": "4.59.0",
- "@rollup/rollup-linux-loong64-musl": "4.59.0",
- "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
- "@rollup/rollup-linux-ppc64-musl": "4.59.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
- "@rollup/rollup-linux-riscv64-musl": "4.59.0",
- "@rollup/rollup-linux-s390x-gnu": "4.59.0",
- "@rollup/rollup-linux-x64-gnu": "4.59.0",
- "@rollup/rollup-linux-x64-musl": "4.59.0",
- "@rollup/rollup-openbsd-x64": "4.59.0",
- "@rollup/rollup-openharmony-arm64": "4.59.0",
- "@rollup/rollup-win32-arm64-msvc": "4.59.0",
- "@rollup/rollup-win32-ia32-msvc": "4.59.0",
- "@rollup/rollup-win32-x64-gnu": "4.59.0",
- "@rollup/rollup-win32-x64-msvc": "4.59.0",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "devOptional": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/sharp": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
- "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@img/colour": "^1.0.0",
- "detect-libc": "^2.1.2",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.34.5",
- "@img/sharp-darwin-x64": "0.34.5",
- "@img/sharp-libvips-darwin-arm64": "1.2.4",
- "@img/sharp-libvips-darwin-x64": "1.2.4",
- "@img/sharp-libvips-linux-arm": "1.2.4",
- "@img/sharp-libvips-linux-arm64": "1.2.4",
- "@img/sharp-libvips-linux-ppc64": "1.2.4",
- "@img/sharp-libvips-linux-riscv64": "1.2.4",
- "@img/sharp-libvips-linux-s390x": "1.2.4",
- "@img/sharp-libvips-linux-x64": "1.2.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
- "@img/sharp-linux-arm": "0.34.5",
- "@img/sharp-linux-arm64": "0.34.5",
- "@img/sharp-linux-ppc64": "0.34.5",
- "@img/sharp-linux-riscv64": "0.34.5",
- "@img/sharp-linux-s390x": "0.34.5",
- "@img/sharp-linux-x64": "0.34.5",
- "@img/sharp-linuxmusl-arm64": "0.34.5",
- "@img/sharp-linuxmusl-x64": "0.34.5",
- "@img/sharp-wasm32": "0.34.5",
- "@img/sharp-win32-arm64": "0.34.5",
- "@img/sharp-win32-ia32": "0.34.5",
- "@img/sharp-win32-x64": "0.34.5"
- }
- },
- "node_modules/shimmer": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz",
- "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==",
- "dev": true,
- "license": "BSD-2-Clause"
- },
- "node_modules/smart-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
- "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks": {
- "version": "2.8.7",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
- "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ip-address": "^10.0.1",
- "smart-buffer": "^4.2.0"
- },
- "engines": {
- "node": ">= 10.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks-proxy-agent": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
- "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "socks": "^2.8.3"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/speedline-core": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/speedline-core/-/speedline-core-1.4.3.tgz",
- "integrity": "sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "image-ssim": "^0.2.0",
- "jpeg-js": "^0.4.1"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/stacktrace-parser": {
- "version": "0.1.11",
- "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz",
- "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==",
- "license": "MIT",
- "dependencies": {
- "type-fest": "^0.7.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/stacktrace-parser/node_modules/type-fest": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
- "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==",
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/streamx": {
- "version": "2.23.0",
- "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
- "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "events-universal": "^1.0.0",
- "fast-fifo": "^1.3.2",
- "text-decoder": "^1.1.0"
- }
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/stubborn-fs": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
- "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "stubborn-utils": "^1.0.1"
- }
- },
- "node_modules/stubborn-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
- "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/styled-jsx": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "client-only": "0.0.1"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "babel-plugin-macros": {
- "optional": true
- }
- }
- },
- "node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tapable": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
- "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/tar-fs": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
- "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pump": "^3.0.0",
- "tar-stream": "^3.1.5"
- },
- "optionalDependencies": {
- "bare-fs": "^4.0.1",
- "bare-path": "^3.0.0"
- }
- },
- "node_modules/tar-stream": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
- "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "b4a": "^1.6.4",
- "bare-fs": "^4.5.5",
- "fast-fifo": "^1.2.0",
- "streamx": "^2.15.0"
- }
- },
- "node_modules/teex": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
- "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "streamx": "^2.12.5"
- }
- },
- "node_modules/terser": {
- "version": "5.46.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
- "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.15.0",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/terser-webpack-plugin": {
- "version": "5.3.17",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz",
- "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
- "jest-worker": "^27.4.5",
- "schema-utils": "^4.3.0",
- "terser": "^5.31.1"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "^5.1.0"
- },
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
- "uglify-js": {
- "optional": true
- }
- }
- },
- "node_modules/text-decoder": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
- "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "b4a": "^1.6.4"
- }
- },
- "node_modules/third-party-web": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.27.0.tgz",
- "integrity": "sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tldts-core": {
- "version": "7.0.24",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.24.tgz",
- "integrity": "sha512-pj7yygNMoMRqG7ML2SDQ0xNIOfN3IBDUcPVM2Sg6hP96oFNN2nqnzHreT3z9xLq85IWJyNTvD38O002DdOrPMw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tldts-icann": {
- "version": "7.0.24",
- "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-7.0.24.tgz",
- "integrity": "sha512-WgCMgvvJEUBU0ZByo0dz8mdLDJE0XoVdu6egZDPJYX2aaxHGX8dJEbF4Il5+M6qix8Br9O5OOeLfyyESU0MoEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tldts-core": "^7.0.24"
- }
- },
- "node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "license": "MIT"
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/typed-query-selector": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
- "integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "license": "MIT"
- },
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
- "node_modules/watchpack": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
- "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "glob-to-regexp": "^0.4.1",
- "graceful-fs": "^4.1.2"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/webdriver-bidi-protocol": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
- "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "license": "BSD-2-Clause"
- },
- "node_modules/webpack": {
- "version": "5.105.4",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz",
- "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/eslint-scope": "^3.7.7",
- "@types/estree": "^1.0.8",
- "@types/json-schema": "^7.0.15",
- "@webassemblyjs/ast": "^1.14.1",
- "@webassemblyjs/wasm-edit": "^1.14.1",
- "@webassemblyjs/wasm-parser": "^1.14.1",
- "acorn": "^8.16.0",
- "acorn-import-phases": "^1.0.3",
- "browserslist": "^4.28.1",
- "chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.20.0",
- "es-module-lexer": "^2.0.0",
- "eslint-scope": "5.1.1",
- "events": "^3.2.0",
- "glob-to-regexp": "^0.4.1",
- "graceful-fs": "^4.2.11",
- "json-parse-even-better-errors": "^2.3.1",
- "loader-runner": "^4.3.1",
- "mime-types": "^2.1.27",
- "neo-async": "^2.6.2",
- "schema-utils": "^4.3.3",
- "tapable": "^2.3.0",
- "terser-webpack-plugin": "^5.3.17",
- "watchpack": "^2.5.1",
- "webpack-sources": "^3.3.4"
- },
- "bin": {
- "webpack": "bin/webpack.js"
- },
- "engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependenciesMeta": {
- "webpack-cli": {
- "optional": true
- }
- }
- },
- "node_modules/webpack-sources": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz",
- "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "node_modules/when-exit": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
- "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/ws": {
- "version": "7.5.10",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
- "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.3.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/xdg-basedir": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz",
- "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4"
- }
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "license": "ISC"
- },
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yauzl": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- }
- }
-}
diff --git a/e2e/package.json b/e2e/package.json
deleted file mode 100644
index f6268fa..0000000
--- a/e2e/package.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "name": "e2e-tests",
- "version": "1.0.0",
- "private": true,
- "scripts": {
- "test": "playwright test",
- "test:ui": "playwright test --ui",
- "test:debug": "playwright test --debug",
- "test:headed": "playwright test --headed",
- "test:smoke": "playwright test --grep @smoke",
- "test:regression": "playwright test --grep @regression",
- "test:performance": "playwright test --grep @performance",
- "test:responsive": "playwright test --grep @responsive",
- "test:visual": "playwright test --grep @visual",
- "test:accessibility": "playwright test --grep @accessibility",
- "test:security": "playwright test --grep @security",
- "test:report": "playwright show-report",
- "test:allure": "allure generate allure-results --clean -o allure-report",
- "test:allure:open": "allure open allure-report",
- "test:allure:serve": "allure serve allure-results",
- "test:all-with-progress": "node run-tests-with-progress.js",
- "test:coverage": "playwright test --config=playwright.coverage.config.ts && node coverage-reporter.js",
- "install": "playwright install --with-deps"
- },
- "devDependencies": {
- "@axe-core/playwright": "^4.11.1",
- "@babel/preset-react": "^7.28.5",
- "@babel/preset-typescript": "^7.28.5",
- "@playwright/test": "^1.58.2",
- "@types/node": "^20.11.0",
- "allure-commandline": "^2.37.0",
- "allure-playwright": "^3.5.0",
- "chrome-launcher": "^1.2.1",
- "glob": "^13.0.6",
- "istanbul-lib-coverage": "^3.2.2",
- "lighthouse": "^13.0.3",
- "typescript": "^5.3.0",
- "v8-to-istanbul": "^9.3.0"
- },
- "dependencies": {
- "@sentry/nextjs": "^10.42.0"
- }
-}
diff --git a/e2e/playwright.config.admin.ts b/e2e/playwright.config.admin.ts
deleted file mode 100644
index 755f875..0000000
--- a/e2e/playwright.config.admin.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { defineConfig, devices } from '@playwright/test';
-
-export default defineConfig({
- testDir: './src/tests/admin',
- fullyParallel: true,
- forbidOnly: !!process.env.CI,
- retries: 0,
- workers: 4,
- reporter: [
- ['list'],
- ['html', { open: 'never' }],
- ],
- timeout: 60000,
- expect: {
- timeout: 20000,
- },
- use: {
- baseURL: 'http://localhost:3000',
- trace: 'retain-on-failure',
- screenshot: 'only-on-failure',
- video: 'retain-on-failure',
- headless: true,
- viewport: { width: 1280, height: 720 },
- actionTimeout: 20000,
- navigationTimeout: 30000,
- storageState: '../.auth/admin.json',
- },
- projects: [
- {
- name: 'admin-chromium',
- use: { ...devices['Desktop Chrome'] },
- },
- ],
-});
diff --git a/e2e/playwright.config.no-auth.ts b/e2e/playwright.config.no-auth.ts
deleted file mode 100644
index ac6ba14..0000000
--- a/e2e/playwright.config.no-auth.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { defineConfig, devices } from '@playwright/test';
-
-export default defineConfig({
- testDir: './src/tests',
- fullyParallel: true,
- forbidOnly: !!process.env.CI,
- retries: process.env.CI ? 2 : 0,
- workers: process.env.CI ? 1 : undefined,
- reporter: [
- ['html', { open: 'never' }],
- ['json', { outputFile: 'test-results/results.json' }],
- ['junit', { outputFile: 'test-results/junit.xml' }],
- ['line'],
- ['list'],
- ],
- timeout: 60000,
- expect: {
- timeout: 10000,
- },
- use: {
- baseURL: 'http://localhost:3000',
- trace: 'on-first-retry',
- screenshot: 'only-on-failure',
- video: 'retain-on-failure',
- headless: true,
- viewport: { width: 1280, height: 720 },
- actionTimeout: 10000,
- navigationTimeout: 30000,
- },
- projects: [
- {
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
- },
- ],
-});
diff --git a/e2e/playwright.config.tiered.ts b/e2e/playwright.config.tiered.ts
deleted file mode 100644
index e8fc734..0000000
--- a/e2e/playwright.config.tiered.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import { defineConfig, devices } from '@playwright/test';
-import { getEnvironment } from './src/config/environments';
-import { getMobileDevices } from './src/utils/devices';
-import { getTestTier } from './src/config/test-tiers';
-
-const env = getEnvironment();
-
-function createTieredConfig(tierName: string) {
- const tier = getTestTier(tierName);
-
- return defineConfig({
- testDir: './src/tests',
- fullyParallel: tier.fullyParallel,
- forbidOnly: !!process.env.CI,
- retries: tier.retries,
- workers: tier.workers,
- globalSetup: require.resolve('./global-setup'),
- reporter: [
- ['html', { open: 'never' }],
- ['json', { outputFile: `test-results/${tierName}-results.json` }],
- ['junit', { outputFile: `test-results/${tierName}-junit.xml` }],
- ['line'],
- ['list'],
- ['allure-playwright', {
- outputFolder: 'allure-results',
- detail: true,
- suiteTitle: false,
- }],
- ],
- timeout: tier.timeout,
- expect: {
- timeout: tier.timeout / 2,
- },
- use: {
- baseURL: env.baseURL,
- trace: env.trace,
- screenshot: env.screenshot,
- video: env.video,
- headless: true,
- viewport: { width: 1280, height: 720 },
- actionTimeout: tier.timeout / 2,
- navigationTimeout: tier.timeout,
- launchOptions: {
- slowMo: env.slowMo,
- },
- storageState: '.auth/admin.json',
- },
- projects: [
- {
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
- },
- {
- name: 'chromium-coverage',
- use: {
- ...devices['Desktop Chrome'],
- browserName: 'chromium',
- },
- testMatch: tier.testMatch,
- },
- {
- name: 'firefox',
- use: { ...devices['Desktop Firefox'] },
- },
- {
- name: 'webkit',
- use: { ...devices['Desktop Safari'] },
- },
- {
- name: 'Mobile Chrome',
- use: { ...devices['Pixel 5'] },
- },
- {
- name: 'Mobile Safari',
- use: { ...devices['iPhone 12'] },
- },
- ...getMobileDevices().map(device => ({
- name: `mobile-${device.name.replace(/\s+/g, '-').toLowerCase()}`,
- use: {
- ...devices['Mobile Chrome'],
- viewport: device.viewport,
- userAgent: device.userAgent,
- deviceScaleFactor: device.deviceScaleFactor,
- isMobile: true,
- },
- })),
- {
- name: 'performance-mobile',
- use: {
- ...devices['Mobile Chrome'],
- viewport: { width: 375, height: 667 },
- isMobile: true,
- },
- testMatch: /.*\.perf\.spec\.ts/,
- },
- {
- name: 'pwa-mobile',
- use: {
- ...devices['Mobile Chrome'],
- viewport: { width: 375, height: 667 },
- isMobile: true,
- serviceWorkers: 'allow',
- },
- testMatch: /.*\.pwa\.spec\.ts/,
- },
- ],
- webServer: env.name === 'development' && !process.env.DISABLE_WEB_SERVER ? {
- command: 'cd .. && npm run dev',
- url: 'http://localhost:3000',
- timeout: 120000,
- reuseExistingServer: !process.env.CI,
- } : undefined,
- });
-}
-
-export default createTieredConfig(process.env.TEST_TIER || 'standard');
\ No newline at end of file
diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts
deleted file mode 100644
index 4856dc9..0000000
--- a/e2e/playwright.config.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { defineConfig, devices } from '@playwright/test';
-import { getEnvironment } from './src/config/environments';
-import { getMobileDevices } from './src/utils/devices';
-
-const env = getEnvironment();
-
-export default defineConfig({
- testDir: './src/tests',
- fullyParallel: true,
- forbidOnly: !!process.env.CI,
- retries: env.retries,
- workers: process.env.CI ? 4 : '50%',
- globalSetup: require.resolve('./global-setup'),
- reporter: [
- ['html', { open: 'never' }],
- ['json', { outputFile: 'test-results/results.json' }],
- ['junit', { outputFile: 'test-results/junit.xml' }],
- ['line'],
- ['list'],
- ['allure-playwright', {
- outputFolder: 'allure-results',
- detail: true,
- suiteTitle: false,
- }],
- ],
- timeout: 90000,
- expect: {
- timeout: 45000,
- },
- use: {
- baseURL: env.baseURL,
- trace: env.trace,
- screenshot: env.screenshot,
- video: env.video,
- headless: true,
- viewport: { width: 1280, height: 720 },
- actionTimeout: 45000,
- navigationTimeout: 90000,
- launchOptions: {
- slowMo: env.slowMo,
- },
- storageState: '.auth/admin.json',
- },
- projects: [
- {
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
- },
- {
- name: 'chromium-coverage',
- use: {
- ...devices['Desktop Chrome'],
- browserName: 'chromium',
- },
- testMatch: /.*\.spec\.ts/,
- globalSetup: undefined,
- },
- {
- name: 'firefox',
- use: { ...devices['Desktop Firefox'] },
- globalSetup: undefined,
- },
- {
- name: 'webkit',
- use: { ...devices['Desktop Safari'] },
- },
- {
- name: 'Mobile Chrome',
- use: { ...devices['Pixel 5'] },
- },
- {
- name: 'Mobile Safari',
- use: { ...devices['iPhone 12'] },
- },
- ...getMobileDevices().map(device => ({
- name: `mobile-${device.name.replace(/\s+/g, '-').toLowerCase()}`,
- use: {
- ...devices['Mobile Chrome'],
- viewport: device.viewport,
- userAgent: device.userAgent,
- deviceScaleFactor: device.deviceScaleFactor,
- isMobile: true,
- },
- })),
- {
- name: 'performance-mobile',
- use: {
- ...devices['Mobile Chrome'],
- viewport: { width: 375, height: 667 },
- isMobile: true,
- },
- testMatch: /.*\.perf\.spec\.ts/,
- },
- {
- name: 'pwa-mobile',
- use: {
- ...devices['Mobile Chrome'],
- viewport: { width: 375, height: 667 },
- isMobile: true,
- serviceWorkers: 'allow',
- },
- testMatch: /.*\.pwa\.spec\.ts/,
- },
- ],
- webServer: env.name === 'development' && !process.env.DISABLE_WEB_SERVER ? {
- command: 'cd .. && npm run dev',
- url: 'http://localhost:3000',
- timeout: 120000,
- reuseExistingServer: !process.env.CI,
- } : undefined,
-});
diff --git a/e2e/playwright.coverage.config.ts b/e2e/playwright.coverage.config.ts
deleted file mode 100644
index 31bbbd6..0000000
--- a/e2e/playwright.coverage.config.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { defineConfig, devices } from '@playwright/test';
-
-export default defineConfig({
- testDir: './src/tests',
- fullyParallel: true,
- forbidOnly: !!process.env.CI,
- retries: 0,
- timeout: 30000,
- use: {
- baseURL: 'http://localhost:3000',
- trace: 'off',
- screenshot: 'off',
- video: 'off',
- headless: true,
- viewport: { width: 1280, height: 720 },
- actionTimeout: 15000,
- navigationTimeout: 30000,
- },
- projects: [
- {
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
- },
- ],
-});
diff --git a/e2e/run-tests-with-progress.js b/e2e/run-tests-with-progress.js
deleted file mode 100644
index ab6f7b4..0000000
--- a/e2e/run-tests-with-progress.js
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env node
-
-const { spawn } = require('child_process');
-const fs = require('fs');
-const { glob } = require('glob');
-
-const testTypes = [
- { name: '冒烟测试', script: 'test:smoke', pattern: 'src/tests/smoke/**/*.spec.ts' },
- { name: '回归测试', script: 'test:regression', pattern: 'src/tests/regression/**/*.spec.ts' },
- { name: '性能测试', script: 'test:performance', pattern: 'src/tests/performance/**/*.spec.ts' },
- { name: '响应式测试', script: 'test:responsive', pattern: 'src/tests/responsive/**/*.spec.ts' }
-];
-
-const TIMEOUT_SECONDS = 600;
-
-async function runTests() {
- console.log('🧪 开始运行E2E测试...\n');
-
- const results = {
- total: 0,
- passed: 0,
- failed: 0,
- byType: {}
- };
-
- for (const testType of testTypes) {
- console.log(`\n${'='.repeat(60)}`);
- console.log(`📋 ${testType.name}`);
- console.log(`${'='.repeat(60)}`);
-
- await new Promise((resolve) => {
- const startTime = Date.now();
- let lastUpdateTime = startTime;
- let currentTest = 0;
- let passedCount = 0;
- let failedCount = 0;
- let isComplete = false;
- let lastTestName = '';
-
- const testProcess = spawn('npm', ['run', testType.script], {
- cwd: __dirname,
- shell: true,
- stdio: ['pipe', 'pipe', 'pipe']
- });
-
- testProcess.stdout.on('data', (data) => {
- const output = data.toString();
-
- if (output.includes('›')) {
- currentTest++;
- const progress = Math.min(100, Math.round((currentTest / 100) * 100));
- const elapsed = Math.round((Date.now() - startTime) / 1000);
- const barLength = Math.floor(progress / 2);
- const bar = '█'.repeat(barLength) + '░'.repeat(50 - barLength);
-
- const testNameMatch = output.match(/›\s+(.+)/);
- if (testNameMatch) {
- lastTestName = testNameMatch[1].trim();
- }
-
- process.stdout.write(`\r⏳ 进度: [${bar}] ${progress}% - ${elapsed}s - ${lastTestName}`);
- lastUpdateTime = Date.now();
- }
-
- if (output.includes('passed')) {
- const match = output.match(/(\d+)\s+passed/);
- if (match) {
- passedCount = parseInt(match[1]);
- }
- }
-
- if (output.includes('failed')) {
- const match = output.match(/(\d+)\s+failed/);
- if (match) {
- failedCount = parseInt(match[1]);
- }
- }
- });
-
- testProcess.stderr.on('data', (data) => {
- const output = data.toString();
- if (output.includes('Error') || output.includes('error')) {
- process.stdout.write('\n❌ 错误: ' + output);
- }
- });
-
- testProcess.on('close', (code) => {
- isComplete = true;
- const elapsed = Math.round((Date.now() - startTime) / 1000);
- process.stdout.write(`\r✅ 完成: [${'█'.repeat(50)}] 100% - ${elapsed}s\n`);
-
- results.total += passedCount + failedCount;
- results.passed += passedCount;
- results.failed += failedCount;
- results.byType[testType.name] = {
- total: passedCount + failedCount,
- passed: passedCount,
- failed: failedCount,
- elapsed: elapsed
- };
-
- resolve();
- });
-
- const progressInterval = setInterval(() => {
- if (!isComplete) {
- const elapsed = Math.round((Date.now() - startTime) / 1000);
- const timeSinceLastUpdate = Date.now() - lastUpdateTime;
-
- if (timeSinceLastUpdate > 10000 && timeSinceLastUpdate < 30000) {
- process.stdout.write(`\r⏳ 等待测试... (${elapsed}s) - ${lastTestName}`);
- } else if (timeSinceLastUpdate >= 30000) {
- process.stdout.write(`\r⚠️ 测试可能卡住 (${elapsed}s) - ${lastTestName}`);
- }
-
- if (elapsed > TIMEOUT_SECONDS) {
- console.log(`\n❌ 测试超时 (${TIMEOUT_SECONDS}s),正在停止...`);
- testProcess.kill();
- clearInterval(progressInterval);
- isComplete = true;
- resolve();
- }
- }
- }, 5000);
-
- testProcess.on('close', () => {
- clearInterval(progressInterval);
- });
- });
- }
-
- console.log(`\n${'='.repeat(60)}`);
- console.log('📊 测试结果汇总');
- console.log(`${'='.repeat(60)}`);
- console.log(`总测试数: ${results.total}`);
- console.log(`通过: ${results.passed} (${((results.passed / results.total) * 100).toFixed(1)}%)`);
- console.log(`失败: ${results.failed} (${((results.failed / results.total) * 100).toFixed(1)}%)`);
-
- console.log('\n分类结果:');
- for (const [name, result] of Object.entries(results.byType)) {
- const passRate = ((result.passed / result.total) * 100).toFixed(1);
- const status = passRate >= 80 ? '✅' : passRate >= 50 ? '⚠️' : '❌';
- console.log(` ${status} ${name}: ${result.passed}/${result.total} (${passRate}%) - ${result.elapsed}s`);
- }
-}
-
-runTests().catch(console.error);
diff --git a/e2e/scripts/generate-report.js b/e2e/scripts/generate-report.js
deleted file mode 100644
index 1c73841..0000000
--- a/e2e/scripts/generate-report.js
+++ /dev/null
@@ -1,99 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-const resultsDir = 'test-results';
-const reportDir = 'test-results';
-
-console.log('📊 生成测试报告...');
-
-if (!fs.existsSync(resultsDir)) {
- console.log('❌ 测试结果目录不存在');
- process.exit(1);
-}
-
-const jsonFiles = fs.readdirSync(resultsDir)
- .filter(file => file.endsWith('.json') && file.includes('-results.json'));
-
-if (jsonFiles.length === 0) {
- console.log('❌ 未找到测试结果文件');
- process.exit(1);
-}
-
-console.log(`📁 找到 ${jsonFiles.length} 个测试结果文件`);
-
-const allResults = [];
-for (const file of jsonFiles) {
- const filePath = path.join(resultsDir, file);
- const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
-
- if (data.suites) {
- for (const suite of data.suites) {
- for (const spec of suite.suites) {
- for (const test of spec.tests) {
- const result = test.results[0];
- allResults.push({
- testId: `${spec.file}::${test.title}`,
- file: spec.file,
- title: test.title,
- status: result.status,
- duration: result.duration,
- tier: file.includes('fast') ? 'fast' : file.includes('deep') ? 'deep' : 'standard',
- });
- }
- }
- }
- }
-}
-
-const report = {
- timestamp: new Date().toISOString(),
- total: {
- name: 'total',
- total: allResults.length,
- passed: allResults.filter(r => r.status === 'passed').length,
- failed: allResults.filter(r => r.status === 'failed').length,
- skipped: allResults.filter(r => r.status === 'skipped').length,
- duration: allResults.reduce((sum, r) => sum + r.duration, 0),
- },
- tiers: {
- fast: allResults.filter(r => r.tier === 'fast').reduce((acc, r) => ({
- name: 'fast',
- total: acc.total + 1,
- passed: acc.passed + (r.status === 'passed' ? 1 : 0),
- failed: acc.failed + (r.status === 'failed' ? 1 : 0),
- duration: acc.duration + r.duration,
- }), { name: 'fast', total: 0, passed: 0, failed: 0, duration: 0 }),
- standard: allResults.filter(r => r.tier === 'standard').reduce((acc, r) => ({
- name: 'standard',
- total: acc.total + 1,
- passed: acc.passed + (r.status === 'passed' ? 1 : 0),
- failed: acc.failed + (r.status === 'failed' ? 1 : 0),
- duration: acc.duration + r.duration,
- }), { name: 'standard', total: 0, passed: 0, failed: 0, duration: 0 }),
- deep: allResults.filter(r => r.tier === 'deep').reduce((acc, r) => ({
- name: 'deep',
- total: acc.total + 1,
- passed: acc.passed + (r.status === 'passed' ? 1 : 0),
- failed: acc.failed + (r.status === 'failed' ? 1 : 0),
- duration: acc.duration + r.duration,
- }), { name: 'deep', total: 0, passed: 0, failed: 0, duration: 0 }),
- },
- failedTests: allResults.filter(r => r.status === 'failed'),
- slowTests: allResults.filter(r => r.duration > 120000),
-};
-
-report.total.avgDuration = report.total.duration / report.total.total;
-
-const reportPath = path.join(reportDir, 'ci-report.json');
-fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
-
-console.log('✅ 测试报告生成完成');
-console.log(` 总测试数: ${report.total.total}`);
-console.log(` 通过: ${report.total.passed}`);
-console.log(` 失败: ${report.total.failed}`);
-console.log(` 总耗时: ${(report.total.duration / 1000).toFixed(2)}s`);
-
-if (report.total.failed > 0) {
- console.log(`\n❌ 发现 ${report.total.failed} 个失败测试`);
- process.exit(1);
-}
\ No newline at end of file
diff --git a/e2e/scripts/manual-login.ts b/e2e/scripts/manual-login.ts
deleted file mode 100644
index 5a3b3a3..0000000
--- a/e2e/scripts/manual-login.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { chromium } from '@playwright/test';
-
-async function login() {
- const browser = await chromium.launch({ headless: false });
- const context = await browser.newContext();
- const page = await context.newPage();
-
- try {
- console.log('🚀 开始登录...');
- await page.goto('http://localhost:3000/admin/login', { timeout: 120000, waitUntil: 'domcontentloaded' });
-
- console.log('📝 填写登录信息...');
- await page.locator('#email').fill('admin@novalon.cn');
- await page.locator('#password').fill('admin123456');
-
- console.log('🖱️ 点击登录按钮...');
- await page.locator('button[type="submit"]').click();
-
- console.log('⏳ 等待登录成功...');
- await page.waitForURL(/\/admin(?!\/login)/, { timeout: 60000 });
-
- console.log('✅ 登录成功!');
- console.log('📍 当前URL:', page.url());
-
- console.log('💾 保存认证状态...');
- await context.storageState({ path: '../.auth/admin.json' });
-
- console.log('✅ 认证状态已保存到 .auth/admin.json');
- } catch (error) {
- console.error('❌ 登录失败:', error);
- await page.screenshot({ path: 'login-error.png' });
- } finally {
- await browser.close();
- }
-}
-
-login();
diff --git a/e2e/src/config/environments.ts b/e2e/src/config/environments.ts
deleted file mode 100644
index 7265a1e..0000000
--- a/e2e/src/config/environments.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-export interface EnvironmentConfig {
- name: string;
- baseURL: string;
- apiURL: string;
- timeout: number;
- retries: number;
- headless: boolean;
- slowMo: number;
- screenshot: 'on' | 'off' | 'only-on-failure';
- video: 'on' | 'off' | 'retain-on-failure';
- trace: 'on' | 'off' | 'retain-on-failure';
-}
-
-export const environments: Record = {
- development: {
- name: 'development',
- baseURL: 'http://localhost:3000',
- apiURL: 'http://localhost:3000/api',
- timeout: 120000,
- retries: 0,
- headless: true,
- slowMo: 100,
- screenshot: 'only-on-failure',
- video: 'retain-on-failure',
- trace: 'retain-on-failure',
- },
- staging: {
- name: 'staging',
- baseURL: 'https://staging.novalon.com',
- apiURL: 'https://staging.novalon.com/api',
- timeout: 120000,
- retries: 1,
- headless: true,
- slowMo: 0,
- screenshot: 'only-on-failure',
- video: 'retain-on-failure',
- trace: 'retain-on-failure',
- },
- production: {
- name: 'production',
- baseURL: 'https://novalon.com',
- apiURL: 'https://novalon.com/api',
- timeout: 120000,
- retries: 2,
- headless: true,
- slowMo: 0,
- screenshot: 'only-on-failure',
- video: 'retain-on-failure',
- trace: 'retain-on-failure',
- },
-};
-
-export function getEnvironment(): EnvironmentConfig {
- const envName = process.env.TEST_ENV || 'development';
- const env = environments[envName];
-
- if (!env) {
- throw new Error(`Unknown environment: ${envName}. Valid environments: ${Object.keys(environments).join(', ')}`);
- }
-
- return env;
-}
-
-export function getBaseURL(): string {
- return getEnvironment().baseURL;
-}
-
-export function getApiURL(): string {
- return getEnvironment().apiURL;
-}
-
-export function isDevelopment(): boolean {
- return getEnvironment().name === 'development';
-}
-
-export function isStaging(): boolean {
- return getEnvironment().name === 'staging';
-}
-
-export function isProduction(): boolean {
- return getEnvironment().name === 'production';
-}
diff --git a/e2e/src/config/network-configs.ts b/e2e/src/config/network-configs.ts
deleted file mode 100644
index eb903b4..0000000
--- a/e2e/src/config/network-configs.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export interface NetworkConfig {
- name: string;
- offline: boolean;
- downloadThroughput?: number;
- uploadThroughput?: number;
- latency?: number;
-}
-
-export const networkConfigs: Record = {
- '2g-slow': {
- name: '2G Slow',
- offline: false,
- downloadThroughput: 250 * 1024,
- uploadThroughput: 50 * 1024,
- latency: 2000,
- },
- '3g-fast': {
- name: '3G Fast',
- offline: false,
- downloadThroughput: 1.6 * 1024 * 1024,
- uploadThroughput: 750 * 1024,
- latency: 100,
- },
- '4g-lte': {
- name: '4G LTE',
- offline: false,
- downloadThroughput: 4 * 1024 * 1024,
- uploadThroughput: 3 * 1024 * 1024,
- latency: 20,
- },
- 'wifi-fast': {
- name: 'WiFi Fast',
- offline: false,
- downloadThroughput: 30 * 1024 * 1024,
- uploadThroughput: 15 * 1024 * 1024,
- latency: 2,
- },
- 'offline': {
- name: 'Offline',
- offline: true,
- },
-};
-
-export function getNetworkConfig(key: string): NetworkConfig {
- return networkConfigs[key] ?? networkConfigs['wifi-fast']!;
-}
-
-export function getAllNetworkConfigs(): NetworkConfig[] {
- return Object.values(networkConfigs);
-}
\ No newline at end of file
diff --git a/e2e/src/config/test-tags.ts b/e2e/src/config/test-tags.ts
deleted file mode 100644
index f202b2e..0000000
--- a/e2e/src/config/test-tags.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export const TEST_TAGS = {
- CRITICAL: '@critical',
- SMOKE: '@smoke',
- REGRESSION: '@regression',
- VISUAL: '@visual',
- PERFORMANCE: '@performance',
- API: '@api',
- MOBILE: '@mobile',
- RESPONSIVE: '@responsive',
- ADMIN: '@admin',
- ACCESSIBILITY: '@a11y',
- SECURITY: '@security',
-} as const;
-
-export type TestTag = typeof TEST_TAGS[keyof typeof TEST_TAGS];
-
-export function getTestPriority(tags: string[]): number {
- if (tags.includes(TEST_TAGS.CRITICAL)) return 1;
- if (tags.includes(TEST_TAGS.SMOKE)) return 2;
- if (tags.includes(TEST_TAGS.REGRESSION)) return 3;
- return 4;
-}
\ No newline at end of file
diff --git a/e2e/src/config/test-tiers.ts b/e2e/src/config/test-tiers.ts
deleted file mode 100644
index 8993fdd..0000000
--- a/e2e/src/config/test-tiers.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-export interface TestTierConfig {
- name: string;
- description: string;
- testMatch: string | RegExp;
- timeout: number;
- retries: number;
- workers: number | string;
- fullyParallel: boolean;
- failFast: boolean;
-}
-
-export const TEST_TIERS: Record = {
- fast: {
- name: '快速层',
- description: '冒烟测试、API测试、基础功能验证',
- testMatch: /.*\.smoke\.spec\.ts$|.*\.api\.spec\.ts$/,
- timeout: 30000,
- retries: 1,
- workers: process.env.CI ? 6 : '75%',
- fullyParallel: true,
- failFast: true,
- },
- standard: {
- name: '标准层',
- description: '功能测试、响应式测试、移动端核心功能',
- testMatch: /.*(admin|navigation|responsive|mobile).*\.spec\.ts$/,
- timeout: 60000,
- retries: 2,
- workers: process.env.CI ? 4 : '50%',
- fullyParallel: true,
- failFast: false,
- },
- deep: {
- name: '深度层',
- description: '视觉回归、性能测试、完整回归测试',
- testMatch: /.*(visual|performance|regression).*\.spec\.ts$/,
- timeout: 120000,
- retries: 3,
- workers: process.env.CI ? 2 : '25%',
- fullyParallel: false,
- failFast: false,
- },
-};
-
-export function getTestTier(tierName: string): TestTierConfig {
- return TEST_TIERS[tierName] || TEST_TIERS.standard;
-}
\ No newline at end of file
diff --git a/e2e/src/data/admin-test-data.ts b/e2e/src/data/admin-test-data.ts
deleted file mode 100644
index 1bae557..0000000
--- a/e2e/src/data/admin-test-data.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-export const adminTestData = {
- users: {
- admin: { email: 'admin@novalon.cn', password: 'admin123456' },
- editor: { email: 'editor@novalon.cn', password: 'editor123' },
- viewer: { email: 'viewer@novalon.cn', password: 'viewer123' }
- },
- content: {
- product: {
- type: 'product',
- title: '测试产品',
- slug: 'test-product',
- content: '产品描述内容'
- },
- service: {
- type: 'service',
- title: '测试服务',
- slug: 'test-service',
- content: '服务描述内容'
- },
- case: {
- type: 'case',
- title: '测试案例',
- slug: 'test-case',
- content: '案例描述内容'
- },
- news: {
- type: 'news',
- title: '测试新闻',
- slug: 'test-news',
- content: '新闻内容'
- }
- }
-};
-
-export function generateTestContent(type: 'product' | 'service' | 'case' | 'news') {
- const timestamp = Date.now();
- return {
- type,
- title: `测试${type}-${timestamp}`,
- slug: `test-${type}-${timestamp}`,
- content: `${type}内容描述-${timestamp}`,
- excerpt: `${type}摘要-${timestamp}`
- };
-}
diff --git a/e2e/src/data/test-data.ts b/e2e/src/data/test-data.ts
deleted file mode 100644
index 164e19c..0000000
--- a/e2e/src/data/test-data.ts
+++ /dev/null
@@ -1,216 +0,0 @@
-export const VALID_CONTACT_DATA = {
- name: '张三',
- email: 'zhangsan@example.com',
- phone: '13800138000',
- subject: '产品咨询',
- message: '您好,我对贵公司的产品很感兴趣,希望能了解更多信息。',
-};
-
-export const INVALID_EMAIL_CASES = [
- { email: 'invalid-email', description: '无@符号' },
- { email: '@example.com', description: '无用户名' },
- { email: 'user@', description: '无域名' },
- { email: 'user@domain', description: '无顶级域名' },
- { email: 'user domain.com', description: '包含空格' },
- { email: 'user@domain..com', description: '连续点号' },
-];
-
-export const INVALID_PHONE_CASES = [
- { phone: '123', description: '过短' },
- { phone: '123456789012345', description: '过长' },
- { phone: 'abcdefghijk', description: '纯字母' },
- { phone: '123-456-7890', description: '包含连字符' },
- { phone: '138 0013 8000', description: '包含空格' },
-];
-
-export const SPECIAL_CHARACTER_CASES = [
- { name: '!@#$%^&*()', description: '特殊符号' },
- { name: '', description: 'XSS脚本' },
- { name: "'; DROP TABLE users; --", description: 'SQL注入' },
- { name: '../../../etc/passwd', description: '路径遍历' },
- { name: '{{template}}', description: '模板注入' },
-];
-
-export const LONG_CONTENT_CASES = [
- { message: 'A'.repeat(500), description: '500字符消息' },
- { message: 'A'.repeat(1000), description: '1000字符消息' },
- { message: 'A'.repeat(1001), description: '超长消息' },
-];
-
-export const BOUNDARY_CASES = {
- name: {
- minLength: '张',
- maxLength: '张三李四王五赵六钱七孙八周九吴十',
- empty: '',
- whitespace: ' ',
- },
- phone: {
- minLength: '1380013800',
- maxLength: '138001380001',
- validFormat: '13800138000',
- },
- email: {
- minLength: 'a@b.c',
- maxLength: `${'a'.repeat(64)}@${'b'.repeat(63)}.com`,
- },
-};
-
-export const PERFORMANCE_THRESHOLDS = {
- loadTime: 5000,
- firstContentfulPaint: 1800,
- largestContentfulPaint: 2500,
- timeToInteractive: 3800,
- cumulativeLayoutShift: 0.1,
- firstInputDelay: 100,
-};
-
-export const RESPONSIVE_BREAKPOINTS = [
- { name: 'mobile-small', width: 320, height: 568 },
- { name: 'mobile-medium', width: 375, height: 667 },
- { name: 'mobile-large', width: 414, height: 896 },
- { name: 'tablet-small', width: 768, height: 1024 },
- { name: 'tablet-large', width: 1024, height: 768 },
- { name: 'desktop-small', width: 1280, height: 720 },
- { name: 'desktop-medium', width: 1366, height: 768 },
- { name: 'desktop-large', width: 1920, height: 1080 },
-];
-
-export const NAVIGATION_ITEMS = [
- { label: '首页', href: '#home', expectedUrl: '/' },
- { label: '关于我们', href: '#about', expectedUrl: '/' },
- { label: '服务', href: '#services', expectedUrl: '/' },
- { label: '产品', href: '#products', expectedUrl: '/' },
- { label: '案例', href: '#cases', expectedUrl: '/' },
- { label: '新闻', href: '#news', expectedUrl: '/' },
- { label: '联系我们', href: '#contact', expectedUrl: '/' },
-];
-
-export const COMPANY_INFO = {
- name: '四川睿新致远科技有限公司',
- address: '四川省成都市高新区天府大道中段1268号天府软件园E区1栋',
- phone: '028-88888888',
- email: 'contact@ruixin.com',
- workHours: [
- { day: '周一至周五', hours: '9:00 - 18:00' },
- { day: '周六', hours: '9:00 - 12:00' },
- { day: '周日', hours: '休息' },
- ],
-};
-
-export const SERVICES = [
- {
- id: 'service-1',
- title: '企业数字化转型',
- description: '帮助企业实现数字化升级',
- },
- {
- id: 'service-2',
- title: '智能制造解决方案',
- description: '提供智能化生产线解决方案',
- },
- {
- id: 'service-3',
- title: '数据分析服务',
- description: '专业的数据分析与可视化服务',
- },
-];
-
-export const PRODUCTS = [
- {
- id: 'product-1',
- title: '智能生产管理系统',
- description: '一体化生产管理平台',
- },
- {
- id: 'product-2',
- title: '数据分析平台',
- description: '企业级数据分析工具',
- },
- {
- id: 'product-3',
- title: '物联网监控平台',
- description: '实时设备监控与管理',
- },
-];
-
-export const NEWS_ITEMS = [
- {
- id: 'news-1',
- title: '公司获得ISO9001认证',
- date: '2024-01-15',
- summary: '公司成功通过ISO9001质量管理体系认证',
- },
- {
- id: 'news-2',
- title: '新产品发布会圆满成功',
- date: '2024-02-20',
- summary: '智能生产管理系统2.0版本正式发布',
- },
-];
-
-export const SECURITY_TEST_CASES = {
- xssPayloads: [
- '',
- '
',
- '