Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 91 additions & 34 deletions .github/workflows/cypress_admin-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,56 +55,44 @@ jobs:
shell: bash
run: |
cd clients/admin-ui/cypress/e2e
# Group test files into balanced groups based on number of tests in each file
# Group test files into balanced groups based on file size (better proxy for execution time than test count)
echo "spec_groups=$(find . -name "*.cy.ts" | python3 -c '
import sys, os, json, math, re
import sys, os, json

# Number of groups to create (adjust based on your needs)
NUM_GROUPS = 5
NUM_GROUPS = 7

# Get all test files with their test counts
# Get all test files with their sizes (file size correlates better with
# test complexity than test count, since heavier tests have more intercepts,
# fixtures, assertions, and setup code)
files = []
for line in sys.stdin:
filepath = line.strip()
if os.path.exists(filepath):
# Count the number of test occurrences (it, describe, context)
test_count = 0
with open(filepath, "r") as f:
content = f.read()
# Count occurrences of test definitions like "it(", "test(", "specify("
test_count += len(re.findall(r"\bit\s*\(", content))
test_count += len(re.findall(r"\btest\s*\(", content))
test_count += len(re.findall(r"\bspecify\s*\(", content))
# If no tests found, set minimum of 1 to ensure file is included
if test_count == 0:
test_count = 1
files.append((filepath.replace("./", ""), test_count))

# Sort files by test count in descending order
size = os.path.getsize(filepath)
files.append((filepath.replace("./", ""), size))

# Sort files by size in descending order
files.sort(key=lambda x: x[1], reverse=True)

# Initialize groups
groups = [[] for _ in range(NUM_GROUPS)]
group_counts = [0] * NUM_GROUPS
group_sizes = [0] * NUM_GROUPS

# Distribute files using greedy algorithm (most tests first)
for file, count in files:
# Find the group with the smallest total test count
min_group_idx = group_counts.index(min(group_counts))
# Distribute files using greedy bin-packing (largest first)
for file, size in files:
# Find the group with the smallest total size
min_group_idx = group_sizes.index(min(group_sizes))
groups[min_group_idx].append(f"cypress/e2e/{file}")
group_counts[min_group_idx] += count
group_sizes[min_group_idx] += size

# Format for GitHub Actions
print(json.dumps([",".join(group) for group in groups]))
')" >> $GITHUB_OUTPUT

Admin-UI-Cypress:
needs: [Check-Admin-UI-Changes, prepare-matrix]
if: needs.Check-Admin-UI-Changes.outputs.has_admin_ui_changes == 'true'
strategy:
fail-fast: false # We want every single job to run completely, we don't want one failing job on the matrix to stop the rest of them.
matrix:
spec_group: ${{ fromJson(needs.prepare-matrix.outputs.spec_groups) }}
build:
needs: Check-Admin-UI-Changes
if: needs.Check-Admin-UI-Changes.outputs.has_admin_ui_changes == 'true' && github.event_name != 'merge_group'
runs-on: ubuntu-latest
defaults:
run:
Expand All @@ -117,9 +105,22 @@ jobs:
uses: actions/setup-node@v5
with:
node-version: 20.x
cache: "npm"

- name: Cache node_modules and Cypress binary
id: deps-cache
uses: actions/cache@v4
with:
path: |
clients/node_modules
clients/admin-ui/node_modules
clients/fides-js/node_modules
clients/fidesui/node_modules
clients/privacy-center/node_modules
~/.cache/Cypress
key: cypress-deps-${{ runner.os }}-${{ hashFiles('clients/package-lock.json') }}

- name: Install dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: npm ci

- name: Build FidesJS
Expand All @@ -130,6 +131,58 @@ jobs:
working-directory: clients/admin-ui
run: npm run build:test

- name: Compress build output
run: tar -czf /tmp/cypress-build.tar.gz admin-ui/.next admin-ui/public/lib fides-js/dist

- name: Upload build output
uses: actions/upload-artifact@v4
with:
name: cypress-build
path: /tmp/cypress-build.tar.gz
retention-days: 1

Admin-UI-Cypress:
needs: [Check-Admin-UI-Changes, build, prepare-matrix]
if: needs.Check-Admin-UI-Changes.outputs.has_admin_ui_changes == 'true'
strategy:
fail-fast: false # We want every single job to run completely, we don't want one failing job on the matrix to stop the rest of them.
matrix:
spec_group: ${{ fromJson(needs.prepare-matrix.outputs.spec_groups) }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: clients
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Use Node.js 20.x
uses: actions/setup-node@v5
with:
node-version: 20.x

- name: Restore node_modules and Cypress binary
uses: actions/cache/restore@v4
with:
path: |
clients/node_modules
clients/admin-ui/node_modules
clients/fides-js/node_modules
clients/fidesui/node_modules
clients/privacy-center/node_modules
~/.cache/Cypress
key: cypress-deps-${{ runner.os }}-${{ hashFiles('clients/package-lock.json') }}
fail-on-cache-miss: true

- name: Download build output
uses: actions/download-artifact@v4
with:
name: cypress-build
path: /tmp

- name: Extract build output
run: tar -xzf /tmp/cypress-build.tar.gz

- name: Cypress Admin UI E2E Tests
uses: cypress-io/github-action@v6
with:
Expand All @@ -150,23 +203,27 @@ jobs:
runs-on: ubuntu-latest
if: always()
needs:
- build
- prepare-matrix
- Admin-UI-Cypress
steps:
- name: Check job results
run: |
echo "build: ${{ needs.build.result }}"
echo "prepare-matrix: ${{ needs.prepare-matrix.result }}"
echo "Admin-UI-Cypress: ${{ needs.Admin-UI-Cypress.result }}"

# Fail only if jobs failed (not if skipped)
if [ "${{ needs.prepare-matrix.result }}" == "failure" ] || \
if [ "${{ needs.build.result }}" == "failure" ] || \
[ "${{ needs.prepare-matrix.result }}" == "failure" ] || \
[ "${{ needs.Admin-UI-Cypress.result }}" == "failure" ]; then
echo "❌ One or more required jobs failed"
exit 1
fi

# Check for cancelled jobs (treat as failure)
if [ "${{ needs.prepare-matrix.result }}" == "cancelled" ] || \
if [ "${{ needs.build.result }}" == "cancelled" ] || \
[ "${{ needs.prepare-matrix.result }}" == "cancelled" ] || \
[ "${{ needs.Admin-UI-Cypress.result }}" == "cancelled" ]; then
echo "❌ One or more required jobs were cancelled"
exit 1
Expand Down
4 changes: 4 additions & 0 deletions changelog/7792-optimize-cypress-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: Developer Experience
description: Optimize Cypress CI with shared build artifacts, file-size-based test sharding, and increased parallelism
pr: 7792
labels: []
2 changes: 1 addition & 1 deletion clients/admin-ui/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ export default defineConfig({
},

// Will only run for cy:run, not cy:open
video: true,
video: false, // change to true if you are having failures in CI and not locally, to help debug
});
Loading