E2E Test PR--fix(utils): fix tooltip arrow safe padding #9084
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: E2E Test PR | |
run-name: E2E Test PR--${{ github.event.pull_request.title }} | |
on: | |
pull_request: | |
types: [opened, reopened, synchronize, edited] | |
# 并发控制,确保相同PR的工作流不会同时运行多个实例 | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event.number || github.sha }} | |
cancel-in-progress: true | |
jobs: | |
detect-changed-files: | |
name: Detect Changed Files | |
runs-on: ubuntu-latest | |
outputs: | |
# 输出三个变量供后续任务使用 | |
changed_components: ${{ steps.find-changed-components.outputs.changed_components }} # 自动检测到的变更组件 | |
manual_components: ${{ steps.parse-title.outputs.manual_components }} # 从PR标题手动指定的组件 | |
testclis: ${{ steps.parse-test-cli.outputs.testClis }} # 测试命令列表 | |
steps: | |
# 检出代码,需要完整历史以便比较变更 | |
- name: Checkout code | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 # 获取完整的git历史,用于检测文件变更 | |
# 使用tj-actions/changed-files获取PR中所有变更的文件 | |
- name: Get changed files | |
id: changed-files | |
uses: tj-actions/changed-files@v41 | |
# 根据变更的文件自动识别受影响的组件 | |
- name: Find changed components | |
id: find-changed-components | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
const changedFiles = '${{ steps.changed-files.outputs.all_changed_files }}'.split(' ').filter(file => file.trim() !== ''); | |
console.log('变更的文件列表:', changedFiles); | |
// 增强组件路径匹配规则,确保能正确识别各种路径格式 | |
const componentPathRules = [ | |
// 匹配 packages/renderless/src/{组件名} 路径,支持更深层次的子目录 | |
{ | |
pattern: /^packages\/renderless\/src\/([^/]+)/, | |
group: 1, | |
type: 'renderless' | |
}, | |
// 匹配 packages/vue/src/{组件名} 路径,支持更深层次的子目录 | |
{ | |
pattern: /^packages\/vue\/src\/([^/]+)/, | |
group: 1, | |
type: 'vue' | |
}, | |
// 匹配示例目录下的组件路径 | |
{ | |
pattern: /^examples\/sites\/demos\/pc\/app\/([^/]+)/, | |
group: 1, | |
type: 'demo' | |
}, | |
// 匹配 packages/theme/src/{组件名} 样式路径 | |
{ | |
pattern: /^packages\/theme\/src\/([^/]+)/, | |
group: 1, | |
type: 'theme' | |
} | |
]; | |
// 定义子组件到父组件的映射关系 | |
const subComponentMapping = { | |
'form-item': 'form', | |
'grid-toolbar': 'grid', | |
'calendar-bar': 'calendar', | |
'carousel-item': 'carousel', | |
'cascader-menu': 'cascader', | |
'cascader-node': 'cascader', | |
'cascader-panel': 'cascader', | |
'checkbox-button': 'checkbox', | |
'checkbox-group': 'checkbox', | |
'radio-button': 'radio', | |
'radio-group': 'radio', | |
'collapse-item': 'collapse', | |
'picker': 'date-picker', | |
'date-range': 'date-picker', | |
'date-table': 'date-picker', | |
'month-range': 'date-picker', | |
'month-table': 'date-picker', | |
'quarter-panel': 'date-picker', | |
'year-range': 'date-picker', | |
'year-table': 'date-picker', | |
'dropdown-item': 'dropdown', | |
'dropdown-menu': 'dropdown', | |
'image-viewer': 'image', | |
'option': 'select', | |
'option-group': 'select', | |
'select-dropdown': 'select', | |
'pager-item': 'pager', | |
'skeleton-item': 'skeleton', | |
'tab-item': 'tabs', | |
'time-range': 'time-picker', | |
'time-panel': 'time-picker', | |
'time-spinner': 'time-picker', | |
'timeline-item': 'timeline', | |
'tree-node': 'tree', | |
'transfer-panel': 'transfer', | |
'col': 'layout', | |
'row': 'layout', | |
}; | |
// 从文件路径中提取组件名称 | |
const components = new Set(); | |
const detectedComponents = {}; // 用于记录检测到的组件和相关文件 | |
const unmatchedFiles = []; // 记录未匹配的文件 | |
changedFiles.forEach(file => { | |
// 跳过工作流文件等非组件相关文件 | |
if (file.startsWith('.github/') || | |
file.includes('README') || | |
file.includes('.md') || | |
file.includes('theme-saas') || // 跳过theme-saas相关文件 | |
file.includes('theme/src/common/')) { // 跳过组件通用样式文件 | |
console.log(`跳过非测试相关文件: ${file}`); | |
return; | |
} | |
let matched = false; | |
for (const rule of componentPathRules) { | |
const match = file.match(rule.pattern); | |
if (match && match[rule.group]) { | |
matched = true; | |
let componentName = match[rule.group]; | |
// 检查是否是需要映射的子组件 | |
if (subComponentMapping[componentName]) { | |
console.log(`检测到子组件 ${componentName},映射到父组件 ${subComponentMapping[componentName]}`); | |
componentName = subComponentMapping[componentName]; | |
} | |
// 记录找到的组件 | |
components.add(componentName); | |
// 记录组件来源和相关文件,用于调试 | |
if (!detectedComponents[componentName]) { | |
detectedComponents[componentName] = { | |
type: rule.type, | |
files: [] | |
}; | |
} | |
detectedComponents[componentName].files.push(file); | |
console.log(`成功匹配组件 ${componentName}: ${file} (类型: ${rule.type})`); | |
break; // 一个文件只匹配一个组件 | |
} | |
} | |
// 检查theme目录中的特殊路径格式 | |
if (!matched && file.includes('/theme/src/') && !file.includes('/common/')) { | |
// 尝试从theme路径中提取组件名 | |
const themePath = file.split('/'); | |
// 常见的组件样式文件命名模式 | |
const possibleComponentNames = []; | |
for (let i = 0; i < themePath.length; i++) { | |
// 跳过明显不是组件名的路径部分 | |
if (['src', 'lib', 'theme', 'packages'].includes(themePath[i])) { | |
continue; | |
} | |
// 检查是否像是组件名(小写字母,可能包含连字符) | |
if (/^[a-z][a-z0-9\-]*$/.test(themePath[i])) { | |
possibleComponentNames.push(themePath[i]); | |
} | |
} | |
if (possibleComponentNames.length > 0) { | |
// 使用最后一个可能的组件名(通常最具体) | |
let componentName = possibleComponentNames[possibleComponentNames.length - 1]; | |
// 检查是否是需要映射的子组件 | |
if (subComponentMapping[componentName]) { | |
console.log(`检测到子组件 ${componentName},映射到父组件 ${subComponentMapping[componentName]}`); | |
componentName = subComponentMapping[componentName]; | |
} | |
components.add(componentName); | |
if (!detectedComponents[componentName]) { | |
detectedComponents[componentName] = { | |
type: 'theme', | |
files: [] | |
}; | |
} | |
detectedComponents[componentName].files.push(file); | |
console.log(`通过主题样式路径分析匹配到组件 ${componentName}: ${file}`); | |
matched = true; | |
} | |
} | |
// 记录未匹配到的文件,用于调试 | |
if (!matched) { | |
unmatchedFiles.push(file); | |
console.log(`未能匹配组件: ${file}`); | |
} | |
}); | |
// 打印详细的检测结果,方便调试 | |
console.log('检测结果详情:', JSON.stringify(detectedComponents, null, 2)); | |
console.log('未匹配文件列表:', unmatchedFiles); | |
// 构建E2E测试的组件过滤器表达式 | |
if (components.size > 0) { | |
// 构建符合E2E测试命令需要的过滤表达式格式 | |
const componentFilters = Array.from(components) | |
.map(comp => `"\\/app\\/${comp}\\/"`) | |
.join(' '); | |
console.log(`检测到变更的组件: ${Array.from(components).join(', ')}`); | |
core.setOutput('changed_components', componentFilters); | |
} else { | |
console.log('没有检测到变更的组件'); | |
core.setOutput('changed_components', ''); | |
} | |
# 从PR标题中解析手动指定的组件列表 | |
- name: Parse Title for Manual Components | |
id: parse-title | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
const prTitle = context.payload.pull_request.title | |
// 匹配PR标题中的 [component1, component2] 格式 | |
const regex = /\[(.*?)\]/ | |
const matches = prTitle.match(regex) | |
if (matches && matches.length > 1 && matches[1]) { | |
// 处理和格式化手动指定的组件列表 | |
let components = matches[1] | |
.split(',') | |
.map((c) => c.trim()) | |
.filter((c) => /^[a-z\-\/]+$/.test(c)) // 确保组件名符合规范 | |
.map((c) => `"\\/app\\/${c}\\/"`) | |
components = [...new Set(components)].join(' ') // 去重并转为字符串 | |
core.setOutput('manual_components', components) | |
} else { | |
core.setOutput('manual_components', '') | |
} | |
# 当没有检测到任何组件时,生成警告提示 | |
- name: Generate warning if no components detected | |
id: warning | |
if: ${{ steps.find-changed-components.outputs.changed_components == '' && steps.parse-title.outputs.manual_components == '' }} | |
run: | | |
cat << EOF > user-tip.txt | |
**[e2e-test-warn]** | |
没有检测到要测试的组件。 | |
系统会自动检测PR中变更的组件文件,或者您可以在PR标题中使用[component1, component2]格式手动指定要测试的组件。 | |
例如: "fix(vue-renderless): [action-menu, alert] fix xxx bug" | |
请确保您已阅读我们的[贡献指南](https://github.com/opentiny/tiny-vue/blob/dev/CONTRIBUTING.md) | |
EOF | |
echo "warning=true" >> $GITHUB_OUTPUT | |
# 上传警告信息作为工作流制品 | |
- name: Upload warning | |
if: ${{ steps.warning.outputs.warning }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: user-tip | |
path: user-tip.txt | |
retention-days: 1 # 保留1天 | |
# 解析测试命令配置 | |
- name: Parse Test Cli | |
id: parse-test-cli | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
// 从GitHub变量中获取测试命令列表,如果未配置则使用默认命令 | |
const testClis = '${{ vars.PLAYWRIGHT_CLIS }}' ? '${{ vars.PLAYWRIGHT_CLIS }}'.split(',') : ['pnpm test:e2e3'] | |
core.setOutput('testClis', JSON.stringify(testClis)) | |
# PR测试任务,运行实际的E2E测试 | |
pr-test: | |
name: PR E2E Test | |
needs: detect-changed-files # 依赖前一个任务的输出 | |
runs-on: ubuntu-latest | |
# 只有当检测到变更组件或手动指定组件时才运行测试 | |
if: ${{ needs.detect-changed-files.outputs.changed_components != '' || needs.detect-changed-files.outputs.manual_components != '' }} | |
strategy: | |
matrix: | |
testcli: ${{ fromJson(needs.detect-changed-files.outputs.testclis) }} # 使用矩阵策略运行多个测试命令 | |
env: | |
# 合并自动检测和手动指定的组件列表 | |
TEST_COMPONENTS: ${{ needs.detect-changed-files.outputs.changed_components }} ${{ needs.detect-changed-files.outputs.manual_components }} | |
steps: | |
# 检出代码 | |
- uses: actions/checkout@v3 | |
# 设置pnpm | |
- name: Setup pnpm | |
uses: pnpm/action-setup@v2 | |
# 设置Node.js环境 | |
- name: Setup node | |
uses: actions/setup-node@v3 | |
with: | |
node-version: 20 | |
# 缓存Playwright浏览器安装,加速工作流 | |
- name: Cache Playwright Installation | |
uses: actions/cache@v3 | |
with: | |
path: ~/.cache/ms-playwright | |
key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
# 获取pnpm缓存目录 | |
- name: Get pnpm store directory | |
id: pnpm-cache | |
run: | | |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT | |
# 设置pnpm缓存 | |
- uses: actions/cache@v3 | |
name: Setup pnpm cache | |
with: | |
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} | |
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
restore-keys: | | |
${{ runner.os }}-pnpm-store- | |
# 安装依赖 | |
- name: Install dependencies | |
run: pnpm i --no-frozen-lockfile | |
# 安装Playwright浏览器 | |
- name: Install Playwright browsers | |
run: pnpm install:browser --with-deps chromium | |
# 显示要测试的组件列表,便于调试 | |
- name: Show detected components | |
run: | | |
echo "Testing components: $TEST_COMPONENTS" | |
# 运行E2E测试 | |
- name: E2E Test | |
run: ${{ matrix.testcli }} ${{ env.TEST_COMPONENTS }} --retries=1 --workers=2 # 带重试和并行工作进程 |