diff --git a/.github/workflows/copilot_deploy.yml b/.github/workflows/copilot_deploy.yml index 85e79ea3..b5ae9df3 100644 --- a/.github/workflows/copilot_deploy.yml +++ b/.github/workflows/copilot_deploy.yml @@ -116,31 +116,6 @@ jobs: echo LAST_COMMIT_GH=runner >> ./runner/.env cat ./runner/.env - - name: Run Unit test before docker build - run: | - echo "-- Git submodule initialize into local --" - git submodule update --init - echo "-- Pulling git submodules into local --" - git pull --recurse-submodules - echo "-- Installing digital-form-builder-adapter locally --" - node update-package.js - yarn install - echo "-- Building digital-form-builder-adapter locally --" - yarn setup - echo "-- Building digital-form-builder locally --" - # shellcheck disable=SC2164 - cd digital-form-builder - yarn - echo "-- Building digital-form-builder model locally --" - yarn model build - echo "-- Building digital-form-builder queue-model locally --" - yarn queue-model build - echo "-- Building digital-form-builder-adapter model locally --" - cd .. - yarn model build - echo "-- Running unit tests --" - yarn runner test-cov - - name: Set up Docker Buildx property run: echo "DOCKER_BUILDKIT=1" >> $GITHUB_ENV @@ -240,6 +215,11 @@ jobs: e2e-test: needs: [ setup, docker-runner-build, docker-designer-build ] runs-on: ubuntu-latest + if: github.ref != 'refs/heads/main' + strategy: + fail-fast: false + matrix: + containers: [ 0, 1, 2, 3, 4 ] name: run e2e outputs: tag: ${{ steps.hashFile.outputs.tag }} @@ -298,28 +278,19 @@ jobs: - name: Run e2e tests id: e2e - run: yarn e2e-test cypress run + run: yarn e2e-test cypress run --spec $(node e2e-test/cypress-parallel.js ${{ matrix.containers }} 5) --reporter junit --reporter-options "mochaFile=./cypress/screenshots/results/test-output-[hash].xml,toConsole=true,overwrite=false" continue-on-error: true - name: Create Folders - run: mkdir -p ./e2e-test/cypress/screenshots/logs - - - name: Get Logs from localstack - run: docker logs localstack > ./e2e-test/cypress/screenshots/logs/localstack.log - - - name: Get Logs from runner - run: docker logs runner > ./e2e-test/cypress/screenshots/logs/runner.log - - - name: Get all the uploaded files in the bucket - run: docker exec localstack /bin/sh -c "awslocal s3api list-objects --bucket fsd-bucket" + run: mkdir -p ./e2e-test/cypress/screenshots/results - name: Upload E2E Test Report Application if: success() || failure() uses: actions/upload-artifact@v4.4.3 with: - name: e2e-test-report-application + name: e2e-test-report-application-${{ matrix.containers }} path: ./e2e-test/cypress/screenshots - retention-days: 5 + retention-days: 1 - name: Check for errors & exit the job run: | @@ -328,6 +299,67 @@ jobs: exit 1 fi + aggregate-test-results: + name: Aggregate test results + needs: e2e-test # Make sure this job runs after 'e2e-test' job + if: github.ref != 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download Report for task-0 + uses: actions/download-artifact@v4 + with: + name: e2e-test-report-application-0 + path: ./result + - name: Download Report for task-1 + uses: actions/download-artifact@v4 + with: + name: e2e-test-report-application-1 + path: ./result + - name: Download Report for task-2 + uses: actions/download-artifact@v4 + with: + name: e2e-test-report-application-2 + path: ./result + - name: Download Report for task-3 + uses: actions/download-artifact@v4 + with: + name: e2e-test-report-application-3 + path: ./result + - name: Download Report for task-4 + uses: actions/download-artifact@v4 + with: + name: e2e-test-report-application-4 + path: ./result + - name: Install XML Tools + run: sudo apt-get update && sudo apt-get install -y libxml2-utils + - name: Generate Test Suite Report + run: | + echo "Aggregating test suite results..." + echo -e "\n+---------------------------------------------------------------------------------------------------------------------------" + printf "| %-60s | %-19s | %-19s |\n" "File Name" "Tests Count" "Status" + echo -e "----------------------------------------------------------------------------------------------------------------------------" + total_tests=0 + find "result" -name '*.xml' | while read file; do + + file_name=$(xmllint --xpath 'string(//testsuite/@file)' "./$file" -o 2>/dev/null) + tests_in_file=$(xmllint --xpath 'string(//*[local-name()='\''testsuite'\''][2]/@tests)' "./$file" -o 2>/dev/null) + failures=$(xmllint --xpath 'string(//testsuite[2]/@failures)' "./$file" -o 2>/dev/null) + + if [ "$failures" -eq 0 ]; then + failures="Passed" + else + failures="Failed" + fi + + total_tests=$((tests_in_file + total_tests)) + + printf "| %-60s | %-19s | %-19s |\n" "$file_name" "$tests_in_file" "$failures" + done + + echo -e "----------------------------------------------------------------------------------------------------------------------------" + echo "Total Tests: $total_tests" + dev_deploy: needs: [ setup, docker-designer-build, docker-runner-build ] if: ${{ always() && contains(fromJSON(needs.setup.outputs.jobs_to_run), 'dev') && (! contains(needs.*.result, 'failure') ) && (! contains(needs.*.result, 'cancelled') ) }} diff --git a/.github/workflows/publish-base-image.yml b/.github/workflows/publish-base-image.yml new file mode 100644 index 00000000..8d28f341 --- /dev/null +++ b/.github/workflows/publish-base-image.yml @@ -0,0 +1,94 @@ +name: Publish base image + +on: + schedule: + - cron: '0 3 * * *' # Run daily at 3:00 AM UTC + workflow_dispatch: # Allow manual trigger + +env: + DOCKER_REGISTRY: ghcr.io + IMAGE_NAME: "funding-service-design-form-runner-adapter-base" + IMAGE_REPO_PATH: "ghcr.io/${{github.repository_owner}}" + +jobs: + form-runner-base-image: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: "Set version in env" + id: set-version + run: | + source version + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Docker metadata + id: metadata + uses: docker/metadata-action@v4 + with: + images: ${{env.IMAGE_REPO_PATH}}/${{env.IMAGE_NAME}} + tags: | + type=sha,format=long + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} + type=raw,value=${{env.VERSION}},enable=${{ github.ref == format('refs/heads/{0}', 'main') }} + type=raw,value=latest + type=ref,event=branch + + - name: Log in to the Container registry + uses: docker/login-action@v1 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + + - name: Get into the directory + id: change-dir + run: yarn config set enableImmutableInstalls false + + - name: Create .env for runner workspace + run: | + touch ./runner/.env + echo LAST_TAG_GH=runner >> ./runner/.env + echo LAST_COMMIT_GH=runner >> ./runner/.env + cat ./runner/.env + + - name: Set up Docker Buildx property + run: echo "DOCKER_BUILDKIT=1" >> $GITHUB_ENV + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: ~/.buildx/cache + key: ${{ runner.os }}-buildx-runner-${{ hashFiles('./runner/Dockerfile.base') }}-${{ hashFiles('./runner/package.json') }} + restore-keys: | + ${{ runner.os }}-buildx-runner-${{ hashFiles('./runner/Dockerfile.base') }}-${{ hashFiles('./runner/package.json') }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: docker-container + + - name: Build and push docker image + uses: docker/build-push-action@v4 + with: + context: . + tags: ${{ steps.metadata.outputs.tags}} + labels: ${{ steps.metadata.outputs.labels }} + push: true + file: ./runner/Dockerfile.base + build-args: | + LAST_TAG='${{env.VERSION}}' + LAST_COMMIT='${{ github.sha }}' + cache-from: type=gha,scope=buildx + cache-to: type=gha,mode=max,scope=buildx diff --git a/designer/Dockerfile b/designer/Dockerfile index 496b07ca..6d4a3738 100644 --- a/designer/Dockerfile +++ b/designer/Dockerfile @@ -1,83 +1,17 @@ -# ---------------------------- -# Stage 1 -# Base image contains the node version and app user creation -# It also configures the non-root user that will be given permission to copied files/folders in every subsequent stages -FROM node:20-alpine AS base-image -RUN mkdir -p /usr/src/app/digital-form-builder-adapter && \ - addgroup -g 1001 appuser && \ - adduser -S -u 1001 -G appuser appuser && \ - chown -R appuser:appuser /usr/src/app/digital-form-builder-adapter && \ - chmod -R +x /usr/src/app/digital-form-builder-adapter && \ - apk update && \ - apk add --no-cache bash git - - -# ---------------------------- -# Stage 2 -# Cache layer contains yarn configurations -# It will re-run only if there is a yarn configuration change -FROM base-image AS yarn-build -WORKDIR /usr/src/app/digital-form-builder-adapter -COPY --chown=appuser:appuser ../.yarn .yarn -COPY --chown=appuser:appuser ../.yarnrc.yml .yarnrc.yml -USER 1001 - - -# ---------------------------- -# Stage 3 -# Cache layer contains digital-form-builder-adapter configurations -# It will re-run only if there is a configuration change -FROM yarn-build AS digital-form-builder-adapter-pre-build -WORKDIR /usr/src/app/digital-form-builder-adapter -USER 1001 -COPY --chown=appuser:appuser .git ./.git -COPY --chown=appuser:appuser .gitmodules ./.gitmodules -RUN git submodule update --init --recursive -COPY --chown=appuser:appuser ../package.json package.json -COPY --chown=appuser:appuser ../tsconfig.json tsconfig.json -COPY --chown=appuser:appuser ../update-package.js update-package.js -COPY --chown=appuser:appuser ../yarn.lock yarn.lock -COPY --chown=appuser:appuser ../designer/package.json ./designer/package.json -COPY --chown=appuser:appuser ../runner/package.json ./runner/package.json -COPY --chown=appuser:appuser ../model/package.json ./model/package.json - +FROM ghcr.io/communitiesuk/funding-service-design-form-runner-adapter-base:latest AS base-image # ---------------------------- -# Stage 4 -# Cache layer contains digital-form-builder-adapter dependencies -# It will re-run only if there is a dependency change -FROM digital-form-builder-adapter-pre-build AS digital-form-builder-adapter-install -WORKDIR /usr/src/app/digital-form-builder-adapter -USER 1001 -RUN --mount=type=cache,target=.yarn/cache,uid=1001,mode=0755,id=digital-form-builder-adapter-install \ - node update-package.js && yarn setup && yarn install - - -# ---------------------------- -# Stage 5 -# Cache layer contains XGovFormBuilder with yarn build with dependencies -# It will re-run only if there is a XGovFormBuilder change -FROM digital-form-builder-adapter-install AS digital-form-builder-build -WORKDIR /usr/src/app/digital-form-builder-adapter -USER 1001 -WORKDIR /usr/src/app/digital-form-builder-adapter/digital-form-builder -RUN --mount=type=cache,target=.yarn/cache,uid=1001,mode=0755,id=digital-form-builder-build \ - --mount=type=cache,target=.yarn/cache,uid=1001,mode=0755,id=digital-form-builder-adapter-install \ - yarn && yarn model build && yarn queue-model build - - -# ---------------------------- -# Stage 6 +# Stage 1 # Cache layer contains model changes # It will re-run only if there is a model change -FROM digital-form-builder-build AS digital-form-builder-adapter-model-pre-build +FROM base-image AS digital-form-builder-adapter-model-pre-build WORKDIR /usr/src/app/digital-form-builder-adapter USER 1001 COPY --chown=appuser:appuser ../model ./model # ---------------------------- -# Stage 7 +# Stage 2 # Cache layer contains model build # It will re-run only if there is a model change and a build change FROM digital-form-builder-adapter-model-pre-build AS digital-form-builder-adapter-model-build @@ -90,7 +24,7 @@ RUN yarn model build # ---------------------------- -# Stage 8 +# Stage 3 # Cache layer contains designer changes # It will re-run only if there is a designer change FROM digital-form-builder-adapter-model-build AS digital-form-builder-adapter-designer-pre-build @@ -100,7 +34,7 @@ COPY --chown=appuser:appuser ../designer ./designer # ---------------------------- -# Stage 9 +# Stage 4 # Cache layer contains designer build # It will re-run only if there is a designer change and a build change FROM digital-form-builder-adapter-designer-pre-build AS digital-form-builder-adapter-designer-build diff --git a/e2e-test/cypress-parallel.js b/e2e-test/cypress-parallel.js new file mode 100644 index 00000000..e6ec56fb --- /dev/null +++ b/e2e-test/cypress-parallel.js @@ -0,0 +1,31 @@ +const fs = require('fs'); +const path = require('path'); + +const NODE_INDEX = Number(process.argv[2] || 1); +const NODE_TOTAL = Number(process.argv[3] || 1); + +const TEST_FOLDER = './e2e-test/cypress/e2e'; + +console.log(getSpecFiles().join(',')) + +function getSpecFiles() { + const allSpecFiles = traverse(TEST_FOLDER); + const node_index = NODE_INDEX + 1; + return allSpecFiles.sort() + .filter((_, index) => (index % NODE_TOTAL) === (node_index - 1)) + .map(file => file.replace(/^e2e-test\//, '')); +} + +function traverse(dir) { + let files = fs.readdirSync(dir); + files = files.map(file => { + const filePath = path.join(dir, file); + const stats = fs.statSync(filePath); + if (stats.isDirectory()) return traverse(filePath); + else if (stats.isFile()) return filePath; + }); + + return files + .reduce((all, folderContents) => all.concat(folderContents), []); + +} diff --git a/e2e-test/cypress/e2e/runner/exit(not-in-use-in-adapter).feature b/e2e-test/cypress/e2e/runner/exit(not-in-use-in-adapter).feature deleted file mode 100644 index 67bab51e..00000000 --- a/e2e-test/cypress/e2e/runner/exit(not-in-use-in-adapter).feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: Exit - As a user, - I want to be able to exit the service, - so that I can save my progress and return at a later date. - - Background: - Given the form "exit-expiry" exists - - @wip - Scenario: Service can be exited with date displayed - When I navigate to the "exit-expiry" form - Then I see "Save and come back later" - When I choose "lisbon" - And I select the button "Save and come back later" - And I enter "test@test.com" for "Enter your email address" - And I select the button "Save and exit" - Then I see "9 July 2024" - And I see "test@test.com" - - @wip - Scenario: A user can start exiting, then go back to the form - When I navigate to the "exit-expiry" form - Then I see "Save and come back later" - When I choose "lisbon" - And I select the button "Save and come back later" - And I go back to application overview - Then I see "First page" - - @wip - Scenario: An initialised session can be exited - Given the session is initialised for the exit form - When I go to the initialised session URL - And I select the button "Save and come back later" - And I enter "test@test.com" for "Enter your email address" - And I select the button "Save and exit" - Then I see "Your application to exit test has been saved" - # TODO: Mock the API in the e2e process so we can check for correct data sent. - diff --git a/e2e-test/cypress/e2e/runner/imageQualityPlayback(not-in-use-in-adapter).feature b/e2e-test/cypress/e2e/runner/imageQualityPlayback(not-in-use-in-adapter).feature deleted file mode 100644 index 33282f04..00000000 --- a/e2e-test/cypress/e2e/runner/imageQualityPlayback(not-in-use-in-adapter).feature +++ /dev/null @@ -1,26 +0,0 @@ -Feature: Image quality playback page - - Background: - Given the form "image-quality-playback" exists - - @wip - Scenario Outline: Handling upload - Given I navigate to the "image-quality-playback" form - When I upload a file that "" - Then I see the heading "" - Examples: - | case | heading | - | fails-ocr | Check your image | - | passes | Summary | - - @wip - Scenario Outline: Navigating away from the playback page - Given I navigate to the "image-quality-playback" form - When I upload a file that "fails-ocr" - And I choose "