|
| 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 | + let desktop = false; |
| 26 | + let mobile = false; |
| 27 | +
|
| 28 | + for await (const { data: files } of github.paginate.iterator( |
| 29 | + github.rest.pulls.listFiles, |
| 30 | + { owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, per_page: 100 } |
| 31 | + )) { |
| 32 | + if (!desktop) desktop = files.some(f => f.filename.startsWith('e2e/desktop/')); |
| 33 | + if (!mobile) mobile = files.some(f => f.filename.startsWith('e2e/mobile/')); |
| 34 | + if (desktop && mobile) break; |
| 35 | + } |
| 36 | +
|
| 37 | + core.setOutput('desktop', desktop.toString()); |
| 38 | + core.setOutput('mobile', mobile.toString()); |
| 39 | +
|
| 40 | + - name: Add E2E checklist comment |
| 41 | + uses: actions/github-script@v7 |
| 42 | + env: |
| 43 | + CHANGED_DESKTOP: ${{ steps.detect.outputs.desktop }} |
| 44 | + CHANGED_MOBILE: ${{ steps.detect.outputs.mobile }} |
| 45 | + with: |
| 46 | + script: | |
| 47 | + const desktopChanged = process.env.CHANGED_DESKTOP === 'true'; |
| 48 | + const mobileChanged = process.env.CHANGED_MOBILE === 'true'; |
| 49 | +
|
| 50 | + // Bump this version when the checklist template changes to refresh existing comments |
| 51 | + const TEMPLATE_VERSION = 'v1'; |
| 52 | +
|
| 53 | + if (!desktopChanged && !mobileChanged) { |
| 54 | + console.log('PR touches e2e/ but not e2e/desktop/ or e2e/mobile/ — skipping checklist'); |
| 55 | + return; |
| 56 | + } |
| 57 | +
|
| 58 | + const platformLabel = desktopChanged && mobileChanged |
| 59 | + ? 'Desktop & Mobile' |
| 60 | + : desktopChanged ? 'Desktop' : 'Mobile'; |
| 61 | +
|
| 62 | + // Build device table with proof link columns per platform |
| 63 | + const header = ['Device']; |
| 64 | + const separator = ['--------']; |
| 65 | + if (desktopChanged) { |
| 66 | + header.push('Proof (Desktop)'); |
| 67 | + separator.push('---'); |
| 68 | + } |
| 69 | + if (mobileChanged) { |
| 70 | + header.push('Proof (Mobile)'); |
| 71 | + separator.push('---'); |
| 72 | + } |
| 73 | +
|
| 74 | + const devices = ['nanoS', 'nanoSP', 'nanoX', 'stax', 'flex', 'nanoGen5']; |
| 75 | + const rows = devices.map(device => { |
| 76 | + const cols = [`| ${device}`]; |
| 77 | + if (desktopChanged) cols.push('_paste link_'); |
| 78 | + if (mobileChanged) cols.push('_paste link_'); |
| 79 | + return cols.join(' | ') + ' |'; |
| 80 | + }); |
| 81 | +
|
| 82 | + const deviceTable = [ |
| 83 | + '| ' + header.join(' | ') + ' |', |
| 84 | + '| ' + separator.join(' | ') + ' |', |
| 85 | + ...rows |
| 86 | + ].join('\n'); |
| 87 | +
|
| 88 | + // Build workflow trigger instructions |
| 89 | + const workflowLinks = []; |
| 90 | + if (desktopChanged) { |
| 91 | + 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'); |
| 92 | + } |
| 93 | + if (mobileChanged) { |
| 94 | + 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'); |
| 95 | + } |
| 96 | +
|
| 97 | +
|
| 98 | + const e2eChecklist = [ |
| 99 | + `<!-- e2e-checklist ${TEMPLATE_VERSION} ${platformLabel} -->`, |
| 100 | + `## 🧪 E2E Test Checklist — ${platformLabel}`, |
| 101 | + '', |
| 102 | + '> This checklist was added automatically because your PR touches `e2e/` files.', |
| 103 | + '', |
| 104 | + '### General', |
| 105 | + '- [ ] Tests pass locally before pushing', |
| 106 | + '- [ ] Tests are independent and can run in any order', |
| 107 | + '', |
| 108 | + '### Test Quality', |
| 109 | + '- [ ] Page Object Model (POM) pattern is followed for new pages/components', |
| 110 | + '- [ ] Appropriate device tags are added: `@NanoSP`, `@LNS`, `@NanoX`, `@Stax`, `@Flex`, `@NanoGen5`', |
| 111 | + '- [ ] Family tags are added where applicable: `@family-evm`, `@family-bitcoin`, etc.', |
| 112 | + '- [ ] TMS/Xray ticket IDs are linked in test annotations (e.g., `B2CQA-XXXX`)', |
| 113 | + '', |
| 114 | + '### Device Coverage (Required for new/updated tests)', |
| 115 | + 'Run your tests on **all supported devices** using the E2E workflow(s):', |
| 116 | + '', |
| 117 | + ...workflowLinks, |
| 118 | + '', |
| 119 | + deviceTable, |
| 120 | + '', |
| 121 | + '> 💡 Use `workflow_dispatch` to trigger the workflow manually on your branch. Set the `speculos_device` input to test each device. Replace *paste link* with a link to the workflow run or Allure report.', |
| 122 | + ].join('\n'); |
| 123 | +
|
| 124 | + // Paginate through all comments to reliably find an existing bot comment |
| 125 | + const marker = '<!-- e2e-checklist'; |
| 126 | + const versionTag = `<!-- e2e-checklist ${TEMPLATE_VERSION} ${platformLabel} -->`; |
| 127 | + let botComment = null; |
| 128 | + for await (const { data: comments } of github.paginate.iterator( |
| 129 | + github.rest.issues.listComments, |
| 130 | + { owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, per_page: 100 } |
| 131 | + )) { |
| 132 | + botComment = comments.find(c => c.user.type === 'Bot' && c.body.includes(marker)); |
| 133 | + if (botComment) break; |
| 134 | + } |
| 135 | +
|
| 136 | + if (!botComment) { |
| 137 | + await github.rest.issues.createComment({ |
| 138 | + owner: context.repo.owner, |
| 139 | + repo: context.repo.repo, |
| 140 | + issue_number: context.issue.number, |
| 141 | + body: e2eChecklist |
| 142 | + }); |
| 143 | + console.log(`E2E checklist comment added (${TEMPLATE_VERSION}, ${platformLabel})`); |
| 144 | + } else if (!botComment.body.includes(versionTag)) { |
| 145 | + // Update if template version or platform changed |
| 146 | + await github.rest.issues.updateComment({ |
| 147 | + owner: context.repo.owner, |
| 148 | + repo: context.repo.repo, |
| 149 | + comment_id: botComment.id, |
| 150 | + body: e2eChecklist |
| 151 | + }); |
| 152 | + console.log(`E2E checklist comment updated (${TEMPLATE_VERSION}, ${platformLabel})`); |
| 153 | + } else { |
| 154 | + console.log('E2E checklist comment already exists and is up to date, skipping'); |
| 155 | + } |
0 commit comments