|
| 1 | +name: "E2E PR Checklist" |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + types: [opened, synchronize] |
| 6 | + paths: |
| 7 | + - "e2e/**" |
| 8 | + |
| 9 | +permissions: |
| 10 | + pull-requests: read |
| 11 | + contents: read |
| 12 | + |
| 13 | +jobs: |
| 14 | + add-e2e-checklist: |
| 15 | + name: "Add E2E Checklist" |
| 16 | + runs-on: ubuntu-latest |
| 17 | + permissions: |
| 18 | + pull-requests: write |
| 19 | + steps: |
| 20 | + - name: Detect changed E2E platforms |
| 21 | + id: detect |
| 22 | + uses: actions/github-script@v7 |
| 23 | + with: |
| 24 | + script: | |
| 25 | + const { data: files } = await github.rest.pulls.listFiles({ |
| 26 | + owner: context.repo.owner, |
| 27 | + repo: context.repo.repo, |
| 28 | + pull_number: context.issue.number, |
| 29 | + per_page: 100 |
| 30 | + }); |
| 31 | +
|
| 32 | + const desktop = files.some(f => f.filename.startsWith('e2e/desktop/')); |
| 33 | + const mobile = files.some(f => f.filename.startsWith('e2e/mobile/')); |
| 34 | +
|
| 35 | + core.setOutput('desktop', desktop.toString()); |
| 36 | + core.setOutput('mobile', mobile.toString()); |
| 37 | +
|
| 38 | + - name: Add E2E checklist comment |
| 39 | + uses: actions/github-script@v7 |
| 40 | + env: |
| 41 | + CHANGED_DESKTOP: ${{ steps.detect.outputs.desktop }} |
| 42 | + CHANGED_MOBILE: ${{ steps.detect.outputs.mobile }} |
| 43 | + with: |
| 44 | + script: | |
| 45 | + const desktopChanged = process.env.CHANGED_DESKTOP === 'true'; |
| 46 | + const mobileChanged = process.env.CHANGED_MOBILE === 'true'; |
| 47 | +
|
| 48 | + const platformLabel = desktopChanged && mobileChanged |
| 49 | + ? 'Desktop & Mobile' |
| 50 | + : desktopChanged ? 'Desktop' : 'Mobile'; |
| 51 | +
|
| 52 | + // Build device table columns based on changed platforms |
| 53 | + const header = ['Device']; |
| 54 | + const separator = ['--------']; |
| 55 | + if (desktopChanged) { header.push('Desktop'); separator.push('---------'); } |
| 56 | + if (mobileChanged) { header.push('Mobile'); separator.push('--------'); } |
| 57 | +
|
| 58 | + const devices = ['nanoS', 'nanoSP', 'nanoX', 'stax', 'flex', 'nanoGen5']; |
| 59 | + const rows = devices.map(device => { |
| 60 | + const cols = [`| ${device}`]; |
| 61 | + if (desktopChanged) cols.push('⬜ Passed'); |
| 62 | + if (mobileChanged) cols.push('⬜ Passed'); |
| 63 | + return cols.join(' | ') + ' |'; |
| 64 | + }); |
| 65 | +
|
| 66 | + const deviceTable = [ |
| 67 | + '| ' + header.join(' | ') + ' |', |
| 68 | + '| ' + separator.join(' | ') + ' |', |
| 69 | + ...rows |
| 70 | + ].join('\n'); |
| 71 | +
|
| 72 | + // Build workflow trigger instructions |
| 73 | + const workflowLinks = []; |
| 74 | + if (desktopChanged) { |
| 75 | + workflowLinks.push('- **Desktop:** Trigger [`test-ui-e2e-only-desktop.yml`](../actions/workflows/test-ui-e2e-only-desktop.yml) with `test_filter` set to your test name/tag'); |
| 76 | + } |
| 77 | + if (mobileChanged) { |
| 78 | + workflowLinks.push('- **Mobile:** Trigger [`test-mobile-e2e-reusable.yml`](../actions/workflows/test-mobile-e2e-reusable.yml) with `test_filter` set to your test name/tag'); |
| 79 | + } |
| 80 | +
|
| 81 | +
|
| 82 | + const e2eChecklist = `## 🧪 E2E Test Checklist — ${platformLabel} |
| 83 | +
|
| 84 | + > This checklist was added automatically because your PR touches \`e2e/\` files. |
| 85 | +
|
| 86 | + ### General |
| 87 | + - [ ] Tests pass locally before pushing |
| 88 | + - [ ] No \`.only\` or \`.skip\` left in test files (unless intentional and documented) |
| 89 | + - [ ] Tests are independent and can run in any order |
| 90 | +
|
| 91 | + ### Test Quality |
| 92 | + - [ ] Page Object Model (POM) pattern is followed for new pages/components |
| 93 | + - [ ] Appropriate device tags are added: \`@NanoSP\`, \`@NanoX\`, \`@Stax\`, \`@Flex\`, \`@NanoGen5\` |
| 94 | + - [ ] Family tags are added where applicable: \`@family-evm\`, \`@family-bitcoin\`, etc. |
| 95 | + - [ ] \`@smoke\` tag added for critical path tests (if applicable) |
| 96 | + - [ ] TMS/Xray ticket IDs are linked in test annotations (e.g., \`B2CQA-XXXX\`) |
| 97 | +
|
| 98 | + ### Device Coverage (Required for new/updated tests) |
| 99 | + Run your tests on **all supported devices** using the E2E workflow(s): |
| 100 | +
|
| 101 | + ${workflowLinks.join('\n')} |
| 102 | +
|
| 103 | + ${deviceTable} |
| 104 | +
|
| 105 | + > 💡 Use \`workflow_dispatch\` to trigger the workflow manually on your branch. Set the \`speculos_device\` input to test each device. |
| 106 | + `; |
| 107 | +
|
| 108 | + // Paginate through all comments to reliably find an existing bot comment |
| 109 | + const marker = '## 🧪 E2E Test Checklist'; |
| 110 | + let botComment = null; |
| 111 | + for await (const { data: comments } of github.paginate.iterator( |
| 112 | + github.rest.issues.listComments, |
| 113 | + { owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, per_page: 100 } |
| 114 | + )) { |
| 115 | + botComment = comments.find(c => c.user.type === 'Bot' && c.body.includes(marker)); |
| 116 | + if (botComment) break; |
| 117 | + } |
| 118 | +
|
| 119 | + if (!botComment) { |
| 120 | + await github.rest.issues.createComment({ |
| 121 | + owner: context.repo.owner, |
| 122 | + repo: context.repo.repo, |
| 123 | + issue_number: context.issue.number, |
| 124 | + body: e2eChecklist |
| 125 | + }); |
| 126 | + console.log(`E2E checklist comment added (${platformLabel})`); |
| 127 | + } else { |
| 128 | + // Update existing comment if platforms changed (e.g., mobile added after desktop-only) |
| 129 | + if (!botComment.body.includes(platformLabel)) { |
| 130 | + await github.rest.issues.updateComment({ |
| 131 | + owner: context.repo.owner, |
| 132 | + repo: context.repo.repo, |
| 133 | + comment_id: botComment.id, |
| 134 | + body: e2eChecklist |
| 135 | + }); |
| 136 | + console.log(`E2E checklist comment updated to ${platformLabel}`); |
| 137 | + } else { |
| 138 | + console.log('E2E checklist comment already exists and is up to date, skipping'); |
| 139 | + } |
| 140 | + } |
0 commit comments