Trezor Keychain Port #1059
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: PR Tests | |
| permissions: | |
| contents: read | |
| on: | |
| pull_request: | |
| branches: [ main, develop ] | |
| types: [ opened, synchronize, reopened ] | |
| push: | |
| branches: [ main ] | |
| jobs: | |
| check-pinned-dependencies: | |
| name: Check Pinned Dependencies | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check for unpinned dependencies | |
| run: | | |
| # Check both dependencies and devDependencies for unpinned versions | |
| # Unpinned = contains ^, ~, >, <, =, *, x, or "latest" | |
| # Note: caret must be at end of character class in jq regex | |
| unpinned=$(jq -r ' | |
| (.dependencies // {}) + (.devDependencies // {}) | |
| | to_entries[] | |
| | select(.value | test("[~><=*x^]|latest")) | |
| | "\(.key): \(.value)" | |
| ' package.json) | |
| if [ -n "$unpinned" ]; then | |
| echo "❌ Found unpinned dependencies in package.json:" | |
| echo "$unpinned" | |
| echo "" | |
| echo "All dependencies must use exact versions (e.g., \"1.2.3\" not \"^1.2.3\")" | |
| exit 1 | |
| fi | |
| echo "✅ All dependencies are pinned to exact versions" | |
| lint-and-type-check: | |
| name: Lint and Type Check | |
| runs-on: ubuntu-latest | |
| needs: check-pinned-dependencies | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run TypeScript type checking | |
| run: npm run compile | |
| - name: Build extension | |
| run: npm run build | |
| unit-tests: | |
| name: Unit Tests (Shard ${{ matrix.shard }}) | |
| runs-on: ubuntu-latest | |
| needs: lint-and-type-check | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build extension | |
| run: npm run build | |
| - name: Run unit tests (Shard ${{ matrix.shard }}/10) | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| run: npx vitest run src --shard=${{ matrix.shard }}/10 | |
| - name: Upload unit test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unit-test-results-shard-${{ matrix.shard }} | |
| path: test-results.json | |
| if-no-files-found: ignore | |
| e2e-tests: | |
| name: E2E Tests (Batch ${{ matrix.batch }}) | |
| runs-on: ubuntu-latest | |
| needs: lint-and-type-check | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Using 20 parallel batches for ~112 test files (~5-6 files per batch) | |
| # This reduces tests per batch to fit within 15-minute timeout | |
| batch: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build extension | |
| run: npm run build | |
| - name: Install Playwright browsers | |
| run: npx playwright install chromium | |
| - name: Install system dependencies | |
| run: npx playwright install-deps chromium | |
| - name: Configure Xvfb (MetaMask style) | |
| run: | | |
| # Start Xvfb in the background with MetaMask's exact configuration | |
| Xvfb -ac :99 -screen 0 1280x1024x16 & | |
| # Give it a moment to start | |
| sleep 2 | |
| - name: Run E2E tests (Batch ${{ matrix.batch }}) | |
| timeout-minutes: 15 # Set a reasonable timeout per batch | |
| run: | | |
| # Dynamically discover all test files and distribute them across batches | |
| TOTAL_BATCHES=20 | |
| CURRENT_BATCH=${{ matrix.batch }} | |
| # Get all test files, sorted for consistency | |
| ALL_FILES=$(find e2e -name "*.spec.ts" -type f | sort) | |
| # Count total files | |
| TOTAL_FILES=$(echo "$ALL_FILES" | wc -l) | |
| FILES_PER_BATCH=$(( (TOTAL_FILES + TOTAL_BATCHES - 1) / TOTAL_BATCHES )) | |
| START_LINE=$(( (CURRENT_BATCH - 1) * FILES_PER_BATCH + 1 )) | |
| END_LINE=$(( START_LINE + FILES_PER_BATCH - 1 )) | |
| # Get files for this batch using sed | |
| BATCH_FILES=$(echo "$ALL_FILES" | sed -n "${START_LINE},${END_LINE}p" | tr '\n' ' ') | |
| # Run tests for this batch | |
| if [ -n "$BATCH_FILES" ]; then | |
| echo "Running tests for batch $CURRENT_BATCH (lines $START_LINE-$END_LINE of $TOTAL_FILES):" | |
| echo "$BATCH_FILES" | |
| npx playwright test $BATCH_FILES --workers=1 --retries=1 | |
| else | |
| echo "No tests to run for batch $CURRENT_BATCH" | |
| fi | |
| env: | |
| CI: true | |
| DISPLAY: ':99' | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-test-results-batch-${{ matrix.batch }} | |
| path: test-results/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| - name: Upload playwright report | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report-batch-${{ matrix.batch }} | |
| path: playwright-report/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # Combined status for branch protection (reports as "Unit Tests") | |
| unit-tests-status: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests] | |
| if: always() | |
| steps: | |
| - name: Check unit test results | |
| run: | | |
| if [ "${{ needs.unit-tests.result }}" != "success" ]; then | |
| echo "Unit tests failed!" | |
| exit 1 | |
| fi | |
| echo "All unit tests passed!" | |
| test-summary: | |
| name: Test Summary | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests, e2e-tests] | |
| if: always() | |
| steps: | |
| - name: Check test results | |
| run: | | |
| if [ "${{ needs.unit-tests.result }}" != "success" ] || [ "${{ needs.e2e-tests.result }}" != "success" ]; then | |
| echo "Tests failed!" | |
| exit 1 | |
| fi | |
| echo "All tests passed!" | |
| security-check: | |
| name: Security Audit | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run security audit | |
| run: | | |
| # Only audit production dependencies (not devDependencies) | |
| # Trezor testing packages have known vulnerabilities in transitive deps we can't fix | |
| npm audit --omit=dev --audit-level=high | |
| continue-on-error: true # Don't fail on low/medium vulnerabilities | |
| - name: Check for known vulnerabilities | |
| run: | | |
| # npm audit returns non-zero for ANY vulnerabilities, so suppress exit code | |
| # Only check production dependencies - dev deps (like @trezor/trezor-user-env-link) | |
| # have unfixed vulnerabilities in their transitive deps | |
| audit_output=$(npm audit --omit=dev --json 2>/dev/null || true) | |
| high_vulns=$(echo "$audit_output" | jq '.metadata.vulnerabilities.high // 0') | |
| critical_vulns=$(echo "$audit_output" | jq '.metadata.vulnerabilities.critical // 0') | |
| if [ "$high_vulns" -gt 0 ] || [ "$critical_vulns" -gt 0 ]; then | |
| echo "Found $critical_vulns critical and $high_vulns high vulnerabilities" | |
| echo "$audit_output" | jq '.vulnerabilities | to_entries[] | select(.value.severity == "high" or .value.severity == "critical") | "\(.key): \(.value.severity)"' | |
| exit 1 | |
| fi | |
| echo "No high or critical vulnerabilities found in production dependencies" |