diff --git a/.github/workflows/dev_on_pull_request_workflow.yml b/.github/workflows/dev_on_pull_request_workflow.yml index 565f0a783fd..046c8e8d95f 100644 --- a/.github/workflows/dev_on_pull_request_workflow.yml +++ b/.github/workflows/dev_on_pull_request_workflow.yml @@ -236,6 +236,24 @@ jobs: GCP_EHP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_EHP_WORKLOAD_IDENTITY_PROVIDER }} GCP_EHP_SERVICE_ACCOUNT: ${{ secrets.GCP_EHP_SERVICE_ACCOUNT }} + # FIXME : (mageoffray, 19-09-2024) + # this workflow is temporary and will be used to transitioned between + # e2e tests based on industrial sandbox and e2e tests based on micro sandboxes + test-pro-e2e-migration: + name: "Tests pro E2E migrations" + needs: [pcapi-init-job, build-pcapi] + uses: ./.github/workflows/dev_on_workflow_tests_pro_e2e_migration.yml + if: | + always() && + needs.pcapi-init-job.outputs.api-changed == 'true' || + needs.pcapi-init-job.outputs.pro-changed == 'true' + with: + tag: ${{ needs.build-pcapi.result == 'skipped' && 'latest' || needs.pcapi-init-job.outputs.checksum-tag }} + CACHE_BUCKET_NAME: "passculture-infra-prod-github-runner-cache" + secrets: + GCP_EHP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_EHP_WORKLOAD_IDENTITY_PROVIDER }} + GCP_EHP_SERVICE_ACCOUNT: ${{ secrets.GCP_EHP_SERVICE_ACCOUNT }} + dependabot-auto-merge: name: "Dependabot" needs: test-pro diff --git a/.github/workflows/dev_on_push_workflow_main.yml b/.github/workflows/dev_on_push_workflow_main.yml index ad56ecf5bc9..688557c95ee 100644 --- a/.github/workflows/dev_on_push_workflow_main.yml +++ b/.github/workflows/dev_on_push_workflow_main.yml @@ -239,6 +239,24 @@ jobs: GCP_EHP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_EHP_WORKLOAD_IDENTITY_PROVIDER }} GCP_EHP_SERVICE_ACCOUNT: ${{ secrets.GCP_EHP_SERVICE_ACCOUNT }} + # FIXME : (mageoffray, 19-09-2024) + # this workflow is temporary and will be used to transitioned between + # e2e tests based on industrial sandbox and e2e tests based on micro sandboxes + test-pro-e2e-migration: + name: "Tests pro E2E migrations" + needs: [pcapi-init-job, build-pcapi] + uses: ./.github/workflows/dev_on_workflow_tests_pro_e2e_migration.yml + if: | + always() && + needs.pcapi-init-job.outputs.api-changed == 'true' || + needs.pcapi-init-job.outputs.pro-changed == 'true' + with: + tag: ${{ needs.build-pcapi.result == 'skipped' && 'latest' || needs.pcapi-init-job.outputs.checksum-tag }} + CACHE_BUCKET_NAME: "passculture-infra-prod-github-runner-cache" + secrets: + GCP_EHP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_EHP_WORKLOAD_IDENTITY_PROVIDER }} + GCP_EHP_SERVICE_ACCOUNT: ${{ secrets.GCP_EHP_SERVICE_ACCOUNT }} + deploy-storybook: name: "Deploy Storybook" needs: [pcapi-init-job] diff --git a/.github/workflows/dev_on_workflow_tests_pro_e2e.yml b/.github/workflows/dev_on_workflow_tests_pro_e2e.yml index 7b10c742f64..bd56ebeb022 100644 --- a/.github/workflows/dev_on_workflow_tests_pro_e2e.yml +++ b/.github/workflows/dev_on_workflow_tests_pro_e2e.yml @@ -145,7 +145,7 @@ jobs: wait-on-timeout: 600 working-directory: pro browser: chrome - config-file: cypress/cypress.config.ts + config-file: cypress/old_cypress.config.ts env: TAGS="@P0" record: ${{ github.ref == 'refs/heads/master' }} # for Cypress Cloud env: diff --git a/.github/workflows/dev_on_workflow_tests_pro_e2e_migration.yml b/.github/workflows/dev_on_workflow_tests_pro_e2e_migration.yml new file mode 100644 index 00000000000..4e29387ce9a --- /dev/null +++ b/.github/workflows/dev_on_workflow_tests_pro_e2e_migration.yml @@ -0,0 +1,192 @@ +name: "3 [on_workflow] Tests E2E - migrations" + +on: + workflow_call: + inputs: + image: + type: string + required: false + default: pcapi + tag: + type: string + required: true + CACHE_BUCKET_NAME: + type: string + required: true + secrets: + GCP_EHP_WORKLOAD_IDENTITY_PROVIDER: + required: true + GCP_EHP_SERVICE_ACCOUNT: + required: true + +env: + registry: europe-west1-docker.pkg.dev/passculture-infra-prod/pass-culture-artifact-registry + +defaults: + run: + working-directory: pro + +jobs: + tests-pro-e2e-tests: + name: "E2E tests and notifications" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.7 + - uses: technote-space/workflow-conclusion-action@v3 + - name: "Authentification to Google" + uses: "google-github-actions/auth@v2" + with: + workload_identity_provider: ${{ secrets.GCP_EHP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_EHP_SERVICE_ACCOUNT }} + - name: "Get Secret" + id: secrets + uses: "google-github-actions/get-secretmanager-secrets@v2" + with: + secrets: |- + SLACK_BOT_TOKEN:passculture-metier-ehp/passculture-ci-slack-bot-token + ARTIFACT_REGISTRY_WORKLOAD_IDENTITY_PROVIDER:passculture-metier-ehp/infra-prod-gcp-workload-identity-provider + ARTIFACT_REGISTRY_SERVICE_ACCOUNT:passculture-metier-ehp/passculture-main-artifact-registry-service-account + CYPRESS_CLOUD_RECORD_KEY:passculture-metier-ehp/e2e-tests-pro-cypress-cloud-record-key + CYPRESS_CLOUD_PROJECT_ID:passculture-metier-ehp/e2e-tests-pro-cypress-cloud-project-id + - name: "OpenID Connect Authentication" + id: openid-auth + uses: "google-github-actions/auth@v2" + with: + create_credentials_file: false + token_format: "access_token" + workload_identity_provider: ${{ steps.secrets.outputs.ARTIFACT_REGISTRY_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ steps.secrets.outputs.ARTIFACT_REGISTRY_SERVICE_ACCOUNT }} + - name: "Docker login" + id: docker-login + uses: "docker/login-action@v1" + with: + registry: "europe-west1-docker.pkg.dev" + username: "oauth2accesstoken" + password: "${{ steps.openid-auth.outputs.access_token }}" + - name: "Compute docker image name:tag" + id: compute-image-name + run: | + echo "image_name=${{ env.registry }}/${{ inputs.image }}:${{ inputs.tag }}" | tee -a ${GITHUB_OUTPUT} + echo "::notice:: Running e2e-tests with ${{ env.registry }}/${{ inputs.image }}:${{ inputs.tag }}" + - name: "Download artifact" + if: ${{ inputs.tag != 'latest' }} + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.image }}-${{ inputs.tag }}.tar + path: ${{ runner.temp }} + - uses: actions/setup-node@v3 + with: + node-version-file: "pro/.nvmrc" + - uses: KengoTODA/actions-setup-docker-compose@v1 + with: + version: "2.23.3" + - name: "Fix local permissions" + run: sudo chown -R $PCAPI_UID:$PCAPI_GID . + working-directory: api + env: + PCAPI_UID: 1000 + PCAPI_GID: 1000 + - name: "Cache the node_modules" + id: "yarn-modules-cache" + uses: pass-culture-github-actions/cache@v1.0.0 + with: + compression-method: "gzip" + bucket: ${{ inputs.CACHE_BUCKET_NAME }} + path: | + ~/.cache/Cypress + **/node_modules + key: v1-yarn-pro-cypress-dependency-cache-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + v1-yarn-pro-cypress-dependency-cache-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + - run: yarn install --immutable + - name: "Run postgres and redis server" + run: docker-compose -f ../docker-compose-backend.yml up postgres redis -d + - name: "Set up Cloud SDK" + uses: "google-github-actions/setup-gcloud@v2" + - name: "Run API server" + run: | + if [ "${{ inputs.tag }}" != "latest" ]; then + docker load --input ${{ runner.temp }}/${{ inputs.image }}-${{ inputs.tag }}.tar + fi + docker run \ + --name pc-api \ + --workdir /usr/src/app \ + --volume ./../api:/usr/src/app \ + --env-file ./../env_file \ + --tty \ + --detach \ + --network pass-culture-main_db_nw \ + --restart on-failure \ + --publish 5001:5001 \ + --publish 10002:10002 \ + --entrypoint bash \ + ${{ steps.compute-image-name.outputs.image_name }} \ + -c "set -e ; flask install_postgres_extensions ; alembic upgrade pre@head ; alembic upgrade post@head ; flask install_data ; python src/pcapi/app.py" + - name: "Wait for migrations to be run" + uses: iFaxity/wait-on-action@v1 + with: + resource: http://localhost:5001/health/api + timeout: 120000 + - name: "Build vite application" + run: yarn build:development + - name: "Serve vite preview" + run: yarn serve & + # Using wait-on does not work well for this service, we use curl + - name: "Wait for front-end to listen" + run: | + timeout 300 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:3001)" != "200" ]]; do sleep 5; done' || false + # Doc : https://github.com/cypress-io/github-action + - name: "Cypress run" + uses: cypress-io/github-action@v6 + with: + wait-on: "http://localhost:5001/health/api,http://localhost:5001/health/database" + wait-on-timeout: 600 + working-directory: pro + browser: chrome + config-file: cypress/cypress.config.ts + env: TAGS="@P0" + record: ${{ github.ref == 'refs/heads/master' }} # for Cypress Cloud + spec: "cypress/e2e/migrations/*" + env: + CYPRESS_RECORD_KEY: ${{ steps.secrets.outputs.CYPRESS_CLOUD_RECORD_KEY }} + CYPRESS_PROJECT_ID: ${{ steps.secrets.outputs.CYPRESS_CLOUD_PROJECT_ID }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Move cypress videos" + if: ${{ github.ref != 'refs/heads/master' }} # useless on master bc Cypress Cloud + run: | + mkdir -p cypress/videos/${{ github.ref }}/${{ github.sha }} && \ + mv cypress/videos/*.mp4 cypress/videos/${{ github.ref }}/${{ github.sha }}/ + - name: "Archive E2E results" + if: ${{ github.ref != 'refs/heads/master' }} # useless on master bc Cypress Cloud + uses: google-github-actions/upload-cloud-storage@v2 + with: + path: "pro/cypress/videos" + destination: "${{ inputs.CACHE_BUCKET_NAME }}/pro/cypress/videos/e2e-artifacts" + - name: 'Show pcapi log when it fails' + if: failure() + run: docker logs pc-api + - name: "Post to a Slack channel" + if: always() && failure() && github.ref == 'refs/heads/master' + uses: slackapi/slack-github-action@v1.27.0 + with: + # channel #dev + channel-id: "CPZ7U1CNP" + payload: | + { + "attachments": [ + { + "mrkdwn_in": ["text"], + "color": "#A30002", + "author_name": "${{github.actor}}", + "author_link": "https://github.com/${{github.actor}}", + "author_icon": "https://github.com/${{github.actor}}.png", + "title": "Tests pro E2E", + "title_link": "https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}", + "text": "Les tests pro E2E échouent sur `master` :boom:" + } + ], + "unfurl_links": false, + "unfurl_media": false + } + env: + SLACK_BOT_TOKEN: ${{ steps.secrets.outputs.SLACK_BOT_TOKEN }} diff --git a/api/src/pcapi/routes/internal/e2e.py b/api/src/pcapi/routes/internal/e2e.py index c661f918a83..75480322970 100644 --- a/api/src/pcapi/routes/internal/e2e.py +++ b/api/src/pcapi/routes/internal/e2e.py @@ -5,7 +5,7 @@ from pcapi.sandboxes.scripts import getters -@private_api.route("/sanboxes//", methods=["GET"]) +@private_api.route("/sandboxes//", methods=["GET"]) def get_sandbox(module_name, getter_name): # type: ignore [no-untyped-def] if not hasattr(getters, module_name): errors = ApiErrors() diff --git a/api/src/pcapi/sandboxes/scripts/getters/pro_01_create_pro_user.py b/api/src/pcapi/sandboxes/scripts/getters/pro_01_create_pro_user.py index 26d1dd47785..bf580230b86 100644 --- a/api/src/pcapi/sandboxes/scripts/getters/pro_01_create_pro_user.py +++ b/api/src/pcapi/sandboxes/scripts/getters/pro_01_create_pro_user.py @@ -1,13 +1,7 @@ -from pcapi.core.finance import factories as finance_factories -from pcapi.core.offerers import factories as offerers_factories from pcapi.core.users import factories as users_factories from pcapi.sandboxes.scripts.utils.helpers import get_pro_user_helper def create_pro_user_with_venue_bank_account_and_userofferer() -> dict: pro_user = users_factories.ProFactory() - venue = offerers_factories.VenueFactory() - - finance_factories.BankAccountFactory(offerer=venue.managingOfferer) - offerers_factories.UserOffererFactory(user=pro_user, offerer=venue.managingOfferer) return {"user": get_pro_user_helper(pro_user)} diff --git a/pro/cypress/cypress.config.ts b/pro/cypress/cypress.config.ts index 2229fb845f6..324009568d8 100644 --- a/pro/cypress/cypress.config.ts +++ b/pro/cypress/cypress.config.ts @@ -25,8 +25,6 @@ async function setupNodeEvents( // ts-unused-exports:disable-next-line export default defineConfig({ e2e: { - specPattern: '**/*.feature', - setupNodeEvents, baseUrl: 'http://localhost:3001', diff --git a/pro/cypress/e2e/migrations/01_login.cy.js b/pro/cypress/e2e/migrations/01_login.cy.js new file mode 100644 index 00000000000..3cc428bc5cb --- /dev/null +++ b/pro/cypress/e2e/migrations/01_login.cy.js @@ -0,0 +1,23 @@ +describe('Login Test with HTTP Request', () => { + + let login = ''; + let password = 'user@AZERTY123' + + before(() => { + cy.visit('/connexion') + cy.request({ + method: 'GET', + url: 'http://localhost:5001/sandboxes/pro_01_create_pro_user/create_pro_user_with_venue_bank_account_and_userofferer', + }).then((response) => { + login = response.body.user.email + }); + }) + + it('Should fill out the login form', () => { + expect(true).to.equal(true) + cy.login({ email: login, password: password, redirectUrl: "/parcours-inscription" }) + cy.findAllByTestId('spinner').should('not.exist') + cy.findByText('Finalisez votre inscription') + }); + +}); \ No newline at end of file diff --git a/pro/cypress/old_cypress.config.ts b/pro/cypress/old_cypress.config.ts new file mode 100644 index 00000000000..4548741947e --- /dev/null +++ b/pro/cypress/old_cypress.config.ts @@ -0,0 +1,50 @@ +import { addCucumberPreprocessorPlugin } from '@badeball/cypress-cucumber-preprocessor' +import { createEsbuildPlugin } from '@badeball/cypress-cucumber-preprocessor/esbuild' +import createBundler from '@bahmutov/cypress-esbuild-preprocessor' +import { defineConfig } from 'cypress' +import cypressFailFast = require('cypress-fail-fast/plugin') + +async function setupNodeEvents( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): Promise { + // This is required for the preprocessor to be able to generate JSON reports after each run, and more, + await addCucumberPreprocessorPlugin(on, config) + + on( + 'file:preprocessor', + createBundler({ + plugins: [createEsbuildPlugin(config)], + }) + ) + cypressFailFast(on, config) + // Make sure to return the config object as it might have been modified by the plugin. + return config +} + +// ts-unused-exports:disable-next-line +export default defineConfig({ + e2e: { + specPattern: '**/*.feature', + setupNodeEvents, + + baseUrl: 'http://localhost:3001', + experimentalRunAllSpecs: true, // Run all specs test in UI mode + }, + retries: { + runMode: 2, + openMode: 0, + }, + viewportHeight: 1080, + viewportWidth: 1920, + defaultCommandTimeout: 30000, + requestTimeout: 30000, + video: true, + videoCompression: true, + watchForFileChanges: false, + env: { + FAIL_FAST_STRATEGY: 'run', + FAIL_FAST_ENABLED: true, + FAIL_FAST_BAIL: 3, + }, +}) diff --git a/pro/package.json b/pro/package.json index 47a11764be1..4d2b9b41054 100644 --- a/pro/package.json +++ b/pro/package.json @@ -23,8 +23,10 @@ "prettier:js:changed:fix": "./scripts/lint_diff.sh", "serve": "vite preview", "start": "vite", - "test:bdd": "cypress run --e2e --browser chrome --env tags=@P0 --config-file cypress/cypress.config.ts", - "test:e2e": "cypress open --e2e --browser chrome --config-file cypress/cypress.config.ts", + "test:bdd": "cypress run --e2e --browser chrome --env tags=@P0 --spec '**/*.feature' --config-file cypress/old_cypress.config.ts", + "test:bdd:migration": "cypress run --e2e --browser chrome --env tags=@P0 --spec cypress/e2e/migrations --config-file cypress/cypress.config.ts", + "test:e2e": "cypress open --e2e --browser chrome --config-file cypress/old_cypress.config.ts", + "test:e2e:migration": "cypress open --e2e --browser chrome --config-file cypress/cypress.config.ts", "test:unit": "TZ=UTC vitest", "test:unit:hook": "TZ=UTC vitest related --run", "test:unit:hookcoverage": "TZ=UTC vitest related --run --coverage.enabled=true --coverage.100=true",