diff --git a/.github/workflows/cypress-dev.yml b/.github/workflows/cypress-dev.yml new file mode 100644 index 0000000000..9aaf565207 --- /dev/null +++ b/.github/workflows/cypress-dev.yml @@ -0,0 +1,34 @@ +name: Cypress E2E — Dev + +# Auto-triggers after a successful dev deployment. +# Can also be run manually — supply base_url to target a feature branch deployment +# instead of the default dev environment URL. +# +# Runs Cypress inside OpenShift (d18498-tools) rather than on the GitHub-hosted +# runner, since the app's Route is IP-allowlisted to the BC Gov network. +# Test credentials come from Vault (GH_UGM_CYPRESS_CONFIG) via External Secrets, +# not GitHub Actions secrets — see cypress-e2e-runner.yml. + +on: + workflow_run: + workflows: ["Dev - Build & Push docker images"] + types: [completed] + workflow_dispatch: + inputs: + base_url: + description: "Override baseUrl (e.g. https://feature-branch.apps.silver.devops.gov.bc.ca/)" + required: false + type: string + +permissions: + contents: read + +jobs: + cypress: + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/cypress-e2e-runner.yml + with: + env_name: dev + cypress_config_key: CYPRESS_CONFIG_DEV + base_url: ${{ inputs.base_url || '' }} + secrets: inherit diff --git a/.github/workflows/cypress-e2e-runner.yml b/.github/workflows/cypress-e2e-runner.yml new file mode 100644 index 0000000000..77d7f6fa06 --- /dev/null +++ b/.github/workflows/cypress-e2e-runner.yml @@ -0,0 +1,130 @@ +name: Cypress E2E (runner) + +# Shared job called by cypress-dev/test/uat/prod.yml. +# +# Unlike Grants, this doesn't run Cypress on the GitHub-hosted runner directly — +# the Unity Grant Manager Route is IP-allowlisted to the BC Gov network, which +# GitHub-hosted runners can't reach. Instead this job launches an OpenShift Job +# (from the unity-cypress-job Template in d18498-tools) that runs the existing +# Unity.AutoUI Cypress suite from inside the cluster, waits for it to finish, +# and pulls the logs/screenshots back into this Actions run. +# +# Test credentials are NOT GitHub secrets — the Job pulls them from the +# unity-cypress-config Secret in d18498-tools, synced from Vault +# (GH_UGM_CYPRESS_CONFIG) via External Secrets. +# +# OpenShift auth reuses the same oc-login pattern already used by +# docker-build-dev.yml/docker-build-test.yml/docker-build-main.yml. + +on: + workflow_call: + inputs: + env_name: + description: "Cypress environment name: dev | test | uat | prod" + required: true + type: string + cypress_config_key: + description: "Key in the unity-cypress-config Secret for this env (e.g. CYPRESS_CONFIG_DEV)" + required: true + type: string + base_url: + description: "Optional baseUrl override — use to target a feature branch deployment instead of the default env URL" + required: false + type: string + default: "" + gh_environment: + description: "GitHub Environment to source OpenShift credentials from (defaults to env_name — dev/test have their own; uat/prod reuse 'main')" + required: false + type: string + default: "" + +permissions: + contents: read + +env: + TOOLS_NAMESPACE: d18498-tools + JOB_TIMEOUT: 20m + +jobs: + cypress: + name: Cypress E2E — ${{ inputs.env_name }} + runs-on: ubuntu-latest + environment: ${{ inputs.gh_environment || inputs.env_name }} + env: + OC_CLUSTER: ${{ vars.OPENSHIFT_CLUSTER }} + OC_AUTH_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} + GH_TOKEN: ${{ secrets.GH_API_TOKEN }} + + steps: + - name: Install OpenShift CLI + run: | + curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/oc/latest/linux/oc.tar.gz + tar -xvf oc.tar.gz + sudo mv oc /usr/local/bin + + - name: Connect to OpenShift API + run: oc login --token=$OC_AUTH_TOKEN --server=$OC_CLUSTER + + - name: Launch Cypress Job + id: launch + run: | + # The unity-cypress-job Template lives in the tenant-gitops-d18498 repo + # and is synced into the cluster by ArgoCD — process it by name directly + # from d18498-tools rather than checking out that repo here. + JOB_REF=$(oc process unity-cypress-job \ + -p ENV=${{ inputs.env_name }} \ + -p CYPRESS_CONFIG_KEY=${{ inputs.cypress_config_key }} \ + -p GIT_REF=$GITHUB_SHA \ + -p GIT_TOKEN=$GH_TOKEN \ + -p BASE_URL="${{ inputs.base_url }}" \ + -n $TOOLS_NAMESPACE \ + | oc create -f - -o name -n $TOOLS_NAMESPACE) + echo "Created $JOB_REF" + echo "job_ref=$JOB_REF" >> "$GITHUB_OUTPUT" + + - name: Wait for Cypress Job to finish + id: wait + run: | + JOB_REF="${{ steps.launch.outputs.job_ref }}" + oc wait --for=condition=complete --timeout=$JOB_TIMEOUT "$JOB_REF" -n $TOOLS_NAMESPACE & + COMPLETE_PID=$! + oc wait --for=condition=failed --timeout=$JOB_TIMEOUT "$JOB_REF" -n $TOOLS_NAMESPACE & + FAILED_PID=$! + wait -n $COMPLETE_PID $FAILED_PID || true + kill $COMPLETE_PID $FAILED_PID 2>/dev/null || true + + SUCCEEDED=$(oc get "$JOB_REF" -n $TOOLS_NAMESPACE -o jsonpath='{.status.succeeded}') + echo "succeeded=${SUCCEEDED:-0}" >> "$GITHUB_OUTPUT" + + - name: Show Cypress logs + if: always() + run: | + JOB_REF="${{ steps.launch.outputs.job_ref }}" + oc logs "$JOB_REF" -n $TOOLS_NAMESPACE -c cypress --tail=-1 || true + + - name: Collect screenshots on failure + if: steps.wait.outputs.succeeded != '1' + run: | + JOB_NAME="${{ steps.launch.outputs.job_ref }}" + JOB_NAME="${JOB_NAME#job.batch/}" + POD=$(oc get pods -n $TOOLS_NAMESPACE -l job-name="$JOB_NAME" -o jsonpath='{.items[0].metadata.name}') + mkdir -p cypress-screenshots + oc cp "$TOOLS_NAMESPACE/$POD:/workspace/applications/Unity.AutoUI/cypress/screenshots" ./cypress-screenshots -c cypress || true + + - name: Upload screenshots on failure + if: steps.wait.outputs.succeeded != '1' + uses: actions/upload-artifact@v5 + with: + name: cypress-screenshots-${{ inputs.env_name }}-${{ github.run_number }} + path: cypress-screenshots + if-no-files-found: ignore + + - name: Clean up Cypress Job + if: always() + run: | + JOB_REF="${{ steps.launch.outputs.job_ref }}" + oc delete "$JOB_REF" -n $TOOLS_NAMESPACE --ignore-not-found + + - name: Fail workflow if Cypress did not succeed + if: steps.wait.outputs.succeeded != '1' + run: exit 1 diff --git a/.github/workflows/cypress-prod.yml b/.github/workflows/cypress-prod.yml new file mode 100644 index 0000000000..03ee93b499 --- /dev/null +++ b/.github/workflows/cypress-prod.yml @@ -0,0 +1,20 @@ +name: Cypress E2E — Prod + +# Manual-only — triggered after UAT sign-off, not automatically after deployment. +# +# Runs Cypress inside OpenShift (d18498-tools) — see cypress-e2e-runner.yml. + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + cypress: + uses: ./.github/workflows/cypress-e2e-runner.yml + with: + env_name: prod + cypress_config_key: CYPRESS_CONFIG_PROD + gh_environment: main + secrets: inherit diff --git a/.github/workflows/cypress-test.yml b/.github/workflows/cypress-test.yml new file mode 100644 index 0000000000..9a907f33d9 --- /dev/null +++ b/.github/workflows/cypress-test.yml @@ -0,0 +1,24 @@ +name: Cypress E2E — Test + +# Runs against the test environment after a successful test deployment, +# or manually via workflow_dispatch. +# +# Runs Cypress inside OpenShift (d18498-tools) — see cypress-e2e-runner.yml. + +on: + workflow_run: + workflows: ["Test - Build & Push docker images"] + types: [completed] + workflow_dispatch: + +permissions: + contents: read + +jobs: + cypress: + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/cypress-e2e-runner.yml + with: + env_name: test + cypress_config_key: CYPRESS_CONFIG_TEST + secrets: inherit diff --git a/.github/workflows/cypress-uat.yml b/.github/workflows/cypress-uat.yml new file mode 100644 index 0000000000..4e3236b8f1 --- /dev/null +++ b/.github/workflows/cypress-uat.yml @@ -0,0 +1,30 @@ +name: Cypress E2E — UAT + +# Runs against the UAT environment after a successful main deployment, +# or manually via workflow_dispatch. +# +# Unity has no separate UAT build workflow — UAT deploys off the main build, +# so this triggers off the same workflow_run as prod's build pipeline. +# There's also no "uat" GitHub Environment today, so oc-login reuses the +# "main" Environment's OpenShift credentials (same cluster-wide access). +# +# Runs Cypress inside OpenShift (d18498-tools) — see cypress-e2e-runner.yml. + +on: + workflow_run: + workflows: ["Main - Build & Push docker images"] + types: [completed] + workflow_dispatch: + +permissions: + contents: read + +jobs: + cypress: + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/cypress-e2e-runner.yml + with: + env_name: uat + cypress_config_key: CYPRESS_CONFIG_UAT + gh_environment: main + secrets: inherit diff --git a/applications/Unity.AutoUI/.gitignore b/applications/Unity.AutoUI/.gitignore index 2b2e1fdfca..7fa35447c1 100644 --- a/applications/Unity.AutoUI/.gitignore +++ b/applications/Unity.AutoUI/.gitignore @@ -1,8 +1,14 @@ # Cypress TypeScript project gitignore -# Comment cypress.env.json to update build pipeline settings +# Comment cypress.env.json to update build pipeline settings cypress.env.json +# Local environment config files — create from the .example files in cypress/config/ +cypress/config/dev.json +cypress/config/test.json +cypress/config/uat.json +cypress/config/prod.json + # Dependency directories node_modules/ jspm_packages/ diff --git a/applications/Unity.AutoUI/cypress/config/README-gitignore-config-json-files.md b/applications/Unity.AutoUI/cypress/config/README-gitignore-config-json-files.md new file mode 100644 index 0000000000..355f5f08c9 --- /dev/null +++ b/applications/Unity.AutoUI/cypress/config/README-gitignore-config-json-files.md @@ -0,0 +1,19 @@ +# Cypress Environment Config Files + +The `*.json` config files in this folder are **excluded from git** (via `.gitignore`) because they contain credentials. +Each developer must create their own local copies from the `.example` files provided. + +In CI, `dev.json`/`test.json`/`uat.json`/`prod.json` are written at runtime from the `unity-cypress-config` Secret +(synced from Vault key `GH_UGM_CYPRESS_CONFIG`) by the Cypress Job — see `cypress-job-template.yaml` in +`tenant-gitops-d18498`. + +## Setup + +Copy the example file(s) for the environment(s) you need and fill in your credentials: + +```bash +Copy-Item cypress/config/dev.json.example cypress/config/dev.json +Copy-Item cypress/config/test.json.example cypress/config/test.json +Copy-Item cypress/config/uat.json.example cypress/config/uat.json +Copy-Item cypress/config/prod.json.example cypress/config/prod.json +``` diff --git a/applications/Unity.AutoUI/cypress/config/dev.json.example b/applications/Unity.AutoUI/cypress/config/dev.json.example new file mode 100644 index 0000000000..184c864869 --- /dev/null +++ b/applications/Unity.AutoUI/cypress/config/dev.json.example @@ -0,0 +1,11 @@ +{ + "webapp.url": "https://dev-unity.apps.silver.devops.gov.bc.ca/", + "environment": "DEV", + "test1username": "", + "test1password": "", + "test2username": "", + "test2password": "", + "TEST_EMAIL_TO": "", + "TEST_EMAIL_CC": "", + "TEST_EMAIL_BCC": "" +} diff --git a/applications/Unity.AutoUI/cypress/config/dev2.json.example b/applications/Unity.AutoUI/cypress/config/dev2.json.example new file mode 100644 index 0000000000..cf9669d754 --- /dev/null +++ b/applications/Unity.AutoUI/cypress/config/dev2.json.example @@ -0,0 +1,11 @@ +{ + "webapp.url": "https://dev2-unity.apps.silver.devops.gov.bc.ca/", + "environment": "DEV2", + "test1username": "", + "test1password": "", + "test2username": "", + "test2password": "", + "TEST_EMAIL_TO": "", + "TEST_EMAIL_CC": "", + "TEST_EMAIL_BCC": "" +} diff --git a/applications/Unity.AutoUI/cypress/config/prod.json.example b/applications/Unity.AutoUI/cypress/config/prod.json.example new file mode 100644 index 0000000000..8daeac885c --- /dev/null +++ b/applications/Unity.AutoUI/cypress/config/prod.json.example @@ -0,0 +1,11 @@ +{ + "webapp.url": "https://prod-unity.apps.silver.devops.gov.bc.ca/", + "environment": "PROD", + "test1username": "", + "test1password": "", + "test2username": "", + "test2password": "", + "TEST_EMAIL_TO": "", + "TEST_EMAIL_CC": "", + "TEST_EMAIL_BCC": "" +} diff --git a/applications/Unity.AutoUI/cypress/config/test.json.example b/applications/Unity.AutoUI/cypress/config/test.json.example new file mode 100644 index 0000000000..0d72840645 --- /dev/null +++ b/applications/Unity.AutoUI/cypress/config/test.json.example @@ -0,0 +1,11 @@ +{ + "webapp.url": "https://test-unity.apps.silver.devops.gov.bc.ca/", + "environment": "TEST", + "test1username": "", + "test1password": "", + "test2username": "", + "test2password": "", + "TEST_EMAIL_TO": "", + "TEST_EMAIL_CC": "", + "TEST_EMAIL_BCC": "" +} diff --git a/applications/Unity.AutoUI/cypress/config/uat.json.example b/applications/Unity.AutoUI/cypress/config/uat.json.example new file mode 100644 index 0000000000..3f82b0d2b3 --- /dev/null +++ b/applications/Unity.AutoUI/cypress/config/uat.json.example @@ -0,0 +1,11 @@ +{ + "webapp.url": "https://uat-unity.apps.silver.devops.gov.bc.ca/", + "environment": "UAT", + "test1username": "", + "test1password": "", + "test2username": "", + "test2password": "", + "TEST_EMAIL_TO": "", + "TEST_EMAIL_CC": "", + "TEST_EMAIL_BCC": "" +}