From 2bcc664741efea49e07692ebbfa973d6f6aafe61 Mon Sep 17 00:00:00 2001 From: Greg Trihus Date: Mon, 22 Dec 2025 09:18:51 -0600 Subject: [PATCH] run cypress tests with docker - launchs a custom version of the dev server with the latest packages for vite testing - sets the vite optimize deps so it is not run during tests - tests are run in a docker instance - user needs to be added to a docker group so sudo is note required - write was added to packages.json to support running stamp - npm run cy:docker:build - sets up containers ... reloads dependencies - npm run cy:docker - runs tests each time - npm run cy:docker:down - removes containers and network add docker cypress tests to github actions add docker-compose add docker clean up aggressive agent clean up for docker add copilot suggestions change group and user name change group and user id remove user change separate cypress tests into their own job docker w/o root deps --- .dockerignore | 40 +++++++++ .github/workflows/dev.yml | 138 ++++++++++++++++++++++++++++++++ src/renderer/.dockerignore | 40 +++++++++ src/renderer/Dockerfile | 39 +++++++++ src/renderer/Dockerfile.cypress | 14 ++++ src/renderer/docker-compose.yml | 58 ++++++++++++++ src/renderer/package-lock.json | 39 ++++++++- src/renderer/package.json | 6 +- src/renderer/vite.config.ts | 4 + 9 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 src/renderer/.dockerignore create mode 100644 src/renderer/Dockerfile create mode 100644 src/renderer/Dockerfile.cypress create mode 100644 src/renderer/docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4c577566 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Dependencies +node_modules +**/node_modules + +# Build outputs +dist +build +*.log + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage +.nyc_output + +# Cypress +cypress/videos +cypress/screenshots + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp +temp +*.tmp diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c87e188f..faf194d5 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -133,3 +133,141 @@ jobs: if: always() run: | pkill -f node || true + + cypress-tests: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Volta + uses: volta-cli/action@v4 + + - name: Install Node.js 22 + run: | + volta install node@22 + node --version + + - name: Clean up memory (kill any existing node processes) + run: | + pkill -f node || true + + # - name: Install root dependencies + # run: npm ci + + - name: Install renderer dependencies + working-directory: src/renderer + run: npm ci + + - name: Make Environment (.env.local) + working-directory: src/renderer + run: | + echo "VITE_DOMAIN=${{ env.DOMAIN }}" > .env.local + echo "VITE_CLIENTID=${{ env.CLIENT_ID }}" >> .env.local + echo "VITE_ENDPOINT=${{ env.APP }}" >> .env.local + echo "VITE_CALLBACK=${{ env.CALLBACK }}" >> .env.local + echo "VITE_HOST=${{ env.HOST }}" >> .env.local + echo "VITE_HELP=${{ env.HELP }}" >> .env.local + echo "VITE_COMMUNITY=${{ env.COMMUNITY }}" >> .env.local + echo "VITE_OPENNOTES=${{ env.OPEN_NOTES }}" >> .env.local + echo "VITE_RESOURCES=${{ env.RESOURCES }}" >> .env.local + echo "VITE_OPENCONTENT=${{ env.OPEN_CONTENT }}" >> .env.local + echo "VITE_COURSE=${{ env.COURSE }}" >> .env.local + echo "VITE_VIDEO_TRAINING=${{ env.VIDEOS }}" >> .env.local + echo "VITE_WALK_THRU=${{ env.WALK_THRU }}" >> .env.local + echo "VITE_AKUO=${{ env.AKUO }}" >> .env.local + echo "VITE_FLAT=${{ env.FLAT }}" >> .env.local + echo "VITE_HIERARCHICAL=${{ env.HIERARCHICAL }}" >> .env.local + echo "VITE_GEN_FLAT=${{ env.GEN_FLAT }}" >> .env.local + echo "VITE_GEN_HIERARCHICAL=${{ env.GEN_HIERARCHICAL }}" >> .env.local + echo "VITE_GOOGLE_SAMPLES=${{ env.GOOGLE_SAMPLES }}" >> .env.local + echo "VITE_SNAGID=${{ env.SNAG_ID }}" >> .env.local + echo "VITE_SIZELIMIT=${{ env.SIZELIMIT }}" >> .env.local + echo "VITE_SITE_TITLE=${{ env.NAME }}" >> .env.local + + - name: Create auth0-variables.json + working-directory: src/renderer + run: | + echo '{"apiIdentifier":"${{ env.API_ID }}","auth0Domain":"${{ env.DOMAIN }}","webClientId":"${{ env.CLIENT_ID }}"}' > src/auth/auth0-variables.json + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install docker-compose + run: | + sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose --version + + - name: Clean up disk space before tests + run: | + # Clean up system packages + sudo apt-get clean || true + sudo rm -rf /var/lib/apt/lists/* || true + + # Clean up Docker aggressively + docker system prune -af --volumes || true + docker builder prune -af || true + + # Remove cache directories to free up space + rm -rf node_modules/.cache || true + rm -rf src/renderer/node_modules/.cache || true + + - name: Check disk space + run: | + df -h + docker system df + + - name: Build and run Cypress tests in Docker + working-directory: src/renderer + env: + DOCKER_BUILDKIT: 1 + COMPOSE_DOCKER_CLI_BUILD: 1 + run: npm run cy:docker:build + + - name: Clean up Docker containers + if: always() + working-directory: src/renderer + run: npm run cy:docker:down + + - name: Clean up Docker system after tests + if: always() + run: | + docker system prune -af --volumes || true + docker builder prune -af || true + + - name: Upload Cypress screenshots on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-screenshots-${{ github.run_number }} + path: src/renderer/cypress/screenshots + retention-days: 7 + if-no-files-found: ignore + + - name: Upload Cypress videos on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-videos-${{ github.run_number }} + path: src/renderer/cypress/videos + retention-days: 7 + if-no-files-found: ignore + + - name: Upload Cypress test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: cypress-results-${{ github.run_number }} + path: | + src/renderer/cypress/reports + src/renderer/cypress/results + retention-days: 30 + if-no-files-found: ignore + + - name: Clean up processes + if: always() + run: | + pkill -f node || true diff --git a/src/renderer/.dockerignore b/src/renderer/.dockerignore new file mode 100644 index 00000000..4c577566 --- /dev/null +++ b/src/renderer/.dockerignore @@ -0,0 +1,40 @@ +# Dependencies +node_modules +**/node_modules + +# Build outputs +dist +build +*.log + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage +.nyc_output + +# Cypress +cypress/videos +cypress/screenshots + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp +temp +*.tmp diff --git a/src/renderer/Dockerfile b/src/renderer/Dockerfile new file mode 100644 index 00000000..0332304d --- /dev/null +++ b/src/renderer/Dockerfile @@ -0,0 +1,39 @@ +FROM node:22.19.0 + +# Install wget for healthcheck +RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy root package files first +COPY package*.json ./ + +# Install root dependencies (use ci for clean install, ignore cache) +RUN npm ci + +# These lines appear to cause the container build to hang +# # Create a non-root user for running the application +# RUN groupadd --gid 1010 apm \ +# && useradd --uid 1010 --gid apm --shell /bin/bash --create-home apm +# # Change ownership of the app directory to the apm user +# RUN chown -R apm:apm /app +# # Switch to the non-root user +# USER apm + +# Copy renderer package files +COPY src/renderer/package*.json ./src/renderer/ + +# Install renderer dependencies (use ci for clean install, ignore cache) +RUN cd src/renderer && npm ci + +# Copy necessary config files +COPY env-config ./env-config +COPY tsconfig*.json ./ +COPY electron.vite.config.ts ./ +COPY src/renderer ./src/renderer + +# Expose Vite dev server port +EXPOSE 3000 + +# The command will be overridden by docker-compose +CMD ["npm", "start"] diff --git a/src/renderer/Dockerfile.cypress b/src/renderer/Dockerfile.cypress new file mode 100644 index 00000000..c77db0bb --- /dev/null +++ b/src/renderer/Dockerfile.cypress @@ -0,0 +1,14 @@ +FROM cypress/included:15.7.1 + +# Install curl for warming up Vite +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +WORKDIR /e2e + +# Copy package files +COPY package*.json ./ + +# Install project dependencies (including vite-dev-server, etc.) +RUN npm ci + +# The rest will be mounted via volumes diff --git a/src/renderer/docker-compose.yml b/src/renderer/docker-compose.yml new file mode 100644 index 00000000..a9782877 --- /dev/null +++ b/src/renderer/docker-compose.yml @@ -0,0 +1,58 @@ +version: '3.8' + +services: + # Vite development server + app: + build: + context: ../.. + dockerfile: src/renderer/Dockerfile + ports: + - '3000:3000' + volumes: + - ../..:/app + - /app/node_modules + - /app/src/renderer/node_modules + environment: + - NODE_ENV=development + command: sh -c "set -e; cd /app && npm run stamp && npm run devs && cd /app/src/renderer && npm start" + healthcheck: + test: + [ + 'CMD', + 'wget', + '--no-verbose', + '--tries=1', + '--spider', + 'http://localhost:3000', + ] + interval: 3s + timeout: 5s + retries: 20 + start_period: 40s + networks: + - cypress-net + + # Cypress tests + cypress: + build: + context: . + dockerfile: Dockerfile.cypress + depends_on: + app: + condition: service_healthy + environment: + - CYPRESS_baseUrl=http://app:3000 + working_dir: /e2e + volumes: + - .:/e2e + - /e2e/node_modules + command: > + docker run + --component + --config-file cypress/config/local.config.ts + networks: + - cypress-net + +networks: + cypress-net: + driver: bridge diff --git a/src/renderer/package-lock.json b/src/renderer/package-lock.json index 43fc6ef1..45235ea4 100644 --- a/src/renderer/package-lock.json +++ b/src/renderer/package-lock.json @@ -120,7 +120,8 @@ "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.9.3", - "vite": "^7.0.0" + "vite": "^7.0.0", + "write": "^2.0.0" } }, "node_modules/@adobe/css-tools": { @@ -5628,6 +5629,19 @@ "node": ">=0.4.0" } }, + "node_modules/add-filename-increment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-filename-increment/-/add-filename-increment-1.0.0.tgz", + "integrity": "sha512-pFV8VZX8jxuVMIycKvGZkWF/ihnUubu9lbQVnOnZWp7noVxbKQTNj7zG2y9fXdPcuZ6lAN3Drr517HaivGCjdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-filename-increment": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -17452,6 +17466,16 @@ "node": ">=8" } }, + "node_modules/strip-filename-increment": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-filename-increment/-/strip-filename-increment-2.0.1.tgz", + "integrity": "sha512-+v5xsiTTsdYqkPj7qz1zlngIsjZedhHDi3xp/9bMurV8kXe9DAr732gNVqtt4X8sI3hOqS3nlFfps5gyVcux6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -19421,6 +19445,19 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/write/-/write-2.0.0.tgz", + "integrity": "sha512-yam9TAqN8sAZokECAejo9HpT2j2s39OgK8i8yxadrFBVo+iSWLfnipRVFulfAw1d2dz5vSuGmlMHYRKG4fysOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "add-filename-increment": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", diff --git a/src/renderer/package.json b/src/renderer/package.json index 2f747c8d..6c626ed3 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -18,6 +18,9 @@ "cy:open-ct": "cypress open --component --browser firefox --config-file cypress/config/local.config.ts", "cy:run-ct": "cypress run --component --config-file cypress/config/local.config.ts", "cy:run-ct-fast": "npm run cy:run-ct --config video=false screenshot=false", + "cy:docker": "docker-compose up --abort-on-container-exit --exit-code-from cypress", + "cy:docker:build": "docker-compose up --build --abort-on-container-exit --exit-code-from cypress", + "cy:docker:down": "docker-compose down", "devs": "cd ../.. && node env-config/changeEnv.cjs dev && cd src/renderer", "qas": "cd ../.. && node env-config/changeEnv.cjs qa && cd src/renderer", "prods": "cd ../.. && node env-config/changeEnv.cjs prod && cd src/renderer" @@ -174,7 +177,8 @@ "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.9.3", - "vite": "^7.0.0" + "vite": "^7.0.0", + "write": "^2.0.0" }, "author": { "name": "SIL Global", diff --git a/src/renderer/vite.config.ts b/src/renderer/vite.config.ts index 9537c8e7..395a7ae1 100644 --- a/src/renderer/vite.config.ts +++ b/src/renderer/vite.config.ts @@ -7,6 +7,10 @@ export default defineConfig({ port: 3000, host: true, }, + optimizeDeps: { + // Removed force: true to allow Vite to cache optimizations + // This prevents re-optimization during test runs + }, plugins: [react()], build: { rollupOptions: {