diff --git a/.commitlintrc.json b/.commitlintrc.json deleted file mode 100644 index 1e59a32dc9b1..000000000000 --- a/.commitlintrc.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "extends": [ - "@commitlint/config-conventional" - ], - "rules": { - "body-max-line-length": [ - 0, - "always" - ], - "subject-case": [ - 2, - "always", - [ - "sentence-case", - "start-case", - "pascal-case", - "upper-case", - "lower-case", - "camel-case" - ] - ], - "type-enum": [ - 2, - "always", - [ - "build", - "chore", - "ci", - "docs", - "feat", - "fix", - "perf", - "refactor", - "revert", - "style", - "test", - "sample" - ] - ] - } -} diff --git a/.cspell.json b/.cspell.json index 1e2800efdfee..ba67a23fe5f8 100644 --- a/.cspell.json +++ b/.cspell.json @@ -3,66 +3,16 @@ "version": "0.2", "language": "en", "words": [ - "Styleable", - "Usergroup", - "Dispatchable", - "MPIMs", - "mpim", - "datetimepicker", - "Placeholdable", - "EARLYHINTS", - "nuxt", - "websockets", - "cacheable", - "mrkdwn", - "Springboot", - "tmproj", - "hgignore", - "bzrignore", - "metatype", - "Paramtype", - "Millis", - "maqsam", - "sadd", - "smembers", - "verifyspf", - "gethostname", - "spftxt", - "domainsuffix", - "resmap", - "bestguess", - "squiebras", - "subjectsthum", - "resobj", - "arobj", - "authres", - "bmsi", - "ipaddrs", - "netwrk", - "cnamek", - "timethen", - "ptrnames", - "kitterma", - "dedup", - "mxlookup", - "modlen", - "cnames", - "mxnames", - "braindead", - "stripdot", - "slodipvh", - "existant", - "domainmatch", - "cidrmatch", - "permerrors", - "mechs", "ABNF", "addrs", "adresses", "africas", "africastalking", + "alanturing", "alexjoverm", "alist", + "aliyun", + "amannn", "antd", "anthonybaxter", "anticon", @@ -77,12 +27,14 @@ "autobuild", "autodocs", "autogeneration", + "automerge", "Automount", "autopipelining", "autorun", "AWSCLIV", "AXFR", "axios", + "azcopy", "backoff", "BADALG", "BADKEY", @@ -112,6 +64,8 @@ "bullmq", "bzrignore", "cacheable", + "cafebabe", + "callout", "canonicalization", "canonicalized", "chartjs", @@ -145,17 +99,20 @@ "Consolas", "consts", "containerd", + "cpack", "Craco", "crossorigin", "CSNET", "customdesigned", "custome", "Datetime", + "datetimepicker", "dbaeumer", "dbnavigator", "dbpath", "Deadlinks", "dearmor", + "decompile", "dedup", "deduplicated", "defexps", @@ -165,6 +122,7 @@ "devs", "Dima", "dinfo", + "Dispatchable", "dkimpy", "dnscount", "dnscounttype", @@ -185,15 +143,20 @@ "doublecolon", "dtos", "dummytype", + "EARLYHINTS", + "eazy", "editorconfig", "ehlo", + "elif", "emailjs", "Embeddable", "EMSA", + "endgroup", "enroute", "envalid", "envsetup", "errmsg", + "esbenp", "eslintcache", "esnext", "EVENT", @@ -208,7 +171,11 @@ "externalredis", "Fdfdf", "fieldname", + "fieldtype", + "Fira", + "firestore", "Firetext", + "firsttris", "fjogeleit", "flowwer", "fontawesome", @@ -232,21 +199,28 @@ "GROUPBY", "Gupshup", "Hacksquad", + "hbspt", "headerapikey", + "HeaderNavNew", "healthcheck", + "HEAY", "Hewgill", "HEXTET", "hextets", + "hgcheck", "hgignore", "hmac", "hokify", "hookform", + "hostedtoolcache", "hostnames", "hotmail", "hset", + "hstack", "hubspot", "Hygen", "IAPNS", + "Icann", "Idand", "idempotency", "Idempotency", @@ -288,9 +262,11 @@ "libarary", "libauth", "libspf", + "limitbar", "livemode", "localforage", "localstack", + "localstorage", "LOGDECORATOR", "loglevel", "machdep", @@ -302,11 +278,15 @@ "mailin", "Mailjet", "mailparser", + "Maizzle", "mansagroup", "mantine", "maqsam", + "Markunread", + "marocchino", "martinbeentjes", "maskdata", + "maxage", "maxsize", "maxtimeout", "mechnisms", @@ -322,7 +302,9 @@ "minkey", "mispelled", "MITM", + "MJML", "mkdir", + "mkdocs", "mlen", "moby", "modlen", @@ -331,6 +313,9 @@ "monokai", "monorepository", "mpeltonen", + "mpim", + "MPIMs", + "mrkdwn", "MSTCP", "msteams", "multilines", @@ -341,6 +326,7 @@ "nameserver", "nbits", "nbsp", + "neom", "nestframework", "nestjs", "netblock", @@ -368,12 +354,16 @@ "Novu's", "novuapp", "novuhq", + "novui", "novutest", + "npmignore", + "npmjs", "npmrc", "nrwl", "ntoa", "ntvs", "nunito", + "nuxt", "nvsk", "nwtgck", "NXDOMAIN", @@ -384,21 +374,28 @@ "opcodemap", "opcodestr", "openapi", + "OPENCOLLECTIVE", "opentelemetry", "orcid", "Otel", + "otlp", + "OTLP", "outdir", "Outgoers", + "pandabox", "pandacss", "Paramtype", + "parens", "pepipost", "permerror", "permerrors", "personalizations", "PGID", + "picocolors", "pino", "PKCS", "pkdata", + "Placeholdable", "plivo", "Plivo", "plusplus", @@ -409,20 +406,27 @@ "prefiltering", "prefixlen", "preheader", + "prepopulating", + "prepush", "presigner", "prettierignore", "printjson", "privkey", "Projectkeys", + "protonmail", "pton", "ptrnames", "ptrs", "pubid", "PUID", + "pulumi", "Pushpad", "Pushwoosh", "pychecker", "pydns", + "pyroscope", + "Pyroscope", + "PYROSCOPE", "pyspf", "QCLASS", "qtype", @@ -431,8 +435,10 @@ "quickstarts", "Quickstarts", "quickstartslogo", + "Raleway", "ratelimit", "Ratelimit", + "ravsamhq", "rcode", "rdatatype", "rdtype", @@ -447,6 +453,7 @@ "Relabelings", "releasewards", "RELEASLY", + "relocator", "replayable", "replstate", "reshard", @@ -463,7 +470,9 @@ "righthand", "rimraf", "ringcentral", + "Roboto", "RSASSA", + "runtimes", "ryver", "sadd", "sandboxed", @@ -471,11 +480,13 @@ "scopsy", "Scriptable", "Segoe", + "sema", "sendchamp", "Sendchamp", "sendgrid", "Sendinblue", "sendsms", + "servername", "SERVFAIL", "sess", "settext", @@ -524,6 +535,7 @@ "stripdot", "Strobl", "stroeder", + "Styleable", "subjectsthum", "sublist", "subnetmask", @@ -561,8 +573,10 @@ "TRANSID", "transpiled", "trunc", + "tsbuildinfo", "TSIG", "tspan", + "tsup", "TTFB", "Twilio", "Twillio", @@ -571,6 +585,7 @@ "typeof", "Unfetch", "Unpromoted", + "unpublish", "unsub", "untracked", "upsert", @@ -579,79 +594,20 @@ "Upstash", "usecase", "USECASE", - "Vonage", - "Krakend", - "ratelimit", - "Ratelimit", - "stdev", - "Stdev", - "openapi", - "headerapikey", - "fieldname", - "isend", - "Otel", - "opentelemetry", - "INITDB", - "isend", - "Idand", - "hubspot", - "hokify", - "containerd", - "snapshotter", - "moby", - "ghaction", - "livemode", - "spyon", - "restapi", - "ringcentral", - "reshard", - "WaitList", - "userid", - "endgroup", - "aliyun", - "azcopy", - "cpack", - "pulumi", - "hostedtoolcache", - "OTLP", - "otlp", - "hostedtoolcache", - "pyroscope", - "HEAY", - "Pyroscope", - "PYROSCOPE", "usecases", - "hbspt", - "prepopulating", - "Vonage", - "fieldtype", - "usecase", - "zulip", + "Usergroup", + "userid", "uuidv", + "vercel", + "verifyspf", "Vonage", - "HeaderNavNew", - "reshard", - "hstack", - "runtimes", - "cafebabe", - "Markunread", - "Fira", - "Roboto", - "Raleway", - "Icann", - "limitbar", - "eazy", - "Maizzle", - "MJML", - "localstorage", - "elif", + "WaitList", + "webfontloader", + "webpush", + "Webpush", "websockets", "whatsapp", "whatsappbusiness", - "parens", - "ravsamhq", - "Webpush", - "webpush", "xkeysib", "firestore", "firsttris", @@ -666,7 +622,17 @@ "automerge", "novui", "tsup", - "pandabox" + "pandabox", + "hljs", + "MINTLIFY", + "mintlify", + "marocchino", + "amannn", + "pandabox", + "zulip", + "zwnj", + "prepush", + "xcodebuild" ], "flagWords": [], "patterns": [ @@ -742,6 +708,7 @@ "packages/application-generic/src/.env.test", "packages/notification-center/src/i18n/languages/**", "packages/notification-center/rollup.config.mjs", + "packages/js/tsup.config.ts", "apps/widget/public/iframeResizer.contentWindow.js", ".eslintrc.js", ".vscode/settings.json", diff --git a/.deepsource.toml b/.deepsource.toml index ab36940c56f5..fd68cc70ac75 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -4,7 +4,6 @@ test_patterns = [ "apps/api/src/**/**/*.spec.ts", "apps/api/e2e/**/*.e2e.ts", "apps/api/src/**/*.e2e.ts", - "apps/web/cypress/**/*.spec.ts", "apps/widget/cypress/**/*.spec.ts" ] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 29fd18878e88..9c3e8e3ee110 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,7 +21,7 @@ // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [4200, 3000, 27017], - "onCreateCommand": "npm run setup:project --exclude=@novu/api,@novu/worker,@novu/web,@novu/widget", + "onCreateCommand": "npm run setup:project -- --exclude=@novu/api,@novu/worker,@novu/web,@novu/widget", // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node", diff --git a/.github/actions/checkout-submodules/action.yml b/.github/actions/checkout-submodules/action.yml index 4130e6dcbd71..ebd47b6aa45d 100644 --- a/.github/actions/checkout-submodules/action.yml +++ b/.github/actions/checkout-submodules/action.yml @@ -20,7 +20,7 @@ runs: steps: - name: Checkout submodule if: ${{ inputs.enabled == 'true' }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ inputs.submodule_token }} repository: novuhq/packages-enterprise diff --git a/.github/actions/setup-project/action.yml b/.github/actions/setup-project/action.yml index b4dd1a898848..dc13839cae31 100644 --- a/.github/actions/setup-project/action.yml +++ b/.github/actions/setup-project/action.yml @@ -3,10 +3,6 @@ name: Setup Novu Monorepo description: Sets up the whole monorepo and install dependencies inputs: - cypress_version: - description: 'The version of cypress to install' - required: false - default: '' slim: description: 'Should only install dependencies and checkout code' required: false @@ -27,9 +23,11 @@ runs: - name: ⚙️ Setup kernel for react native, increase watchers shell: bash run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - - uses: pnpm/action-setup@v2 - - uses: actions/setup-node@v3 + - name: Install pnpm + uses: pnpm/action-setup@v3 + + - uses: actions/setup-node@v4 name: ⚙️ Setup Node Version with: node-version: '20.8.1' @@ -45,27 +43,10 @@ runs: with: mongodb-version: 4.2.8 - - name: Cache Cypress Binary For Version - id: cache-cypress-binary-version - if: ${{ inputs.cypress_version != '' }} - uses: actions/cache@v3 - with: - path: /home/runner/.cache/Cypress/${{ inputs.cypress_version }} - key: cypress-binary-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ inputs.cypress_version }} - - - name: Cache Cypress Binary - id: cache-cypress-binary - if: ${{ inputs.cypress_version == '' }} - uses: actions/cache@v3 - with: - path: /home/runner/.cache/Cypress - key: cypress-binary-${{ hashFiles('**/pnpm-lock.yaml') }} - - name: 🛟 Install dependencies shell: bash run: pnpm install --frozen-lockfile - - name: Link submodules shell: bash if: ${{ inputs.submodules == 'true' }} diff --git a/.github/actions/start-localstack/action.yml b/.github/actions/start-localstack/action.yml index e7a2a8a32beb..440ead969fb7 100644 --- a/.github/actions/start-localstack/action.yml +++ b/.github/actions/start-localstack/action.yml @@ -16,7 +16,7 @@ runs: AWS_EC2_METADATA_DISABLED: true working-directory: docker/local run: | - docker-compose -f docker-compose.localstack.yml up -d + docker-compose -f docker-compose.e2e.yml up -d sleep 10 max_retry=30 counter=0 @@ -28,5 +28,5 @@ runs: echo "Trying again. Try #$counter" ((counter++)) done - docker-compose -f docker-compose.localstack.yml logs --tail="all" + docker-compose -f docker-compose.e2e.yml logs --tail="all" aws --endpoint-url=http://127.0.0.1:4566 --cli-connect-timeout 600 s3 mb s3://novu-test diff --git a/.github/workflows/cloud-deploy.yml b/.github/workflows/cloud-deploy.yml new file mode 100644 index 000000000000..f9d847ac31f7 --- /dev/null +++ b/.github/workflows/cloud-deploy.yml @@ -0,0 +1,105 @@ +name: Release Prod +on: + workflow_dispatch: + push: + branches: + - prod + +jobs: + pre-release: + runs-on: ubuntu-latest + steps: + + - name: Start Deployment Notification + uses: actions/slack@master + with: + channel: '#eng-feed-deployments' + message: 'Release has started' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + release-api: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-api.yml + + release-embed: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-embed.yml + + release-web: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-web.yml + + release-web-components: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-web-components.yml + + release-webhook: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-webhook.yml + + release-widget: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-widget.yml + + release-worker: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-worker.yml + + release-ws: + needs: [pre-release] + runs-on: ubuntu-latest + steps: + - uses: ./.github/workflows/cloud-deploy/cloud-deploy-ws.yml + + post-release: + if: ${{ always() }} + needs: [ + release-api, + release-embed, + release-web, + release-web-components, + release-webhook, + release-widget, + release-worker, + release-ws + ] + runs-on: ubuntu-latest + steps: + - name: Deploy API Documentation + uses: fjogeleit/http-request-action@v1 + with: + url: ${{ secrets.API_DOCS_BUILD_WEBHOOK }} + method: 'POST' + + - name: Start Deployment Notification + if: ${{ failure() }} # This step runs only if any of the previous jobs fail. + uses: actions/slack@master + with: + channel: '#eng-feed-deployments' + message: 'Release has failed' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + - name: Start Deployment Notification + if: ${{ success() }} # This step runs only if all the previous jobs succeed. + uses: actions/slack@master + with: + channel: '#eng-feed-deployments' + message: 'Release has finished' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/cloud-deploy/cloud-deploy-api.yml b/.github/workflows/cloud-deploy/cloud-deploy-api.yml new file mode 100644 index 000000000000..79e6a446705b --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-api.yml @@ -0,0 +1,136 @@ +name: Deploy PROD API + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + workflow_call: + +jobs: + + build_prod_image: + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: Production + strategy: + matrix: + name: [ 'novu/api-ee' ] + outputs: + docker_image: ${{ steps.build-image.outputs.IMAGE }} + permissions: + contents: read + packages: write + deployments: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + submodules: ${{ contains (matrix.name,'-ee') }} + token: ${{ secrets.SUBMODULES_TOKEN }} + - uses: ./.github/actions/setup-project + with: + submodules: ${{ contains (matrix.name,'-ee') }} + + - name: build api + run: pnpm build:api + + - uses: crazy-max/ghaction-setup-docker@v2 + with: + version: v24.0.6 + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Set Up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: 'image=moby/buildkit:v0.13.1' + + - name: Prepare + shell: bash + run: | + service=${{ matrix.name }} + echo "SERVICE_NAME=$(basename "${service//-/-}")" >> $GITHUB_ENV + + - name: Set Bull MQ Env variable for EE + if: contains(matrix.name, 'ee') + shell: bash + run: | + echo "BULL_MQ_PRO_NPM_TOKEN=${{ secrets.BULL_MQ_PRO_NPM_TOKEN }}" >> $GITHUB_ENV + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + REGISTRY_OWNER: novuhq + DOCKER_NAME: ${{matrix.name}} + IMAGE_TAG: ${{ github.sha }} + GH_ACTOR: ${{ github.actor }} + GH_PASSWORD: ${{ secrets.GH_PACKAGES }} + DOCKER_BUILD_ARGUMENTS: > + --cache-from type=registry,ref=ghcr.io/novuhq/cache:build-cache-${{ env.SERVICE_NAME }}-prod + --cache-to type=registry,ref=ghcr.io/novuhq/cache:build-cache-${{ env.SERVICE_NAME }}-prod,mode=max + --platform=linux/amd64 + --output=type=image,name=ghcr.io/novuhq/${{ matrix.name }},push-by-digest=true,name-canonical=true + run: | + echo $GH_PASSWORD | docker login ghcr.io -u $GH_ACTOR --password-stdin + cd apps/api && pnpm --silent --workspace-root pnpm-context -- apps/api/Dockerfile | BULL_MQ_PRO_NPM_TOKEN=${BULL_MQ_PRO_NPM_TOKEN} docker buildx build --secret id=BULL_MQ_PRO_NPM_TOKEN --build-arg PACKAGE_PATH=apps/api - -t novu-api --load $DOCKER_BUILD_ARGUMENTS + docker tag novu-api ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:latest + docker tag novu-api ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:prod + docker tag novu-api ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + + docker run --network=host --name api -dit --env NODE_ENV=test ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + docker run --network=host appropriate/curl --retry 10 --retry-delay 5 --retry-connrefused http://127.0.0.1:1337/v1/health-check | grep 'ok' + + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:prod + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:latest + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + echo "IMAGE=ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG" >> $GITHUB_OUTPUT + + deploy_prod_api_eu: + needs: build_prod_image + uses: ./.github/workflows/reusable-app-service-deploy.yml + secrets: inherit + with: + environment: Production + service_name: api + terraform_workspace: novu-prod-eu + docker_image: ghcr.io/novuhq/novu/api-ee:${{ github.sha }} + + deploy_prod_api_us: + needs: + - build_prod_image + uses: ./.github/workflows/reusable-app-service-deploy.yml + secrets: inherit + with: + environment: Production + service_name: api + terraform_workspace: novu-prod + docker_image: ghcr.io/novuhq/novu/api-ee:${{ github.sha }} + deploy_sentry_release: true + sentry_project: api + + newrelic: + runs-on: ubuntu-latest + name: New Relic Deploy + needs: deploy_prod_api_us + environment: Production + steps: + # This step builds a var with the release tag value to use later + - name: Set Release Version from Tag + run: echo "RELEASE_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV + # This step creates a new Change Tracking Marker + - name: New Relic Application Deployment Marker + uses: newrelic/deployment-marker-action@v2.3.0 + with: + region: EU + apiKey: ${{ secrets.NEW_RELIC_API_KEY }} + guid: "MzgxMjQwOHxBUE18QVBQTElDQVRJT058NDk3NzA2ODk2" + version: "${{ env.RELEASE_VERSION }}" + user: "${{ github.actor }}" diff --git a/.github/workflows/cloud-deploy/cloud-deploy-embed.yml b/.github/workflows/cloud-deploy/cloud-deploy-embed.yml new file mode 100644 index 000000000000..9dec8db91175 --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-embed.yml @@ -0,0 +1,28 @@ +name: Deploy PROD EMBED + +on: + workflow_call: + +jobs: + deploy_embed_eu: + uses: ./.github/workflows/reusable-embed-deploy.yml + with: + environment: Production + widget_url: https://eu.widget.novu.co + netlify_deploy_message: Production deployment + netlify_alias: prod + netlify_gh_env: Production + netlify_site_id: 0c830b50-df83-480b-ba36-a7f3176efcc8 + secrets: inherit + + deploy_embed_us: + uses: ./.github/workflows/reusable-embed-deploy.yml + with: + environment: Production + widget_url: https://widget.novu.co + netlify_deploy_message: Production deployment + netlify_alias: prod + netlify_gh_env: Production + netlify_site_id: 0689c015-fca0-4940-a26d-3e33f561bc48 + secrets: inherit + diff --git a/.github/workflows/cloud-deploy/cloud-deploy-web-components.yml b/.github/workflows/cloud-deploy/cloud-deploy-web-components.yml new file mode 100644 index 000000000000..e70078d55299 --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-web-components.yml @@ -0,0 +1,40 @@ +name: Deploy PROD Notification Center Web Component + +on: + workflow_call: + +jobs: + build: + uses: ./.github/workflows/reusable-notification-center.yml + + deploy_web_component: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 15 + if: "!contains(github.event.head_commit.message, 'ci skip')" + environment: Production + + steps: + - uses: actions/checkout@v4 + + - name: Download the artifact + uses: actions/download-artifact@v4 + with: + name: notification-center-web-component + path: packages/notification-center/dist/umd + + - name: Deploy Notification Center Web Component to PROD + uses: scopsy/actions-netlify@develop + with: + publish-dir: packages/notification-center/dist/umd + github-token: ${{ secrets.GITHUB_TOKEN }} + deploy-message: prod + production-deploy: true + alias: prod + github-deployment-environment: Production + github-deployment-description: Notification Center Web Component Deployment + netlify-config-path: packages/notification-center/netlify.toml + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: 468165e2-bd64-4f33-9fd9-4b93ef8a0be0 + timeout-minutes: 1 diff --git a/.github/workflows/cloud-deploy/cloud-deploy-web.yml b/.github/workflows/cloud-deploy/cloud-deploy-web.yml new file mode 100644 index 000000000000..1ae021f9f28a --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-web.yml @@ -0,0 +1,45 @@ + +name: Deploy PROD WEB + +on: + workflow_call: + +jobs: + + deploy_web_eu: + uses: ./.github/workflows/reusable-web-deploy.yml + with: + environment: Production + react_app_api_url: https://eu.api.novu.co + react_app_ws_url: https://eu.ws.novu.co + react_app_webhook_url: https://eu.webhook.novu.co + react_app_widget_embed_path: https://eu.embed.novu.co/embed.umd.min.js + react_app_sentry_dsn: https://2b5160da86384949be4cc66679c54e79@o1161119.ingest.sentry.io/6250907 + react_app_environment: production + react_app_mail_server_domain: eu.inbound-mail.novu.co + react_app_hubspot_embed: 44416662 + netlify_deploy_message: Prod deployment + netlify_alias: prod + netlify_gh_env: Production + netlify_site_id: d2e8b860-7016-4202-9256-ebca0f13259a + secrets: inherit + + deploy_web_us: + needs: + - deploy_web_eu + uses: ./.github/workflows/reusable-web-deploy.yml + with: + environment: Production + react_app_api_url: https://api.novu.co + react_app_ws_url: https://ws.novu.co + react_app_webhook_url: https://webhook.novu.co + react_app_widget_embed_path: https://embed.novu.co/embed.umd.min.js + react_app_sentry_dsn: https://2b5160da86384949be4cc66679c54e79@o1161119.ingest.sentry.io/6250907 + react_app_environment: production + react_app_mail_server_domain: inbound-mail.novu.co + react_app_hubspot_embed: 44416662 + netlify_deploy_message: Prod deployment + netlify_alias: prod + netlify_gh_env: Production + netlify_site_id: 8639d8b9-81f9-44c3-b885-585a7fd2b5ff + secrets: inherit diff --git a/.github/workflows/cloud-deploy/cloud-deploy-webhook.yml b/.github/workflows/cloud-deploy/cloud-deploy-webhook.yml new file mode 100644 index 000000000000..9e76df656e31 --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-webhook.yml @@ -0,0 +1,41 @@ +name: Deploy PROD WEBHOOK + +on: + workflow_call: + +jobs: + + publish_docker_image_webhook: + needs: test_webhook + uses: ./.github/workflows/reusable-docker.yml + with: + environment: Production + package_name: novu/webhook + project_path: apps/webhook + test_port: 1341 + health_check: true + local_tag: novu-webhook + env_tag: prod + secrets: inherit + + deploy_prod_webhook_eu: + needs: + - publish_docker_image_webhook + uses: ./.github/workflows/reusable-app-service-deploy.yml + secrets: inherit + with: + environment: Production + service_name: webhook + terraform_workspace: novu-prod-eu + docker_image: ${{ needs.publish_docker_image_webhook.outputs.docker_image_ee }} + + deploy_prod_webhook_us: + needs: + - publish_docker_image_webhook + uses: ./.github/workflows/reusable-app-service-deploy.yml + secrets: inherit + with: + environment: Production + service_name: webhook + terraform_workspace: novu-prod + docker_image: ${{ needs.publish_docker_image_webhook.outputs.docker_image_ee }} diff --git a/.github/workflows/cloud-deploy/cloud-deploy-widget.yml b/.github/workflows/cloud-deploy/cloud-deploy-widget.yml new file mode 100644 index 000000000000..649b79d22546 --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-widget.yml @@ -0,0 +1,38 @@ +name: Deploy PROD Widget + +on: + workflow_call: + +jobs: + + deploy_widget_eu: + uses: ./.github/workflows/reusable-widget-deploy.yml + with: + environment: Production + react_app_api_url: https://eu.api.novu.co + react_app_ws_url: https://eu.ws.novu.co + react_app_webhook_url: https://eu.webhook.novu.co + react_app_sentry_dsn: https://02189965b1bb4cf8bb4776f417f80b92@o1161119.ingest.sentry.io/625116 + react_app_environment: production + netlify_deploy_message: Prod deployment + netlify_alias: prod + netlify_gh_env: Production + netlify_site_id: 20a64bdd-1934-4284-875f-862410c69a3b + secrets: inherit + + deploy_widget_us: + needs: + - test_widget + uses: ./.github/workflows/reusable-widget-deploy.yml + with: + environment: Production + react_app_api_url: https://api.novu.co + react_app_ws_url: https://ws.novu.co + react_app_webhook_url: https://webhook.novu.co + react_app_sentry_dsn: https://02189965b1bb4cf8bb4776f417f80b92@o1161119.ingest.sentry.io/625116 + react_app_environment: production + netlify_deploy_message: Prod deployment + netlify_alias: prod + netlify_gh_env: Production + netlify_site_id: 6f927fd4-dcb0-4cf3-8c0b-8c5539d0d034 + secrets: inherit diff --git a/.github/workflows/cloud-deploy/cloud-deploy-worker.yml b/.github/workflows/cloud-deploy/cloud-deploy-worker.yml new file mode 100644 index 000000000000..d48ef7306e02 --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-worker.yml @@ -0,0 +1,136 @@ +name: Deploy PROD Worker + +on: + workflow_call: + +jobs: + + build_prod_image: + if: "!contains(github.event.head_commit.message, 'ci skip')" + # The type of runner that the job will run on + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: Production + strategy: + # The order is important for ee to be first, otherwise outputs not work correctly + matrix: + name: [ 'novu/worker-ee' ] + outputs: + docker_image: ${{ steps.build-image.outputs.IMAGE }} + permissions: + contents: read + packages: write + deployments: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + submodules: ${{ contains (matrix.name,'-ee') }} + token: ${{ secrets.SUBMODULES_TOKEN }} + - uses: ./.github/actions/setup-project + with: + submodules: ${{ contains (matrix.name,'-ee') }} + + - name: build worker + run: pnpm build:worker + + - uses: crazy-max/ghaction-setup-docker@v2 + with: + version: v24.0.6 + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Set Up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: 'image=moby/buildkit:v0.13.1' + + - name: Prepare + shell: bash + run: | + service=${{ matrix.name }} + echo "SERVICE_NAME=$(basename "${service//-/-}")" >> $GITHUB_ENV + + - name: Set Bull MQ Env variable for EE + if: contains(matrix.name, 'ee') + shell: bash + run: | + echo "BULL_MQ_PRO_NPM_TOKEN=${{ secrets.BULL_MQ_PRO_NPM_TOKEN }}" >> $GITHUB_ENV + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + REGISTRY_OWNER: novuhq + DOCKER_NAME: ${{matrix.name}} + IMAGE_TAG: ${{ github.sha }} + GH_ACTOR: ${{ github.actor }} + GH_PASSWORD: ${{ secrets.GH_PACKAGES }} + DOCKER_BUILD_ARGUMENTS: > + --cache-from type=registry,ref=ghcr.io/novuhq/cache:build-cache-${{ env.SERVICE_NAME }}-prod + --cache-to type=registry,ref=ghcr.io/novuhq/cache:build-cache-${{ env.SERVICE_NAME }}-prod,mode=max + --platform=linux/amd64 + --output=type=image,name=ghcr.io/novuhq/${{ matrix.name }},push-by-digest=true,name-canonical=true + run: | + echo $GH_PASSWORD | docker login ghcr.io -u $GH_ACTOR --password-stdin + cd apps/worker && pnpm --silent --workspace-root pnpm-context -- apps/worker/Dockerfile | BULL_MQ_PRO_NPM_TOKEN=${BULL_MQ_PRO_NPM_TOKEN} docker buildx build --secret id=BULL_MQ_PRO_NPM_TOKEN --build-arg PACKAGE_PATH=apps/worker - -t novu-worker --load $DOCKER_BUILD_ARGUMENTS + docker tag novu-worker ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:latest + docker tag novu-worker ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:prod + docker tag novu-worker ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + + docker run --network=host --name worker -dit --env NODE_ENV=test ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + docker run --network=host appropriate/curl --retry 10 --retry-delay 5 --retry-connrefused http://127.0.0.1:1342/v1/health-check | grep 'ok' + + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:prod + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:latest + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + echo "IMAGE=ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG" >> $GITHUB_OUTPUT + + deploy_prod_workers_eu: + needs: build_prod_image + uses: ./.github/workflows/reusable-workers-service-deploy.yml + secrets: inherit + with: + environment: Production + terraform_workspace: novu-prod-eu + # This is a workaround to an issue with matrix outputs + docker_image: ghcr.io/novuhq/novu/worker-ee:${{ github.sha }} + + deploy_prod_workers_us: + needs: build_prod_image + uses: ./.github/workflows/reusable-workers-service-deploy.yml + secrets: inherit + with: + environment: Production + terraform_workspace: novu-prod + # This is a workaround to an issue with matrix outputs + docker_image: ghcr.io/novuhq/novu/worker-ee:${{ github.sha }} + deploy_sentry_release: true + sentry_project: worker + + newrelic: + runs-on: ubuntu-latest + name: New Relic Deploy + needs: deploy_prod_workers_us + environment: Production + steps: + # This step builds a var with the release tag value to use later + - name: Set Release Version from Tag + run: echo "RELEASE_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV + # This step creates a new Change Tracking Marker + - name: New Relic Application Deployment Marker + uses: newrelic/deployment-marker-action@v2.3.0 + with: + region: EU + apiKey: ${{ secrets.NEW_RELIC_API_KEY }} + guid: "MzgxMjQwOHxBUE18QVBQTElDQVRJT058NDk3NzA2ODk2" + version: "${{ env.RELEASE_VERSION }}" + user: "${{ github.actor }}" diff --git a/.github/workflows/cloud-deploy/cloud-deploy-ws.yml b/.github/workflows/cloud-deploy/cloud-deploy-ws.yml new file mode 100644 index 000000000000..c897be244288 --- /dev/null +++ b/.github/workflows/cloud-deploy/cloud-deploy-ws.yml @@ -0,0 +1,116 @@ +name: Deploy PROD WS + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + workflow_call: + +jobs: + + # This workflow contains a single job called "build" + build_prod_image: + # The type of runner that the job will run on + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: Production + strategy: + matrix: + name: [ 'novu/ws-ee' ] + outputs: + docker_image: ${{ steps.build-image.outputs.IMAGE }} + steps: + - uses: actions/checkout@v4 + with: + submodules: ${{ contains (matrix.name,'-ee') }} + token: ${{ secrets.SUBMODULES_TOKEN }} + - uses: ./.github/actions/setup-project + with: + submodules: ${{ contains (matrix.name,'-ee') }} + + - name: Set Bull MQ Env variable for EE + if: contains(matrix.name, 'ee') + shell: bash + run: | + echo "BULL_MQ_PRO_NPM_TOKEN=${{ secrets.BULL_MQ_PRO_NPM_TOKEN }}" >> $GITHUB_ENV + + - name: build api + run: pnpm build:ws + + - uses: crazy-max/ghaction-setup-docker@v2 + with: + version: v24.0.6 + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Set Up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: 'image=moby/buildkit:v0.13.1' + + - name: Prepare + shell: bash + run: | + service=${{ matrix.name }} + echo "SERVICE_NAME=$(basename "${service//-/-}")" >> $GITHUB_ENV + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + REGISTRY_OWNER: novuhq + DOCKER_NAME: ${{matrix.name}} + IMAGE_TAG: ${{ github.sha }} + GH_ACTOR: ${{ github.actor }} + GH_PASSWORD: ${{ secrets.GH_PACKAGES }} + DOCKER_BUILD_ARGUMENTS: > + --cache-from type=registry,ref=ghcr.io/novuhq/cache:build-cache-${{ env.SERVICE_NAME }}-prod + --cache-to type=registry,ref=ghcr.io/novuhq/cache:build-cache-${{ env.SERVICE_NAME }}-prod,mode=max + --platform=linux/amd64 + --output=type=image,name=ghcr.io/novuhq/${{ matrix.name }},push-by-digest=true,name-canonical=true + run: | + echo $GH_PASSWORD | docker login ghcr.io -u $GH_ACTOR --password-stdin + BULL_MQ_PRO_NPM_TOKEN=${BULL_MQ_PRO_NPM_TOKEN} docker buildx build --secret id=BULL_MQ_PRO_NPM_TOKEN -t ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG --load -f apps/ws/Dockerfile . $DOCKER_BUILD_ARGUMENTS + docker run --network=host --name api -dit --env NODE_ENV=test ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + docker run --network=host appropriate/curl --retry 10 --retry-delay 5 --retry-connrefused http://127.0.0.1:1340/v1/health-check | grep 'ok' + docker tag ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:prod + docker tag ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:latest + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:prod + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:latest + docker push ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG + echo "IMAGE=ghcr.io/$REGISTRY_OWNER/$DOCKER_NAME:$IMAGE_TAG" >> $GITHUB_OUTPUT + + deploy_prod_ws_eu: + needs: + - build_prod_image + uses: ./.github/workflows/reusable-app-service-deploy.yml + secrets: inherit + with: + environment: Production + service_name: ws + terraform_workspace: novu-prod-eu + # This is a workaround to an issue with matrix outputs + docker_image: ghcr.io/novuhq/novu/ws-ee:${{ github.sha }} + deploy_sentry_release: true + sentry_project: ws + + deploy_prod_ws_us: + needs: + - build_prod_image + uses: ./.github/workflows/reusable-app-service-deploy.yml + secrets: inherit + with: + environment: Production + service_name: ws + terraform_workspace: novu-prod + # This is a workaround to an issue with matrix outputs + docker_image: ghcr.io/novuhq/novu/ws-ee:${{ github.sha }} + deploy_sentry_release: true + sentry_project: ws diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b4292cd564d1..29281d9f972c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -11,7 +11,7 @@ # name: "CodeQL" concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true on: @@ -41,7 +41,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/community-label.yml b/.github/workflows/community-label.yml index 3f9a2172dc90..b57aea56e3a8 100644 --- a/.github/workflows/community-label.yml +++ b/.github/workflows/community-label.yml @@ -7,7 +7,7 @@ on: - '!prod' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true jobs: @@ -15,9 +15,9 @@ jobs: name: Verify runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20.8.1 - name: Install Octokit diff --git a/.github/workflows/community-releases.yml b/.github/workflows/community-releases.yml deleted file mode 100644 index b016b28face3..000000000000 --- a/.github/workflows/community-releases.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Prepare Self-hosted Release - -on: - workflow_dispatch: - inputs: - version: - description: 'The version to tag docker images' - required: true - type: string - -permissions: - contents: write - packages: write - deployments: write - id-token: write - -jobs: - prepare: - runs-on: ubuntu-latest - timeout-minutes: 80 - steps: - - uses: actions/checkout@v4 - - docker_build: - runs-on: ubuntu-latest - needs: prepare - timeout-minutes: 80 - environment: Production - strategy: - fail-fast: false - matrix: - name: ['novu/api','novu/worker','novu/inbound-mail','novu/web','novu/webhook','novu/widget','novu/ws'] - steps: - - uses: actions/checkout@v4 - - - name: Variables - shell: bash - run: | - service=${{ matrix.name }} - echo "SERVICE_NAME=$(basename "${service//-/-}")" >> $GITHUB_ENV - echo "REGISTRY_OWNER=novuhq" >> $GITHUB_ENV - - - name: Install pnpm - uses: pnpm/action-setup@v3 - with: - version: 8.9.0 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.8.1 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - uses: ./.github/actions/setup-qemu - - - name: Login To Registry - shell: bash - env: - GH_ACTOR: ${{ github.actor }} - GH_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - run: | - echo $GH_PASSWORD | docker login ghcr.io -u $GH_ACTOR --password-stdin - - - name: Build Community Docker Image with Buildx, tag, and test - shell: bash - env: - DOCKER_BUILD_ARGUMENTS: > - --cache-from type=registry,ref=ghcr.io/${{ env.REGISTRY_OWNER }}/cache:build-cache-${{ env.SERVICE_NAME }}-community - --cache-to type=registry,ref=ghcr.io/${{ env.REGISTRY_OWNER }}/cache:build-cache-${{ env.SERVICE_NAME }}-community,mode=max - --platform=linux/amd64,linux/arm64 --provenance=false - --output=type=image,name=ghcr.io/${{ env.REGISTRY_OWNER }}/${{ env.SERVICE_NAME }},push-by-digest=true,name-canonical=true - run: | - cd apps/$SERVICE_NAME && pnpm run docker:build - docker images - - - name: Tag and Push docker image - shell: bash - run: | - docker tag novu-$SERVICE_NAME ghcr.io/$REGISTRY_OWNER/$SERVICE_NAME:${{ inputs.version }} - docker push ghcr.io/$REGISTRY_OWNER/$SERVICE_NAME:${{ inputs.version }} - - tag_create: - runs-on: ubuntu-latest - needs: docker_build - timeout-minutes: 80 - steps: - - uses: actions/checkout@v4 - - name: Create tag ${{ inputs.version }} - uses: actions/github-script@v5 - with: - script: | - github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: 'refs/tags/${{ inputs.version }}', - sha: context.sha - }) diff --git a/.github/workflows/conventional-commit.yml b/.github/workflows/conventional-commit.yml new file mode 100644 index 000000000000..047aa847d483 --- /dev/null +++ b/.github/workflows/conventional-commit.yml @@ -0,0 +1,72 @@ +name: "Lint PR title" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + + # Use the following event if you want edit and test setting regarding conventional commits + # pull_request: + # types: + # - opened + # - edited + # - synchronize + +permissions: + pull-requests: write + +jobs: + main: + name: Validate PR titles + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + + - name: Generate scopes + id: generate_scopes + run: | + scopes=$(pnpm m ls --json --depth=-1 | grep "name" | sed -e 's/.*\: \(.*\)/\1/' -e 's/@novu\///g' -e 's/[",]//g') + echo 'SCOPES<> $GITHUB_ENV + echo "$scopes" >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + + - uses: amannn/action-semantic-pull-request@v5 + id: lint_pr_title + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + requireScope: true + scopes: | + ${{ env.SCOPES }} + + + - uses: marocchino/sticky-pull-request-comment@v2 + # When the previous steps fails, the workflow would stop. By adding this + # condition you can continue the execution with the populated error message. + if: always() && (steps.lint_pr_title.outputs.error_message != null) + with: + header: pr-title-lint-error + message: | + Hey there and thank you for opening this pull request! 👋 + + We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. + + Your PR title is: `${{ github.event.pull_request.title }}` + It should be something like: `feat(scope): Add fancy new feature` + + **Details:** + + ${{ steps.lint_pr_title.outputs.error_message }} + + # Delete a previous comment when the issue has been resolved + - if: ${{ steps.lint_pr_title.outputs.error_message == null }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: pr-title-lint-error + delete: true diff --git a/.github/workflows/dev-deploy-api.yml b/.github/workflows/dev-deploy-api.yml index 6ccf1d35f9bc..898c980b55b0 100644 --- a/.github/workflows/dev-deploy-api.yml +++ b/.github/workflows/dev-deploy-api.yml @@ -45,7 +45,7 @@ jobs: matrix: name: ['novu/api-ee', 'novu/api'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} @@ -73,7 +73,7 @@ jobs: - name: Configure AWS credentials if: ${{ contains (matrix.name,'-ee') }} - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -81,7 +81,7 @@ jobs: - name: Terraform setup if: ${{ contains (matrix.name,'-ee') }} - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v3 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} terraform_version: 1.5.5 diff --git a/.github/workflows/dev-deploy-inbound-mail.yml b/.github/workflows/dev-deploy-inbound-mail.yml index c8646e4e099c..2d18fb0b0ae6 100644 --- a/.github/workflows/dev-deploy-inbound-mail.yml +++ b/.github/workflows/dev-deploy-inbound-mail.yml @@ -44,7 +44,7 @@ jobs: name: ['novu/inbound-mail-ee'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - uses: crazy-max/ghaction-setup-docker@v2 @@ -112,14 +112,14 @@ jobs: path: cloud-infra - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-2 - name: Terraform setup - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v3 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} terraform_version: 1.5.5 diff --git a/.github/workflows/dev-deploy-web-component.yml b/.github/workflows/dev-deploy-web-component.yml index 4aa705701089..a691a2a552e8 100644 --- a/.github/workflows/dev-deploy-web-component.yml +++ b/.github/workflows/dev-deploy-web-component.yml @@ -21,10 +21,10 @@ jobs: environment: Development steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download the artifact - uses: actions/download-artifact@4 + uses: actions/download-artifact@v4 with: name: notification-center-web-component path: packages/notification-center/dist/umd diff --git a/.github/workflows/dev-deploy-worker.yml b/.github/workflows/dev-deploy-worker.yml index 8fcfe2ca0ae4..66ac46c64a3e 100644 --- a/.github/workflows/dev-deploy-worker.yml +++ b/.github/workflows/dev-deploy-worker.yml @@ -47,7 +47,7 @@ jobs: matrix: name: ['novu/worker-ee', 'novu/worker'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} diff --git a/.github/workflows/dev-deploy-ws.yml b/.github/workflows/dev-deploy-ws.yml index 84c010fdb3cc..68e20ef01c15 100644 --- a/.github/workflows/dev-deploy-ws.yml +++ b/.github/workflows/dev-deploy-ws.yml @@ -37,7 +37,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} @@ -72,7 +72,7 @@ jobs: echo "SERVICE_NAME=$(basename "${service//-/-}")" >> $GITHUB_ENV - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -86,7 +86,7 @@ jobs: path: cloud-infra - name: Terraform setup - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v3 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} terraform_version: 1.5.5 diff --git a/.github/workflows/issue-label.yml b/.github/workflows/issue-label.yml index 29a5e8f93569..e313538befd2 100644 --- a/.github/workflows/issue-label.yml +++ b/.github/workflows/issue-label.yml @@ -5,7 +5,7 @@ on: types: [opened] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true jobs: @@ -13,9 +13,9 @@ jobs: name: Verify runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20.8.1 - name: Install Octokit diff --git a/.github/workflows/jarvis.yml b/.github/workflows/jarvis.yml index 835512567736..b5144c331b41 100644 --- a/.github/workflows/jarvis.yml +++ b/.github/workflows/jarvis.yml @@ -10,7 +10,7 @@ jobs: permissions: issues: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: get-comment-body run: | ls -la diff --git a/.github/workflows/milestone-assign.yml b/.github/workflows/milestone-assign.yml index 517fef45c761..c6235cb8bd0d 100644 --- a/.github/workflows/milestone-assign.yml +++ b/.github/workflows/milestone-assign.yml @@ -5,7 +5,7 @@ on: types: [submitted] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true jobs: @@ -17,9 +17,9 @@ jobs: pull-requests: write runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20.8.1 - name: Install Octokit diff --git a/.github/workflows/on-pr-change.yml b/.github/workflows/on-pr-change.yml new file mode 100644 index 000000000000..eddcb8fd727c --- /dev/null +++ b/.github/workflows/on-pr-change.yml @@ -0,0 +1,21 @@ +name: Check pull request source branch +on: + pull_request_target: + types: + - opened + - reopened + - synchronize + - edited +jobs: + check-branches: + runs-on: ubuntu-latest + steps: + - name: Check branches + env: + HEAD_REF: ${{ github.head_ref }} + BASE_REF: ${{ github.base_ref }} + run: | + if [ $HEAD_REF != "next" ] && [ $BASE_REF == "prod" ]; then + echo "Merge requests to prod branch are only allowed from next branch." + exit 1 + fi diff --git a/.github/workflows/test.yml b/.github/workflows/on-pr.yml similarity index 79% rename from .github/workflows/test.yml rename to .github/workflows/on-pr.yml index 9d72d21f2fb0..501801671722 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/on-pr.yml @@ -1,6 +1,6 @@ -name: Testing Pipeline +name: Check pull request concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true on: @@ -11,18 +11,42 @@ jobs: dependency-review: name: Dependency review runs-on: ubuntu-latest + environment: Linting steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 spellcheck: name: Spell check runs-on: ubuntu-latest + environment: Linting steps: - - uses: actions/checkout@v3 - - uses: streetsidesoftware/cspell-action@v5 + - uses: actions/checkout@v4 + - name: Run Spell Check + uses: streetsidesoftware/cspell-action@v6 + with: + root: 'apps/web' + files: '**/*' + incremental_files_only: true + + find-flags: + runs-on: ubuntu-latest + name: Find LaunchDarkly feature flags in diff + environment: Linting + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Find flags + uses: launchdarkly/find-code-references-in-pull-request@v2 + id: find-flags + with: + project-key: default + environment-key: production + access-token: ${{ secrets.LD_ACCESS_TOKEN }} + repo-token: ${{ secrets.GITHUB_TOKEN }} get-affected: name: Get Affected Packages @@ -54,7 +78,7 @@ jobs: else echo "branch=main" >> $GITHUB_OUTPUT fi - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: ./.github/actions/setup-project @@ -91,35 +115,15 @@ jobs: echo "test-libs=$(pnpm run get-affected test origin/${{steps.get-base-branch-name.outputs.branch}} libs | tail -n +5)" >> $GITHUB_OUTPUT fi - test_web: - name: Test Web Cypress - needs: [get-affected] -# if: ${{ contains(fromJson(needs.get-affected.outputs.test-cypress), '@novu/web') }} - if: contains(github.event.pull_request.labels.*.name, 'run-e2e') - uses: ./.github/workflows/reusable-web-e2e.yml - secrets: inherit - with: - ee: true - - test_widget: - name: Test Widget Cypress - needs: [get-affected] - uses: ./.github/workflows/reusable-widget-e2e.yml - with: - ee: true - if: contains(github.event.pull_request.labels.*.name, 'run-e2e') -# if: ${{ contains(fromJson(needs.get-affected.outputs.test-cypress), '@novu/widget') || contains(fromJson(needs.get-affected.outputs.test-unit), '@novu/notification-center') || contains(fromJson(needs.get-affected.outputs.test-unit), '@novu/ws') }} - secrets: inherit - - test_providers: - name: Unit Test Providers + test_unit_providers: + name: Unit test @novu/providers runs-on: ubuntu-latest needs: [get-affected] if: ${{ fromJson(needs.get-affected.outputs.test-providers)[0] }} timeout-minutes: 80 steps: - run: echo '${{ needs.get-affected.outputs.test-providers }}' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project with: slim: 'true' @@ -129,8 +133,8 @@ jobs: parallel: 5 projects: ${{join(fromJson(needs.get-affected.outputs.test-providers), ',')}} - test_packages: - name: Unit Test Packages + test_unit_packages: + name: Unit test @novu public NPM packages (except providers) runs-on: ubuntu-latest needs: [get-affected] if: ${{ fromJson(needs.get-affected.outputs.test-packages)[0] }} @@ -141,20 +145,15 @@ jobs: deployments: write id-token: write steps: - - name: Display Test Packages + - name: Affected packages run: echo '${{ needs.get-affected.outputs.test-packages }}' - - name: Checkout Code - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Setup Project - uses: ./.github/actions/setup-project + - uses: ./.github/actions/setup-project with: slim: 'true' - - name: Setup Redis Cluster - uses: ./.github/actions/setup-redis-cluster - - name: Run Lint, Build, Test uses: mansagroup/nrwl-nx-action@v3 env: @@ -163,39 +162,27 @@ jobs: targets: lint,build,test projects: ${{join(fromJson(needs.get-affected.outputs.test-packages), ',')}} - test_libs: - name: Unit Test Libs + test_unit_libs: + name: Unit test @novu internal packages runs-on: ubuntu-latest needs: [get-affected] if: ${{ fromJson(needs.get-affected.outputs.test-libs)[0] }} timeout-minutes: 80 steps: - - run: echo '${{ needs.get-affected.outputs.test-libs }}' - - uses: actions/checkout@v3 + - name: Affected libs + run: echo '${{ needs.get-affected.outputs.test-libs }}' + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-project - - uses: mansagroup/nrwl-nx-action@v3 + + - name: Run Lint, Build, Test + uses: mansagroup/nrwl-nx-action@v3 with: targets: lint,build,test projects: ${{join(fromJson(needs.get-affected.outputs.test-libs), ',')}} - test_unit_api: - name: Test API - needs: [get-affected] - if: contains(github.event.pull_request.labels.*.name, 'run-e2e') - strategy: - # The order is important for ee to be first, otherwise outputs not work correctly - matrix: - name: ['novu/api-ee', 'novu/api'] - uses: ./.github/workflows/reusable-api-e2e.yml - with: - ee: ${{ contains (matrix.name,'-ee') }} - test-e2e-affected: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/api') }} - test-e2e-ee-affected: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e-ee), '@novu/api') }} - job-name: ${{ matrix.name }} - secrets: inherit - - test_unit: - name: Unit Test + test_unit_services: + name: Unit test backend services runs-on: ubuntu-latest needs: [get-affected] if: ${{ fromJson(needs.get-affected.outputs.test-unit)[0] }} @@ -211,7 +198,7 @@ jobs: id-token: write steps: - run: echo ${{ matrix.projectName }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project with: # Don't run redis and etc... for other unit tests @@ -223,6 +210,22 @@ jobs: targets: lint,build,test projects: ${{matrix.projectName}} + test_e2e_api: + name: E2E test API + needs: [get-affected] + strategy: + # The order is important for ee to be first, otherwise outputs not work correctly + matrix: + name: ['novu/api-ee', 'novu/api'] + uses: ./.github/workflows/reusable-api-e2e.yml + with: + ee: ${{ contains (matrix.name,'-ee') }} + test-e2e-affected: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/api') }} + test-e2e-ee-affected: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e-ee), '@novu/api') }} + job-name: ${{ matrix.name }} + secrets: inherit + + validate_openapi: name: Validate OpenAPI runs-on: ubuntu-latest @@ -235,7 +238,7 @@ jobs: deployments: write id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster - uses: ./.github/actions/run-api @@ -243,3 +246,21 @@ jobs: launch_darkly_sdk_key: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }} - uses: ./.github/actions/validate-openapi + + test_e2e_web: + name: E2E test Web app + needs: [get-affected] + if: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/web') }} + uses: ./.github/workflows/reusable-web-e2e.yml + secrets: inherit + with: + ee: true + + test_e2e_widget: + name: E2E test Widget + needs: [get-affected] + uses: ./.github/workflows/reusable-widget-e2e.yml + with: + ee: true + if: ${{ contains(fromJson(needs.get-affected.outputs.test-cypress), '@novu/widget') || contains(fromJson(needs.get-affected.outputs.test-unit), '@novu/notification-center') || contains(fromJson(needs.get-affected.outputs.test-unit), '@novu/ws') }} + secrets: inherit diff --git a/.github/workflows/prepare-cloud-release.yaml b/.github/workflows/prepare-cloud-release.yaml index 5673715cc4df..e914132a602a 100644 --- a/.github/workflows/prepare-cloud-release.yaml +++ b/.github/workflows/prepare-cloud-release.yaml @@ -17,8 +17,6 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Set output variables id: output-variables @@ -43,7 +41,7 @@ jobs: id: enable-pr-automerge if: ${{ steps.create-pr.outputs.pr_url != '' }} run: | - gh pr merge --auto -r ${{steps.create-pr.outputs.pr_url}} + gh pr merge --auto -s ${{steps.create-pr.outputs.pr_url}} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/prepare-self-hosted-release.yml b/.github/workflows/prepare-self-hosted-release.yml new file mode 100644 index 000000000000..cad150742af0 --- /dev/null +++ b/.github/workflows/prepare-self-hosted-release.yml @@ -0,0 +1,134 @@ +name: Prepare Self-hosted Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: write + packages: write + deployments: write + id-token: write + +jobs: + prepare: + runs-on: ubuntu-latest + timeout-minutes: 60 + outputs: + latest-version: ${{ steps.get_version.outputs.latest-version }} + steps: + - uses: actions/checkout@v4 + - name: Get Current Lerna Release version and comparing with the Git tag + id: get_version + shell: bash + run: | + LATEST_VERSION="v$(jq -r '.version' lerna.json)" + CURRENT_GIT_TAG="${{ github.ref_name }}" + if [ "$LATEST_VERSION" == "$CURRENT_GIT_TAG" ] + then + echo "latest-version=$(jq -r '.version' lerna.json)" >> $GITHUB_OUTPUT + else + echo "::error::The version in the file lerna.json does not match the git tag. The version in lerna.json is $LATEST_VERSION. The git version is $CURRENT_GIT_TAG" + exit 1 + fi + build_docker: + runs-on: ubuntu-latest + needs: prepare + timeout-minutes: 90 + environment: Production + strategy: + fail-fast: false + matrix: + name: ['novu/api','novu/worker','novu/inbound-mail','novu/web','novu/webhook','novu/widget','novu/ws','novu/embed','novu/inbound-mail-ee','novu/worker-ee','novu/api-ee','novu/web-ee','novu/widget-ee','novu/embed-ee','novu/ws-ee','novu/webhook-ee'] + steps: + - name: Git Checkout with Submodules + uses: actions/checkout@v4 + if: contains(matrix.name, '-ee') + with: + submodules: true + token: ${{ secrets.SUBMODULES_TOKEN }} + + - name: Git Checkout without Submodules + if: "!contains(matrix.name, '-ee')" + uses: actions/checkout@v4 + + - name: Variables + shell: bash + run: | + echo "The release version is ${{ needs.prepare.outputs.latest-version }}" + service=${{ matrix.name }} + SERVICE_NAME=$(basename "${service//-/-}") + SERVICE_COMMON_NAME=$(echo "$SERVICE_NAME" | sed 's/-ee$//') + echo "SERVICE_NAME=$SERVICE_NAME" >> $GITHUB_ENV + echo "SERVICE_COMMON_NAME=$SERVICE_COMMON_NAME" >> $GITHUB_ENV + echo "REGISTRY_OWNER=novuhq" >> $GITHUB_ENV + - name: Install pnpm + uses: pnpm/action-setup@v3 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.8.1 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Link submodules + if: contains(matrix.name, '-ee') + run: pnpm symlink:submodules + + - uses: ./.github/actions/free-space + - uses: ./.github/actions/setup-qemu + + - name: Login To Registry + shell: bash + env: + GH_ACTOR: ${{ github.actor }} + GH_PASSWORD: ${{ secrets.GH_PACKAGES }} + run: | + echo $GH_PASSWORD | docker login ghcr.io -u $GH_ACTOR --password-stdin + - name: Set Bull MQ Env variable for EE + if: contains(matrix.name, '-ee') + shell: bash + run: | + echo "BULL_MQ_PRO_NPM_TOKEN=${{ secrets.BULL_MQ_PRO_NPM_TOKEN }}" >> $GITHUB_ENV + - name: Build ${{ env.SERVICE_NAME }} Community Docker Image + shell: bash + env: + DOCKER_BUILD_ARGUMENTS: > + --cache-from type=registry,ref=ghcr.io/${{ env.REGISTRY_OWNER }}/cache:build-cache-${{ env.SERVICE_NAME }}-community + --cache-to type=registry,ref=ghcr.io/${{ env.REGISTRY_OWNER }}/cache:build-cache-${{ env.SERVICE_NAME }}-community,mode=max + --platform=linux/amd64,linux/arm64 --provenance=false + --output=type=image,name=ghcr.io/${{ env.REGISTRY_OWNER }}/${{ env.SERVICE_NAME }},push-by-digest=true,name-canonical=true + run: | + if [ "${{ env.SERVICE_NAME }}" = "embed" ] || [ "${{ env.SERVICE_NAME }}" = "embed-ee" ]; then + cd libs/$SERVICE_COMMON_NAME + else + cd apps/$SERVICE_COMMON_NAME + fi + pnpm run docker:build + docker images + - name: Check for EE files + if: "!contains(matrix.name, '-ee')" + id: check-ee-files + run: | + patterns=( + './node_modules/@novu/ee-**/dist/index.js' + './node_modules/@taskforcesh/bullmq-pro' # Add more patterns as needed + ) + for pattern in "${patterns[@]}"; do + if docker run --rm novu-$SERVICE_COMMON_NAME sh -c "ls $pattern 2>/dev/null"; then + echo "::error::'$pattern' files were detected in ${{ matrix.name }}." + exit 1 + fi + done + echo "No matching EE files found in the Docker image ${{ matrix.name }}" + - name: Tag and Push docker image + shell: bash + run: | + docker tag novu-$SERVICE_COMMON_NAME ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:${{ needs.prepare.outputs.latest-version }} + docker tag novu-$SERVICE_COMMON_NAME ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:latest + docker push ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:${{ needs.prepare.outputs.latest-version }} + docker push ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:latest diff --git a/.github/workflows/prod-deploy-api.yml b/.github/workflows/prod-deploy-api.yml index f7f7d303bc79..37b6031ff9a7 100644 --- a/.github/workflows/prod-deploy-api.yml +++ b/.github/workflows/prod-deploy-api.yml @@ -35,7 +35,7 @@ jobs: deployments: write id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} diff --git a/.github/workflows/prod-deploy-inbound-mail.yml b/.github/workflows/prod-deploy-inbound-mail.yml index e3903fad4bf0..4d47eac18e9b 100644 --- a/.github/workflows/prod-deploy-inbound-mail.yml +++ b/.github/workflows/prod-deploy-inbound-mail.yml @@ -30,7 +30,7 @@ jobs: matrix: name: [ 'novu/inbound-mail-ee', 'novu/inbound-mail' ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - name: build api diff --git a/.github/workflows/prod-deploy-web-component.yml b/.github/workflows/prod-deploy-web-component.yml index 4b8698bbae1c..2ed532e3b932 100644 --- a/.github/workflows/prod-deploy-web-component.yml +++ b/.github/workflows/prod-deploy-web-component.yml @@ -15,7 +15,7 @@ jobs: environment: Production steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download the artifact uses: actions/download-artifact@v4 diff --git a/.github/workflows/prod-deploy-worker.yml b/.github/workflows/prod-deploy-worker.yml index 42e7899638cd..75663a6266dd 100644 --- a/.github/workflows/prod-deploy-worker.yml +++ b/.github/workflows/prod-deploy-worker.yml @@ -35,7 +35,7 @@ jobs: deployments: write id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} diff --git a/.github/workflows/prod-deploy-ws.yml b/.github/workflows/prod-deploy-ws.yml index f064ef5246e6..f0d78b2832fb 100644 --- a/.github/workflows/prod-deploy-ws.yml +++ b/.github/workflows/prod-deploy-ws.yml @@ -29,7 +29,7 @@ jobs: outputs: docker_image: ${{ steps.build-image.outputs.IMAGE }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} diff --git a/.github/workflows/release-helm.yml b/.github/workflows/release-helm.yml index c777a891c580..250873976c0b 100644 --- a/.github/workflows/release-helm.yml +++ b/.github/workflows/release-helm.yml @@ -9,7 +9,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: stefanprodan/helm-gh-pages@v1.7.0 with: diff --git a/.github/workflows/reusable-api-e2e.yml b/.github/workflows/reusable-api-e2e.yml index 286c76020026..58e8f89f2ce1 100644 --- a/.github/workflows/reusable-api-e2e.yml +++ b/.github/workflows/reusable-api-e2e.yml @@ -55,7 +55,7 @@ jobs: needs: [check_submodule_token] steps: # checkout with submodules if token is provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout with submodules if: ${{ needs.check_submodule_token.outputs.has_token == 'true' && inputs.ee }} with: @@ -63,7 +63,7 @@ jobs: token: ${{ secrets.SUBMODULES_TOKEN }} # else checkout without submodules if the token is not provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout if: ${{ needs.check_submodule_token.outputs.has_token != 'true' || !contains (inputs.job-name,'-ee') }} diff --git a/.github/workflows/reusable-app-service-deploy.yml b/.github/workflows/reusable-app-service-deploy.yml index 771adba4bf5c..3c8fb543c7c1 100644 --- a/.github/workflows/reusable-app-service-deploy.yml +++ b/.github/workflows/reusable-app-service-deploy.yml @@ -45,7 +45,7 @@ jobs: path: cloud-infra - name: Terraform setup - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v3 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} terraform_version: 1.5.5 @@ -68,7 +68,7 @@ jobs: echo "aws_region=$(terraform output -json aws_region | jq -r .)" >> $GITHUB_ENV - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -95,7 +95,7 @@ jobs: cluster: ${{ env.ecs_cluster }} wait-for-service-stability: true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ inputs.deploy_sentry_release }} - name: get-npm-version diff --git a/.github/workflows/reusable-docker.yml b/.github/workflows/reusable-docker.yml index df8419abcde3..977205e0603f 100644 --- a/.github/workflows/reusable-docker.yml +++ b/.github/workflows/reusable-docker.yml @@ -61,7 +61,7 @@ jobs: matrix: name: [ '${{ inputs.package_name }}-ee', '${{ inputs.package_name }}' ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: ${{ contains (matrix.name,'-ee') }} token: ${{ secrets.SUBMODULES_TOKEN }} diff --git a/.github/workflows/reusable-embed-deploy.yml b/.github/workflows/reusable-embed-deploy.yml index fa6d16744b09..a28a2718ef0f 100644 --- a/.github/workflows/reusable-embed-deploy.yml +++ b/.github/workflows/reusable-embed-deploy.yml @@ -36,7 +36,7 @@ jobs: deployments: write id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project with: slim: 'true' diff --git a/.github/workflows/reusable-inbound-mail-e2e.yml b/.github/workflows/reusable-inbound-mail-e2e.yml index 400d6c504c7f..c655735457e6 100644 --- a/.github/workflows/reusable-inbound-mail-e2e.yml +++ b/.github/workflows/reusable-inbound-mail-e2e.yml @@ -34,13 +34,13 @@ jobs: echo "has_token=false" >> $GITHUB_OUTPUT fi # checkout with submodules if token is provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token == 'true' with: submodules: ${{ inputs.ee }} token: ${{ secrets.SUBMODULES_TOKEN }} # else checkout without submodules if the token is not provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token != 'true' - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster diff --git a/.github/workflows/reusable-notification-center.yml b/.github/workflows/reusable-notification-center.yml index ea1ac3aea696..c67ea65c8e46 100644 --- a/.github/workflows/reusable-notification-center.yml +++ b/.github/workflows/reusable-notification-center.yml @@ -7,7 +7,7 @@ jobs: build_test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project diff --git a/.github/workflows/reusable-web-deploy.yml b/.github/workflows/reusable-web-deploy.yml index 99bffdf35560..16d5c8768098 100644 --- a/.github/workflows/reusable-web-deploy.yml +++ b/.github/workflows/reusable-web-deploy.yml @@ -58,7 +58,7 @@ jobs: deployments: write id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout with submodules with: submodules: true diff --git a/.github/workflows/reusable-web-e2e.yml b/.github/workflows/reusable-web-e2e.yml index 2a6d32eb8583..37a3e7ae4fa6 100644 --- a/.github/workflows/reusable-web-e2e.yml +++ b/.github/workflows/reusable-web-e2e.yml @@ -23,18 +23,12 @@ on: jobs: # This workflow contains a single job called "build" e2e_web: - if: "!contains(github.event.head_commit.message, 'ci skip')" - strategy: - # when one test fails, DO NOT cancel the other - # containers, because this will kill Cypress processes - # leaving the Dashboard hanging ... - # https://github.com/cypress-io/github-action/issues/48 fail-fast: false matrix: - # run 5 copies of the current job in parallel - containers: [1, 2, 3, 4, 5] - total: [5] + # run 4 copies of the current job in parallel + containers: [1, 2, 3, 4] + total: [4] # The type of runner that the job will run on runs-on: ubuntu-latest @@ -46,29 +40,31 @@ jobs: deployments: write id-token: write - # Steps represent a sequence of tasks that will be executed as part of the job steps: - - id: setup + - id: determine_run_type + name: Determing community vs enterprise run run: | if ! [[ -z "${{ secrets.SUBMODULES_TOKEN }}" ]]; then - echo "has_token=true" >> $GITHUB_OUTPUT + echo "enterprise_run=true" >> $GITHUB_OUTPUT else - echo "has_token=false" >> $GITHUB_OUTPUT + echo "enterprise_run=false" >> $GITHUB_OUTPUT fi - # checkout with submodules if token is provided - - uses: actions/checkout@v3 - if: steps.setup.outputs.has_token == 'true' + - id: checkout-enterprise-code + name: Checkout enterprise code from the submodule + uses: actions/checkout@v4 + if: steps.determine_run_type.outputs.enterprise_run == 'true' with: - submodules: ${{ inputs.ee }} + submodules: true token: ${{ secrets.SUBMODULES_TOKEN }} - # else checkout without submodules if the token is not provided - - uses: actions/checkout@v3 - if: steps.setup.outputs.has_token != 'true' + + - id: checkout-community-code + name: Checkout community code + uses: actions/checkout@v4 + if: steps.determine_run_type.outputs.enterprise_run != 'true' + - uses: ./.github/actions/setup-project - id: setup-project with: - cypress: true submodules: true - uses: mansagroup/nrwl-nx-action@v3 @@ -85,63 +81,35 @@ jobs: cypress_github_oauth_client_id: ${{ secrets.CYPRESS_GITHUB_OAUTH_CLIENT_ID }} cypress_github_oauth_client_secret: ${{ secrets.CYPRESS_GITHUB_OAUTH_CLIENT_SECRET }} launch_darkly_sdk_key: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }} - ci_ee_test: ${{ steps.setup.outputs.has_token }} + ci_ee_test: ${{ steps.determine_run_type.outputs.enterprise_run }} - - name: Start Client + - name: Start WS + run: | + cd apps/ws && pnpm start:test & + + - name: Start Novu web app working-directory: apps/web env: REACT_APP_API_URL: http://127.0.0.1:1336 REACT_APP_WS_URL: http://127.0.0.1:1340 REACT_APP_WEBHOOK_URL: http://127.0.0.1:1341 - REACT_APP_LAUNCH_DARKLY_CLIENT_SIDE_ID: ${{ secrets.TEST_LAUNCH_DARKLY_CLIENT_SIDE_ID }} + # Disable LaunchDarkly client-side SDK in the test environment to reduce E2E flakiness + REACT_APP_LAUNCH_DARKLY_CLIENT_SIDE_ID: '' + NOVU_ENTERPRISE: ${{ steps.determine_run_type.outputs.enterprise_run }} run: pnpm start:static:build & - - name: Start WS - run: | - cd apps/ws && pnpm start:test & - - name: Wait on Services run: wait-on --timeout=180000 http://127.0.0.1:1340/v1/health-check http://127.0.0.1:4200/ - # run cypress install only when cache was not hit - - name: Cypress install - if: steps.setup-project.outputs.cypress_cache_hit != 'true' + - name: Install Playwright working-directory: apps/web - run: pnpm cypress install + run: pnpm test:e2e:install - - uses: browser-actions/setup-chrome@latest - with: - chrome-version: "stable" - - run: | - echo "BROWSER_PATH=$(which chrome)" >> $GITHUB_ENV - - - name: Cypress run e2e - uses: cypress-io/github-action@v6 - env: - NODE_ENV: test - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_WEB_KEY }} - CYPRESS_GITHUB_USER_EMAIL: ${{ secrets.CYPRESS_GITHUB_USER_EMAIL }} - CYPRESS_GITHUB_USER_PASSWORD: ${{ secrets.CYPRESS_GITHUB_USER_PASSWORD }} - CYPRESS_IS_CI: true - with: - working-directory: apps/web - browser: "${{ env.BROWSER_PATH }}" - record: true - parallel: true - install: false - config-file: cypress.config.ts - spec: | - cypress/tests/**/*.spec.ts - ${{ steps.setup.outputs.has_token == 'true' && inputs.ee && 'cypress/tests/**/*.spec-ee.ts' }} - - - name: Playwright Install - working-directory: apps/web - run: pnpm playwright:install - - - name: Run Playwright tests + - name: Run E2E tests working-directory: apps/web - run: pnpm playwright:test --shard=${{ matrix.containers }}/${{ matrix.total }} + env: + NOVU_ENTERPRISE: ${{ steps.determine_run_type.outputs.enterprise_run }} + run: pnpm test:e2e --shard=${{ matrix.containers }}/${{ matrix.total }} - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} @@ -150,13 +118,6 @@ jobs: path: apps/web/blob-report retention-days: 1 - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: cypress-screenshots-${{ matrix.containers }} - path: apps/web/cypress/screenshots - retention-days: 30 - merge-reports: # Merge reports after playwright-tests, even if some shards have failed if: ${{ !cancelled() }} @@ -190,53 +151,3 @@ jobs: if: failure() with: slackWebhookURL: ${{ secrets.SLACK_WEBHOOK_URL_ENG_FEED_GITHUB }} - - component_web: - if: "!contains(github.event.head_commit.message, 'ci skip')" - - # The type of runner that the job will run on - runs-on: ubuntu-latest - timeout-minutes: 80 - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 - - - uses: ./.github/actions/setup-project - id: setup-project - - # Runs a single command using the runners shell - - name: Build - run: CI='' pnpm build:web --skip-nx-cache - - # run cypress install only when cache was not hit - - name: Cypress install - if: steps.setup-project.outputs.cypress_cache_hit != 'true' - working-directory: apps/web - run: pnpm cypress install - - - name: Run Component tests 🧪 - uses: cypress-io/github-action@v6 - env: - NODE_ENV: test - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_WEB_KEY }} - NODE_OPTIONS: --max_old_space_size=4096 - with: - browser: chrome - working-directory: apps/web - install: false - component: true - - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: cypress-screenshots - path: apps/web/cypress/screenshots - - - name: Send Slack notifications - uses: ./.github/actions/slack-notify-on-failure - if: ${{ failure() || needs.e2e_web.result == 'failure' }} - with: - slackWebhookURL: ${{ secrets.SLACK_WEBHOOK_URL_ENG_FEED_GITHUB }} diff --git a/.github/workflows/reusable-webhook-e2e.yml b/.github/workflows/reusable-webhook-e2e.yml index fff41335a50d..48d7a1238009 100644 --- a/.github/workflows/reusable-webhook-e2e.yml +++ b/.github/workflows/reusable-webhook-e2e.yml @@ -14,7 +14,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project diff --git a/.github/workflows/reusable-widget-deploy.yml b/.github/workflows/reusable-widget-deploy.yml index d3b8425216b9..d4d1b4295b6d 100644 --- a/.github/workflows/reusable-widget-deploy.yml +++ b/.github/workflows/reusable-widget-deploy.yml @@ -49,7 +49,7 @@ jobs: deployments: write id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project with: slim: 'true' diff --git a/.github/workflows/reusable-widget-e2e.yml b/.github/workflows/reusable-widget-e2e.yml index 9377c068543a..07f3ed0ad211 100644 --- a/.github/workflows/reusable-widget-e2e.yml +++ b/.github/workflows/reusable-widget-e2e.yml @@ -34,13 +34,13 @@ jobs: echo "has_token=false" >> $GITHUB_OUTPUT fi # checkout with submodules if token is provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token == 'true' with: submodules: ${{ inputs.ee }} token: ${{ secrets.SUBMODULES_TOKEN }} # else checkout without submodules if the token is not provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token != 'true' - uses: ./.github/actions/setup-project diff --git a/.github/workflows/reusable-worker-e2e.yml b/.github/workflows/reusable-worker-e2e.yml index 3959bd789ad1..5407fc9570cb 100644 --- a/.github/workflows/reusable-worker-e2e.yml +++ b/.github/workflows/reusable-worker-e2e.yml @@ -34,13 +34,13 @@ jobs: echo "has_token=false" >> $GITHUB_OUTPUT fi # checkout with submodules if token is provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token == 'true' with: submodules: ${{ inputs.ee }} token: ${{ secrets.SUBMODULES_TOKEN }} # else checkout without submodules if the token is not provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token != 'true' - uses: ./.github/actions/setup-project diff --git a/.github/workflows/reusable-workers-service-deploy.yml b/.github/workflows/reusable-workers-service-deploy.yml index 28e52ef80545..50447f00959b 100644 --- a/.github/workflows/reusable-workers-service-deploy.yml +++ b/.github/workflows/reusable-workers-service-deploy.yml @@ -46,7 +46,7 @@ jobs: path: cloud-infra - name: Terraform setup - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v3 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} terraform_version: 1.5.5 @@ -82,7 +82,7 @@ jobs: - run: echo "Deploying ${{ matrix.name }} to ${{ inputs.terraform_workspace }} And Docker Tag ${{ inputs.docker_image }}" - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -109,7 +109,7 @@ jobs: cluster: ${{ needs.infrastructure_data.outputs.ecs_cluster }} wait-for-service-stability: true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ inputs.deploy_sentry_release }} - name: get-npm-version diff --git a/.github/workflows/reusable-ws-e2e.yml b/.github/workflows/reusable-ws-e2e.yml index 40ca3a1afa1e..ea99d61fb445 100644 --- a/.github/workflows/reusable-ws-e2e.yml +++ b/.github/workflows/reusable-ws-e2e.yml @@ -34,13 +34,13 @@ jobs: echo "has_token=false" >> $GITHUB_OUTPUT fi # checkout with submodules if token is provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token == 'true' with: submodules: ${{ inputs.ee }} token: ${{ secrets.SUBMODULES_TOKEN }} # else checkout without submodules if the token is not provided - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: steps.setup.outputs.has_token != 'true' - uses: ./.github/actions/setup-project diff --git a/.github/workflows/rollback.yml b/.github/workflows/rollback.yml index 44c7dec494a0..a6414c5ef4e4 100644 --- a/.github/workflows/rollback.yml +++ b/.github/workflows/rollback.yml @@ -85,7 +85,7 @@ jobs: path: cloud-infra - name: Terraform setup - uses: hashicorp/setup-terraform@v1 + uses: hashicorp/setup-terraform@v3 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} terraform_version: 1.5.5 diff --git a/.github/workflows/scheduled_e2e.yml b/.github/workflows/scheduled_e2e.yml index 8277dd21f896..f0705cf9f539 100644 --- a/.github/workflows/scheduled_e2e.yml +++ b/.github/workflows/scheduled_e2e.yml @@ -18,7 +18,7 @@ env: jobs: test_web: - name: Test Web Cypress + name: Test Web Playwright uses: ./.github/workflows/reusable-web-e2e.yml secrets: inherit with: diff --git a/.github/workflows/tag-images.yml b/.github/workflows/tag-images.yml index 9ea6f242c0b2..daf317f65b84 100644 --- a/.github/workflows/tag-images.yml +++ b/.github/workflows/tag-images.yml @@ -21,10 +21,10 @@ jobs: deployments: write steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup kernel for react native, increase watchers run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: '20.8.1' diff --git a/.gitignore b/.gitignore index dcac0b322f05..b2cd8ee01904 100644 --- a/.gitignore +++ b/.gitignore @@ -97,7 +97,7 @@ fabric.properties # Module directory .terraform/ -.env +**/.env docker/.env .vercel nx-cloud.env diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 9977b434367c..56dd86b95ce2 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -1,4 +1,4 @@ FROM gitpod/workspace-mongodb RUN sudo apt-get update && sudo apt-get install -y redis-server && sudo rm -rf /var/lib/apt/lists/* -RUN npm install -g pnpm@8.9.0 +RUN npm install -g pnpm@9.1.4 diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index 0bd658f49625..000000000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx --no-install commitlint --edit "$1" diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 2ff8622f1722..000000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -package.json \ No newline at end of file diff --git a/.idea/nx-angular-config.xml b/.idea/nx-angular-config.xml index 977a418d1fe9..f7b1f29c249d 100644 --- a/.idea/nx-angular-config.xml +++ b/.idea/nx-angular-config.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/.idea/runConfigurations/API___TEST.xml b/.idea/runConfigurations/API___TEST.xml index ec00f60a7732..a0f7ea61f56b 100644 --- a/.idea/runConfigurations/API___TEST.xml +++ b/.idea/runConfigurations/API___TEST.xml @@ -6,7 +6,9 @@ - - diff --git a/apps/web/cypress/support/component.ts b/apps/web/cypress/support/component.ts deleted file mode 100644 index 69e1fa9661a8..000000000000 --- a/apps/web/cypress/support/component.ts +++ /dev/null @@ -1,39 +0,0 @@ -// *********************************************************** -// This example support/component.ts is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') - -import { mount } from 'cypress/react'; - -// Augment the Cypress namespace to include type definitions for -// your custom command. -// Alternatively, can be defined in cypress/support/component.d.ts -// with a at the top of your spec. -declare global { - namespace Cypress { - interface Chainable { - mount: typeof mount; - } - } -} - -Cypress.Commands.add('mount', mount); - -// Example use: -// cy.mount() diff --git a/apps/web/cypress/support/e2e.ts b/apps/web/cypress/support/e2e.ts deleted file mode 100644 index fe65b09d5fb3..000000000000 --- a/apps/web/cypress/support/e2e.ts +++ /dev/null @@ -1,53 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** -// load the global Cypress types -/// - -import 'cypress-localstorage-commands'; - -// Import commands.js using ES2015 syntax: -import './commands'; - -import 'cypress-network-idle'; -// Alternatively you can use CommonJS syntax: -// require('./commands') - -const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; -Cypress.on('uncaught:exception', (err) => { - /* returning false here prevents Cypress from failing the test */ - if (resizeObserverLoopErrRe.test(err.message)) { - return false; - } -}); - -/** - * Cypress has known issues with testing clipboard (i.e. copy) functionality, so this attempts to circumvent that. - * Ref: https://github.com/cypress-io/cypress/issues/2752#issuecomment-1039285381 - */ -Cypress.on('window:before:load', (win) => { - let copyText; - - if (!win.navigator.clipboard) { - // @ts-ignore - win.navigator.clipboard = { - __proto__: {}, - }; - } - - // @ts-ignore - win.navigator.clipboard.__proto__.writeText = (text) => (copyText = text); - // @ts-ignore - win.navigator.clipboard.__proto__.readText = () => copyText; -}); diff --git a/apps/web/cypress/tests/activities-page.spec.ts b/apps/web/cypress/tests/activities-page.spec.ts deleted file mode 100644 index 8a5887e5ce2c..000000000000 --- a/apps/web/cypress/tests/activities-page.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { JobStatusEnum } from '@novu/shared'; - -describe('Activity Feed Screen', function () { - beforeEach(function () { - cy.initializeSession() - .as('session') - .then((session: any) => { - return cy.task('createNotifications', { - identifier: session.templates[0].triggers[0].identifier, - token: session.token, - count: 25, - organizationId: session.organization._id, - environmentId: session.environment._id, - templateId: session.templates[0]._id, - }); - }); - }); - - it('should display notification for activity', function () { - cy.visit('/activities'); - cy.getByTestId('activities-table') - .find('button') - .first() - .getByTestId('row-template-name') - .contains(this.session.templates[0].name); - - cy.getByTestId('activities-table').find('button').first().getByTestId('in_app-step').should('exist'); - cy.getByTestId('activities-table').find('button').first().getByTestId('email-step').should('exist'); - cy.getByTestId('activities-table').find('button').first().getByTestId('subscriber-id').should('exist'); - }); - - it('should show errors and warning', function () { - cy.intercept(/.*notifications\?page.*/, (r) => { - r.continue((res) => { - if (!res.body?.data) return; - res.body.data[0].jobs[0].status = JobStatusEnum.FAILED; - res.send({ body: res.body }); - }); - }); - cy.visit('/activities'); - - cy.waitForNetworkIdle(500); - cy.getByTestId('activities-table') - .find('button') - .first() - .getByTestId('status-badge-item') - .eq(0) - .should('have.css', 'color') - .and('eq', 'rgb(229, 69, 69)'); - }); - - it('should filter by email channel', function () { - cy.visit('/activities'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('email-step').should('have.length', 10); - cy.getByTestId('activities-filter').click(); - cy.get('.mantine-MultiSelect-item').contains('SMS').click(); - cy.getByTestId('submit-filters').click(); - cy.getByTestId('email-step').should('have.length', 0); - }); - - it('should show the clear filters button when template is selected', function () { - cy.visit('/activities'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('email-step').should('have.length', 10); - - cy.getByTestId('templates-filter').click({ force: true }); - cy.get('.mantine-MultiSelect-item').contains(this.session.templates[0].name).click({ force: true }); - - cy.getByTestId('activities-table') - .find('button') - .first() - .getByTestId('row-template-name') - .contains(this.session.templates[0].name); - - cy.getByTestId('clear-filters').should('exist'); - cy.getByTestId('clear-filters').click({ force: true }); - - cy.getByTestId('templates-filter').find('.mantine-Text-root').should('not.exist'); - cy.getByTestId('email-step').should('have.length', 10); - }); - - it('should clear all filters', function () { - cy.visit('/activities'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('email-step').should('have.length', 10); - - cy.getByTestId('activities-filter').click(); - cy.get('.mantine-MultiSelect-item').contains('Email').click(); - cy.getByTestId('templates-filter').click(); - cy.get('.mantine-MultiSelect-item').contains(this.session.templates[0].name).click(); - cy.getByTestId('transactionId-filter').type('test'); - cy.getByTestId('subscriberId-filter').type('test'); - - cy.getByTestId('clear-filters').should('exist'); - cy.getByTestId('clear-filters').click(); - - cy.getByTestId('activities-filter').find('.mantine-Text-root').should('not.exist'); - cy.getByTestId('templates-filter').find('.mantine-Text-root').should('not.exist'); - cy.getByTestId('transactionId-filter').should('not.have.value'); - cy.getByTestId('subscriberId-filter').should('not.have.value'); - - cy.getByTestId('email-step').should('have.length', 10); - }); -}); diff --git a/apps/web/cypress/tests/activity-graph.spec.ts b/apps/web/cypress/tests/activity-graph.spec.ts deleted file mode 100644 index e9a2aa20d707..000000000000 --- a/apps/web/cypress/tests/activity-graph.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The tests from this file were moved to the corresponding Playwright file apps/web/tests/activity-graph.spec.ts. - * @deprecated - */ -describe.skip('Activity page', function () { - beforeEach(function () { - // @ts-expect-error - cy.initializeSession() - .as('session') - .then((session: any) => { - return cy.task('createNotifications', { - identifier: session.templates[0].triggers[0].identifier, - token: session.token, - count: 25, - organizationId: session.organization._id, - environmentId: session.environment._id, - }); - }); - }); - - it('should display email available for connection', function () { - cy.visit('/activities'); - cy.location('pathname').should('equal', '/activities'); - - // @ts-expect-error - cy.getByTestId('activity-stats-weekly-sent').contains('25'); - }); -}); diff --git a/apps/web/cypress/tests/auth.spec.ts b/apps/web/cypress/tests/auth.spec.ts deleted file mode 100644 index d6a0962a781b..000000000000 --- a/apps/web/cypress/tests/auth.spec.ts +++ /dev/null @@ -1,252 +0,0 @@ -import * as capitalize from 'lodash.capitalize'; -import { FeatureFlagsKeysEnum, JobTitleEnum, jobTitleToLabelMapper } from '@novu/shared'; - -describe('User Sign-up and Login', function () { - beforeEach(function () { - cy.mockFeatureFlags({ [FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED]: false }); - }); - describe('Sign up', function () { - beforeEach(function () { - cy.clearDatabase(); - cy.seedDatabase(); - }); - - it('should allow a visitor to sign-up, login, and logout', function () { - cy.intercept('**/organization/**/switch').as('appSwitch'); - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/signup'); - }); - cy.getByTestId('fullName').type('Test User'); - cy.getByTestId('email').type('example@example.com'); - cy.getByTestId('password').type('usEr_password_123!'); - cy.getByTestId('accept-cb').click({ force: true }); - - cy.getByTestId('submitButton').click(); - - cy.location('pathname').should('equal', '/auth/application'); - cy.getByTestId('questionnaire-job-title').click(); - cy.get('.mantine-Select-item').contains(jobTitleToLabelMapper[JobTitleEnum.PRODUCT_MANAGER]).click(); - cy.getByTestId('questionnaire-company-name').type('Company Name'); - cy.getByTestId('check-box-container-multi_channel').trigger('mouseover').click(); - - cy.getByTestId('submit-btn').click(); - - cy.location('pathname').should('equal', '/get-started'); - }); - - it('should show account already exists when signing up with already registered mail', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/signup'); - }); - cy.getByTestId('fullName').type('Test User'); - cy.getByTestId('email').type('test-user-1@example.com'); - cy.getByTestId('password').type('usEr_password_123!'); - cy.getByTestId('accept-cb').click({ force: true }); - cy.getByTestId('submitButton').click(); - cy.get('.mantine-TextInput-error').contains('An account with this email already exists'); - }); - - it('should show invalid email error when signing up with invalid email', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/signup'); - }); - cy.getByTestId('fullName').type('Test User'); - cy.getByTestId('email').type('test-user-1@example.c'); - cy.getByTestId('password').type('usEr_password_123!'); - cy.getByTestId('accept-cb').click({ force: true }); - cy.getByTestId('submitButton').click(); - cy.get('.mantine-TextInput-error').contains('Please provide a valid email'); - }); - - it.skip('should allow to sign-up with GitHub, logout, and login', function () { - const isCI = Cypress.env('IS_CI'); - if (!isCI) return; - - cy.intercept('**/organization/**/switch').as('appSwitch'); - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/signup'); - }); - - cy.loginWithGitHub(); - - cy.location('pathname').should('equal', '/auth/application'); - cy.getByTestId('questionnaire-company-name').type('Organization Name'); - cy.getByTestId('submit-btn').click(); - - cy.location('pathname').should('equal', '/quickstart'); - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-organization-name').contains(capitalize('Organization Name'.split(' ')[0])); - cy.getByTestId('header-dropdown-username').contains('Johnny Depp'); - - cy.getByTestId('logout-button').click(); - cy.getByTestId('github-button').click(); - - cy.location('pathname').should('equal', '/workflows'); - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-username').contains('Johnny Depp'); - }); - - it.skip('should allow to sign-up, logout, and login with GitHub using same email address', function () { - const isCI = Cypress.env('IS_CI'); - if (!isCI) return; - - const gitHubUserEmail = Cypress.env('GITHUB_USER_EMAIL'); - - cy.intercept('**/organization/**/switch').as('appSwitch'); - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/signup'); - }); - cy.getByTestId('fullName').type('Test User'); - cy.getByTestId('email').type(gitHubUserEmail); - cy.getByTestId('password').type('usEr_password_123!'); - cy.getByTestId('accept-cb').click({ force: true }); - cy.getByTestId('submitButton').click(); - - cy.location('pathname').should('equal', '/auth/application'); - cy.getByTestId('questionnaire-company-name').type('Organization Name'); - cy.getByTestId('submit-btn').click(); - - cy.location('pathname').should('equal', '/quickstart'); - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-username').contains('Test User'); - cy.getByTestId('logout-button').click(); - - cy.location('pathname').should('equal', '/auth/login'); - cy.loginWithGitHub(); - - cy.location('pathname').should('equal', '/workflows'); - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-username').contains('Test User'); - }); - }); - - describe('Password Reset', function () { - before(() => { - cy.clearDatabase(); - cy.seedDatabase(); - cy.initializeSession().as('session'); - }); - - it('should request a password reset flow', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/reset/request'); - }); - cy.getByTestId('email').type(this.session.user.email); - cy.getByTestId('submit-btn').click(); - cy.getByTestId('success-screen-reset').should('be.visible'); - - cy.task('passwordResetToken', this.session.user._id).then((token) => { - cy.visit('/auth/reset/' + token); - }); - - // unfortunately there seems to be a timing issue in in which inputs are disabled - cy.wait(500); - cy.getByTestId('password').type('A123e3e3e3!'); - cy.getByTestId('password-repeat').focus().type('A123e3e3e3!'); - - cy.getByTestId('submit-btn').click(); - }); - }); - - describe('Login', function () { - beforeEach(() => { - cy.clearDatabase(); - cy.seedDatabase(); - }); - - it('should redirect to the dashboard page when a token exists in query', function () { - cy.initializeSession({ disableLocalStorage: true }).then((session) => { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/login?token=' + session.token); - }); - cy.location('pathname').should('equal', '/workflows'); - }); - }); - - it('should be redirect login with no auth', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/'); - }); - cy.location('pathname').should('equal', '/auth/login'); - }); - - it('should successfully login the user', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/login'); - }); - - cy.getByTestId('email').type('test-user-1@example.com'); - cy.getByTestId('password').type('123qwe!@#'); - cy.getByTestId('submit-btn').click(); - cy.location('pathname').should('equal', '/workflows'); - }); - - it('should show incorrect email or password error when authenticating with bad credentials', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/login'); - }); - - cy.getByTestId('email').type('test-user-1@example.com'); - cy.getByTestId('password').type('123456'); - cy.getByTestId('submit-btn').click(); - cy.get('.mantine-TextInput-error').contains('Incorrect email or password provided'); - }); - - it('should show invalid email error when authenticating with invalid email', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/login'); - }); - - cy.getByTestId('email').type('test-user-1@example.c'); - cy.getByTestId('password').type('123456'); - cy.getByTestId('submit-btn').click(); - cy.get('.mantine-TextInput-error').contains('Please provide a valid email'); - }); - - it('should show incorrect email or password error when authenticating with non-existing email', function () { - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/login'); - }); - - cy.getByTestId('email').type('test-user-1@example.de'); - cy.getByTestId('password').type('123456'); - cy.getByTestId('submit-btn').click(); - cy.get('.mantine-TextInput-error').contains('Incorrect email or password provided'); - }); - }); - - describe('Logout', function () { - beforeEach(function () { - cy.clearDatabase(); - cy.seedDatabase(); - }); - - it('should logout user when auth token is expired', function () { - // login the user - cy.waitLoadFeatureFlags(() => { - cy.visit('/auth/login'); - }); - - cy.getByTestId('email').type('test-user-1@example.com'); - cy.getByTestId('password').type('123qwe!@#'); - cy.getByTestId('submit-btn').click(); - - cy.waitLoadFeatureFlags(); - cy.waitForNetworkIdle(500); - cy.location('pathname').should('equal', '/workflows'); - - // setting current time in future, to simulate expired token - const ONE_MINUTE = 1000 * 60; // adding 1 minute to be sure that token is expired - const THIRTY_DAYS = ONE_MINUTE * 60 * 24 * 30; // iat - exp = 30 days - const date = new Date(Date.now() + THIRTY_DAYS + ONE_MINUTE); - cy.clock(date); - - cy.getByTestId('side-nav-subscribers-link').click(); - - // checking if token is removed from local storage - cy.getLocalStorage('auth_token').should('be.null'); - // checking if user is redirected to login page - cy.location('pathname').should('equal', '/auth/login'); - }); - }); -}); diff --git a/apps/web/cypress/tests/billing/annual-subscription.spec-ee.ts b/apps/web/cypress/tests/billing/annual-subscription.spec-ee.ts deleted file mode 100644 index 8e42a8074938..000000000000 --- a/apps/web/cypress/tests/billing/annual-subscription.spec-ee.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** cspell:disable */ -describe('Billing - Annual subscription', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: null, - trialEnd: null, - hasPaymentMethod: false, - status: 'active', - }, - }).as('getSubscription'); - }); - - it('should display monthly in modal as default', function () { - cy.intercept('POST', '**/billing/checkout', { - data: { - clientSecret: 'seti_1Mm8s8LkdIwHu7ix0OXBfTRG_secret_NXDICkPqPeiBTAFqWmkbff09lRmSVXe', - }, - }).as('checkout'); - - cy.visit('/settings/billing'); - - cy.getByTestId('plan-business-upgrade').click(); - cy.wait(['@checkout']); - - cy.getByTestId('billing-interval-control-monthly') - .last() - .parent() - .should('have.class', 'mantine-SegmentedControl-labelActive'); - - cy.getByTestId('modal-monthly-pricing').should('exist'); - }); - - it('should display annually if it is selected before open modal', function () { - cy.intercept('POST', '**/billing/checkout', { - data: { - clientSecret: 'seti_1Mm8s8LkdIwHu7ix0OXBfTRG_secret_NXDICkPqPeiBTAFqWmkbff09lRmSVXe', - }, - }).as('checkout'); - - cy.visit('/settings/billing'); - - cy.getByTestId('billing-interval-control-annually').click(); - - cy.getByTestId('plan-business-upgrade').click(); - cy.wait(['@checkout']); - - cy.getByTestId('billing-interval-control-annually') - .last() - .parent() - .should('have.class', 'mantine-SegmentedControl-labelActive'); - - cy.getByTestId('modal-anually-pricing').should('exist'); - }); - - it('should display billing page with billing interval control', function () { - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'free', - }, - ]; - return response; - }); - }).as('organizations'); - - cy.visit('/settings/billing'); - - cy.getByTestId('billing-interval-control').should('exist'); - cy.getByTestId('billing-interval-price').should('have.text', '$250 month package / billed monthly'); - cy.getByTestId('billing-interval-control-annually').click(); - cy.getByTestId('billing-interval-control-annually') - .parent() - .should('have.class', 'mantine-SegmentedControl-labelActive'); - cy.getByTestId('billing-interval-price').should( - 'have.text', - `$${(2700).toLocaleString()} year package / billed annually` - ); - }); -}); diff --git a/apps/web/cypress/tests/billing/billing.spec-ee.ts b/apps/web/cypress/tests/billing/billing.spec-ee.ts deleted file mode 100644 index 891cda7dda04..000000000000 --- a/apps/web/cypress/tests/billing/billing.spec-ee.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { addDays, subDays, startOfDay, endOfDay } from 'date-fns'; - -describe('Billing', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should display billing page', function () { - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: null, - trialEnd: null, - hasPaymentMethod: false, - status: 'active', - }, - }).as('getSubscription'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - - cy.getByTestId('plan-title').should('have.text', 'Plans'); - }); - - it('should display free trial widget', function () { - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = response.body.data.map((org) => { - return { - ...org, - apiServiceLevel: 'business', - }; - }); - return response; - }); - }).as('organizations'); - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: startOfDay(new Date()), - trialEnd: addDays(endOfDay(new Date()), 30), - hasPaymentMethod: false, - status: 'trialing', - }, - }).as('getSubscription'); - - cy.visit('/workflows'); - - cy.wait(['@getSubscription', '@organizations']); - cy.getByTestId('free-trial-widget-text').should('have.text', '30 days left on your free trial'); - cy.getByTestId('free-trial-widget-button').should('have.text', 'Upgrade'); - cy.getByTestId('free-trial-widget-progress').find('.mantine-Progress-bar').should('have.css', 'width', '0px'); - }); - - it('should display free trial widget after 10 days', function () { - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'business', - }, - ]; - return response; - }); - }).as('organizations'); - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: subDays(startOfDay(new Date()), 10), - trialEnd: addDays(endOfDay(new Date()), 20), - hasPaymentMethod: false, - status: 'trialing', - }, - }).as('getSubscription'); - - cy.visit('/workflows'); - - cy.wait(['@getSubscription', '@organizations']); - cy.getByTestId('free-trial-widget-text').should('have.text', '20 days left on your free trial'); - cy.getByTestId('free-trial-widget-button').should('have.text', 'Upgrade'); - cy.getByTestId('free-trial-widget-progress') - .find('.mantine-Progress-bar') - .should('have.css', 'background-color', 'rgb(77, 153, 128)'); - }); - - it('should display free trial widget after 20 days', function () { - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'business', - }, - ]; - return response; - }); - }).as('organizations'); - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: subDays(startOfDay(new Date()), 20), - trialEnd: addDays(endOfDay(new Date()), 10), - hasPaymentMethod: false, - status: 'trialing', - }, - }).as('getSubscription'); - - cy.visit('/workflows'); - - cy.wait(['@getSubscription', '@organizations']); - cy.getByTestId('free-trial-widget-text').should('have.text', '10 days left on your free trial'); - cy.getByTestId('free-trial-widget-button').should('have.text', 'Upgrade'); - cy.getByTestId('free-trial-widget-progress') - .find('.mantine-Progress-bar') - .should('have.css', 'background-color', 'rgb(253, 224, 68)'); - cy.getByTestId('free-trial-banner').should('exist'); - cy.getByTestId('free-trial-banner-upgrade').should('have.text', 'Upgrade'); - cy.getByTestId('free-trial-banner-contact-sales').should('have.text', 'Contact sales'); - }); - - it('should not display free trial widget after 30 days', function () { - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: null, - trialEnd: null, - hasPaymentMethod: false, - status: 'active', - }, - }).as('getSubscription'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - cy.getByTestId('free-trial-widget-text').should('not.exist'); - cy.getByTestId('free-trial-plan-widget').should('not.exist'); - }); - - it('should display free trail info on billing page', function () { - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: startOfDay(new Date()), - trialEnd: addDays(endOfDay(new Date()), 30), - hasPaymentMethod: false, - status: 'trialing', - }, - }).as('getSubscription'); - - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'business', - }, - ]; - return response; - }); - }).as('organizations'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - - cy.getByTestId('plan-title').should('have.text', 'Plans'); - cy.getByTestId('free-trial-plan-widget').should('have.text', '30 days left on your trial'); - cy.getByTestId('plan-business-current').should('exist'); - cy.getByTestId('plan-business-add-payment').should('exist'); - }); - - it('should be able to manage subscription', function () { - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: null, - trialEnd: null, - hasPaymentMethod: true, - status: 'active', - }, - }).as('getSubscription'); - - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'business', - }, - ]; - return response; - }); - }).as('organizations'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - - cy.getByTestId('plan-title').should('have.text', 'Plans'); - cy.getByTestId('plan-business-current').should('exist'); - cy.getByTestId('plan-business-manage').should('exist'); - }); - - it('should be able to upgrade from free', function () { - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: null, - trialEnd: null, - hasPaymentMethod: null, - status: 'active', - }, - }).as('getSubscription'); - - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'free', - }, - ]; - return response; - }); - }).as('organizations'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - - cy.getByTestId('plan-title').should('have.text', 'Plans'); - cy.getByTestId('plan-free-current').should('exist'); - cy.getByTestId('plan-business-upgrade').should('exist'); - }); - - it('should display billing page', function () { - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: subDays(startOfDay(new Date()), 20), - trialEnd: addDays(endOfDay(new Date()), 10), - hasPaymentMethod: false, - status: 'trialing', - }, - }).as('getSubscription'); - - cy.intercept('GET', '**/v1/organizations', (request) => { - request.reply((response) => { - if (!response.body.data) { - return response; - } - - response.body['data'] = [ - { - ...response.body.data[0], - apiServiceLevel: 'business', - }, - ]; - return response; - }); - }).as('organizations'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - - cy.getByTestId('plan-title').should('have.text', 'Plans'); - cy.getByTestId('plan-business-current').should('exist'); - cy.getByTestId('plan-business-add-payment').should('exist'); - cy.getByTestId('free-trial-plan-widget').should('have.text', '10 days left on your trial'); - cy.getByTestId('free-trial-widget-text').should('have.text', '10 days left on your free trial'); - cy.getByTestId('free-trial-banner').should('exist'); - - cy.intercept('GET', '**/v1/billing/subscription', { - data: { - trialStart: startOfDay(new Date()), - trialEnd: addDays(endOfDay(new Date()), 30), - hasPaymentMethod: true, - status: 'active', - }, - }).as('getSubscription'); - - cy.visit('/settings/billing'); - - cy.wait(['@getSubscription']); - cy.getByTestId('free-trial-plan-widget').should('not.exist'); - cy.getByTestId('free-trial-widget-text').should('not.exist'); - cy.getByTestId('free-trial-banner').should('not.exist'); - }); -}); diff --git a/apps/web/cypress/tests/brand.spec.ts b/apps/web/cypress/tests/brand.spec.ts deleted file mode 100644 index bef9df3eebc4..000000000000 --- a/apps/web/cypress/tests/brand.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -describe('Brand Screen', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - cy.visit('/brand'); - cy.intercept('*/organizations/branding').as('updateBrandingSettings'); - }); - - it('should update logo', function () { - cy.intercept('*/storage/upload-url*').as('uploadLogo'); - - cy.fixture('test-logo.png', {}).then((contents) => { - cy.getByTestId('upload-image-button') - .find('input') - .selectFile( - { - contents: Buffer.from(contents), - fileName: 'test-logo.png', - mimeType: 'image/png', - }, - { force: true } - ); - }); - - cy.wait('@uploadLogo'); - - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', '.png'); - - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', this.session.organization._id); - cy.getByTestId('submit-branding-settings').click(); - - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', '.png'); - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', this.session.organization._id); - }); - - it('should change look and feel settings', function () { - cy.getByTestId('color-picker').click({ force: true }); - cy.get('button[aria-label="#BA68C8"]').click(); - cy.getByTestId('color-picker').should('have.value', '#BA68C8'); - cy.getByTestId('color-picker').click({ force: true }); - cy.get('div[aria-valuetext="rgba(185, 103, 199, 1)"]'); - cy.get('body').click(); - - cy.getByTestId('font-family-selector').click({ force: true }); - cy.get('.mantine-Select-dropdown .mantine-Select-item').contains('Lato').click(); - cy.getByTestId('font-family-selector').should('have.value', 'Lato'); - - cy.getByTestId('submit-branding-settings').click({ force: true }); - cy.wait('@updateBrandingSettings'); - - cy.reload(); - cy.getByTestId('color-picker').should('have.value', '#BA68C8'); - cy.getByTestId('font-family-selector').should('have.value', 'Lato'); - }); -}); diff --git a/apps/web/cypress/tests/changes.spec.ts b/apps/web/cypress/tests/changes.spec.ts deleted file mode 100644 index 8c66b5d0427d..000000000000 --- a/apps/web/cypress/tests/changes.spec.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { dragAndDrop, editChannel } from './notification-editor'; -import { goBack } from './notification-editor'; - -describe('Changes Screen', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should display changes to promote ', function () { - createNotification(); - - cy.visit('/changes'); - cy.getByTestId('pending-changes-table').find('tbody tr').should('have.length', 1); - }); - - it.skip('fields should be disabled in Production', function () { - createNotification(); - promoteNotification(); - - switchEnvironment('Production'); - cy.location('pathname').should('equal', '/workflows'); - - cy.getByTestId('create-workflow-btn').get('button').should('be.disabled'); - cy.getByTestId('notifications-template').find('tbody tr').first().click({ force: true }); - }); - - it('should show correct count of pending changes and update real time', function () { - createNotification(); - cy.getByTestId('side-nav-changes-count').contains('1'); - - createNotification(); - cy.getByTestId('side-nav-changes-count').contains('2'); - - promoteNotification(); - cy.getByTestId('side-nav-changes-count').contains('1'); - }); - - it('should show correct type and description of change', function () { - createNotification(); - cy.visit('/changes'); - cy.getByTestId('change-type').contains('Workflow Change'); - cy.getByTestId('change-content').contains('Test Notification Title'); - }); - - it('should show one change for status change and template update', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('title').first().clear().type('Updated Title'); - cy.getByTestId('sidebar-close').click(); - cy.getByTestId('notification-template-submit-btn').click(); - - cy.getByTestId('side-nav-changes-count').contains('1'); - - cy.getByTestId('settings-page').click(); - cy.getByTestId('active-toggle-switch').click({ force: true }); - cy.getByTestId('side-nav-changes-count').contains('1'); - - promoteNotification(); - switchEnvironment('Production'); - cy.location('pathname').should('equal', '/workflows'); - - cy.getByTestId('notifications-template').find('tbody tr').first().click({ force: true }); - - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('title').first().should('have.value', 'Updated Title'); - - cy.getByTestId('active-toggle-switch').get('label').contains('Inactive'); - }); - - it('should show history of changes', function () { - createNotification(); - promoteNotification(); - - cy.getByTestId('pending-changes-table').find('tbody tr').should('not.exist'); - - cy.get('.mantine-Tabs-tabsList').contains('History').click(); - - cy.getByTestId('history-changes-table').find('tbody tr').should('have.length', 1); - cy.getByTestId('promote-btn').should('be.disabled'); - }); - - it.skip('should promote all changes with promote all btn 2', function () { - cy.intercept('**/v1/changes?promoted=false&page=0&limit=10').as('changes'); - cy.intercept('**/v1/changes/bulk/apply').as('bulk-apply'); - cy.intercept('**/notification-templates**').as('notificationTemplates'); - - createNotification(); - cy.waitForNetworkIdle(500); - createNotification(); - cy.waitForNetworkIdle(1000); - - cy.visit('/changes'); - cy.waitForNetworkIdle(500); - cy.wait(['@changes']); - cy.awaitAttachedGetByTestId('pending-changes-table').find('tbody tr').should('have.length', 2); - cy.wait(['@changes']); - cy.intercept('**/v1/changes?promoted=false&page=0&limit=10').as('changes-2'); - cy.awaitAttachedGetByTestId('promote-all-btn').click({ force: true }); - cy.wait(['@bulk-apply']); - cy.wait(['@changes-2']); - cy.wait(['@changes-2']); - - cy.waitForNetworkIdle(700); - cy.awaitAttachedGetByTestId('pending-changes-table').find('tbody tr').should('not.exist'); - }); -}); - -function switchEnvironment(environment: 'Production' | 'Development') { - cy.getByTestId('environment-switch').find(`input[value="${environment}"]`).click({ force: true }); - cy.waitForNetworkIdle(500); -} - -function createNotification() { - cy.intercept('**/notification-groups').as('getNotificationGroups'); - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('name-input').clear().type('Test Notification Title'); - - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('description').clear().type('This is a test description for a test title'); - cy.get('body').click(); - - goBack(); - - dragAndDrop('email'); - cy.waitForNetworkIdle(500); - - editChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('emailSubject').type('this is email subject'); - - goBack(); - cy.getByTestId('notification-template-submit-btn').click(); -} - -function promoteNotification() { - cy.visit('/changes'); - cy.getByTestId('promote-btn').eq(0).click({ force: true }); - cy.waitForNetworkIdle(500); -} diff --git a/apps/web/cypress/tests/digest-playground.spec.ts b/apps/web/cypress/tests/digest-playground.spec.ts deleted file mode 100644 index 5ffff1d51bd6..000000000000 --- a/apps/web/cypress/tests/digest-playground.spec.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * The tests from this file were moved to the corresponding Playwright file apps/web/tests/digest-playground.spec.ts. - * @deprecated - */ -describe.skip('Digest Playground Workflow Page', function () { - beforeEach(function () { - cy.initializeSession({ noTemplates: true }).as('session'); - }); - - it('should have a link to the docs', function () { - cy.intercept('GET', '**/notification-templates**').as('notificationTemplates'); - cy.visit('/get-started'); - - cy.getByTestId('get-started-footer-left-side').click(); - cy.wait('@notificationTemplates'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('try-digest-playground-btn').click(); - - cy.url().should('include', '/digest-playground'); - cy.contains('Digest Workflow Playground'); - - cy.get('a[href^="https://docs.novu.co/workflows/digest"]').contains('Learn more in docs'); - }); - - it('the set up digest workflow should redirect to template edit page', function () { - cy.intercept('GET', '**/notification-templates**').as('notificationTemplates'); - cy.intercept('GET', '**/notification-templates/**').as('getNotificationTemplate'); - cy.visit('/get-started'); - - cy.getByTestId('get-started-footer-left-side').click(); - cy.wait('@notificationTemplates'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('try-digest-playground-btn').click(); - - cy.url().should('include', '/digest-playground'); - cy.contains('Digest Workflow Playground'); - - cy.get('button').contains('Set Up Digest Workflow').click(); - cy.wait('@getNotificationTemplate'); - - cy.url().should('include', '/workflows/edit'); - }); - - it('should show the digest workflow hints', () => { - cy.intercept('GET', '**/notification-templates**').as('notificationTemplates'); - cy.intercept('GET', '**/notification-templates/**').as('getNotificationTemplate'); - cy.visit('/get-started'); - - // click try digest playground - cy.getByTestId('get-started-footer-left-side').click(); - cy.wait('@notificationTemplates'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('try-digest-playground-btn').click(); - - cy.url().should('include', '/digest-playground'); - cy.contains('Digest Workflow Playground'); - - // click set up digest workflow - cy.get('button').contains('Set Up Digest Workflow').click(); - cy.wait('@getNotificationTemplate'); - - // in the template workflow editor - cy.url().should('include', '/workflows/edit'); - - // check the digest hint - cy.getByTestId('digest-workflow-tooltip').contains('Set-up time interval'); - cy.getByTestId('digest-workflow-tooltip').contains( - 'Specify for how long the digest should collect events before sending a digested event to the next step step in the workflow.' - ); - cy.getByTestId('digest-workflow-tooltip-primary-button').contains('Next'); - cy.getByTestId('digest-workflow-tooltip-skip-button').contains('Skip tour'); - cy.getByTestId('digest-workflow-tooltip-dots-navigation').should('be.visible'); - - // check if has digest step - cy.getByTestId('node-digestSelector').should('be.visible'); - // check if digest step settings opened - cy.getByTestId('step-editor-sidebar').should('exist'); - cy.getByTestId('step-editor-sidebar').contains('All events'); - - // click next on hint - cy.getByTestId('digest-workflow-tooltip-primary-button').contains('Next').click(); - cy.waitForNetworkIdle(1000); - - // check the email hint - cy.getByTestId('digest-workflow-tooltip').contains('Set-up email content'); - cy.getByTestId('digest-workflow-tooltip').contains( - 'Use custom HTML or our visual editor to define how the email will look like when sent to the subscriber.' - ); - cy.getByTestId('digest-workflow-tooltip-primary-button').contains('Next'); - cy.getByTestId('digest-workflow-tooltip-skip-button').contains('Skip tour'); - cy.getByTestId('digest-workflow-tooltip-dots-navigation').should('be.visible'); - - // check if email step settings opened - cy.getByTestId('step-editor-sidebar').should('exist'); - cy.getByTestId('step-editor-sidebar').contains('Email'); - - // click next on hint - cy.getByTestId('digest-workflow-tooltip-primary-button').contains('Next').click(); - - // check the email hint - cy.getByTestId('digest-workflow-tooltip').contains('Test your workflow'); - cy.getByTestId('digest-workflow-tooltip').contains( - 'We will trigger the workflow multiple times to represent how it aggregates notifications.' - ); - cy.getByTestId('digest-workflow-tooltip-primary-button').contains('Got it'); - cy.getByTestId('digest-workflow-tooltip-skip-button').should('not.exist'); - cy.getByTestId('digest-workflow-tooltip-dots-navigation').should('be.visible'); - - // the step settings should be hidden - cy.getByTestId('workflow-sidebar').should('exist'); - cy.getByTestId('workflow-sidebar').contains('Trigger'); - - // click got it should hide the hint - cy.getByTestId('digest-workflow-tooltip-primary-button').contains('Got it').click(); - - cy.getByTestId('digest-workflow-tooltip').should('not.exist'); - }); - - it('should hide the digest workflow hints when clicking on skip tour button', () => { - cy.intercept('GET', '**/notification-templates**').as('notificationTemplates'); - cy.intercept('GET', '**/notification-templates/**').as('getNotificationTemplate'); - cy.visit('/get-started'); - - // click try digest playground - cy.getByTestId('get-started-footer-left-side').click(); - cy.wait('@notificationTemplates'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('try-digest-playground-btn').click(); - - cy.url().should('include', '/digest-playground'); - cy.contains('Digest Workflow Playground'); - - // click set up digest workflow - cy.get('button').contains('Set Up Digest Workflow').click(); - cy.wait('@getNotificationTemplate'); - - // in the template workflow editor - cy.url().should('include', '/workflows/edit'); - - // check the digest hint - cy.getByTestId('digest-workflow-tooltip').contains('Set-up time interval'); - cy.getByTestId('digest-workflow-tooltip-skip-button').contains('Skip tour').click(); - - cy.getByTestId('digest-workflow-tooltip').should('not.exist'); - }); - - it('when clicking on the back button from the playground it should redirect to /get-started/preview', function () { - cy.intercept('GET', '**/notification-templates**').as('notificationTemplates'); - cy.intercept('GET', '**/notification-templates/**').as('getNotificationTemplate'); - cy.visit('/get-started'); - - // click try digest playground - cy.getByTestId('get-started-footer-left-side').click(); - cy.wait('@notificationTemplates'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('try-digest-playground-btn').click(); - - cy.url().should('include', '/digest-playground'); - cy.contains('Digest Workflow Playground'); - - cy.contains('Go Back').click(); - - cy.url().should('include', '/get-started/preview'); - }); -}); diff --git a/apps/web/cypress/tests/environment-switch.spec.ts b/apps/web/cypress/tests/environment-switch.spec.ts deleted file mode 100644 index c13164639349..000000000000 --- a/apps/web/cypress/tests/environment-switch.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -describe('Environment Switch Control', function () { - const modes = ['Development', 'Production']; - - beforeEach(function () { - cy.initializeSession().as('session'); - cy.visit('/'); - }); - - it('should display switch when page is loaded', function () { - cy.getByTestId('environment-switch').find('label').contains(modes[0]); - cy.getByTestId('environment-switch').find('label').contains(modes[1]); - }); - - it('should use different jwt token after switches', function () { - const originToken = this.session.token; - - cy.getByTestId('environment-switch') - .find('.mantine-SegmentedControl-controlActive') - .then((dom) => { - if (dom.find('input').prop('value') === modes[0]) { - cy.getByTestId('environment-switch').find(`input[value="${modes[1]}"]`).click({ force: true }); - } else { - cy.getByTestId('environment-switch').find(`input[value="${modes[0]}"]`).click({ force: true }); - } - - cy.getByTestId('environment-switch-loading-overlay') - .should('not.exist') - .then(() => { - cy.task('getSession', {}).then((response: any) => { - expect(response.token).not.to.equal(originToken); - }); - }); - }); - }); - - it('should display loading indicator when switches', function () { - cy.getByTestId('environment-switch').find('.mantine-SegmentedControl-controlActive'); - cy.getByTestId('environment-switch').find(`input[value="${modes[1]}"]`).click({ force: true }); - cy.getByTestId('environment-switch-loading-overlay').should('not.exist'); - }); -}); diff --git a/apps/web/cypress/tests/explore.spec.ts b/apps/web/cypress/tests/explore.spec.ts deleted file mode 100644 index b0e33d1866d3..000000000000 --- a/apps/web/cypress/tests/explore.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -describe('Just launch the app for exploration', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should launch the app', function () { - cy.visit('/'); - cy.location('pathname').should('equal', '/workflows'); - }); -}); diff --git a/apps/web/cypress/tests/get-started-page.spec.ts b/apps/web/cypress/tests/get-started-page.spec.ts deleted file mode 100644 index 7063102aa384..000000000000 --- a/apps/web/cypress/tests/get-started-page.spec.ts +++ /dev/null @@ -1,259 +0,0 @@ -interface ITabLinkInfo { - label: string; - type: 'button' | 'a'; - urlRegex: RegExp; - windowOpenCallIndex?: number; -} - -interface ITabTest { - tabName: string; - tabTitle: string; - numTimelineSteps: number; - linkSteps: ITabLinkInfo[]; -} - -const BASE_ROUTE = '/get-started'; - -const visitTabAndVerifyContent = ({ tabName, tabTitle, numTimelineSteps, linkSteps }: ITabTest) => { - cy.visit(BASE_ROUTE); - cy.window().then((win) => { - cy.stub(win, 'open').as('windowOpen'); - }); - - cy.contains(tabName).as('tab').click(); - - // Check that the clicked tab is now selected - cy.get('button[role="tab"][aria-selected="true"]').contains(tabName).should('exist'); - - // Validate tab content title - cy.contains('h2', tabTitle).should('exist'); - - // There is a docs link - cy.contains('a', 'Learn about') - .should('have.attr', 'href') - .and('match', /docs.novu.co/); - - // Animation is present - cy.get('canvas').should('exist'); - - // Validate timeline steps count by ensuring there is a numbered pill for each step - new Array(numTimelineSteps).fill(0).forEach((_, index) => { - cy.contains('div', index + 1); - }); - - // validate various links, open them, and go back to the tab - linkSteps.forEach(({ urlRegex, label, type, windowOpenCallIndex }) => { - cy.contains(type, label).should('be.visible'); - if (type === 'a') { - cy.contains(type, label).should('have.attr', 'target', '_blank'); - cy.contains(type, label).should('have.attr', 'rel', 'noopener noreferrer'); - cy.contains(type, label).should('have.attr', 'href').should('match', urlRegex); - } else { - const index = windowOpenCallIndex ?? 0; - cy.contains(type, label).click(); - cy.get('@windowOpen') - .its('callCount') - .should('equal', index + 1); - - cy.get('@windowOpen') - .its('args') - .then((args) => { - const newTabUrl = args[index][0]; - const target = args[index][1]; - const rel = args[index][2]; - expect(newTabUrl).to.match(urlRegex); - expect(target).to.equal('_blank'); - expect(rel).to.equal('noreferrer noopener'); - }); - } - }); -}; - -describe.skip('GetStartedPage', () => { - beforeEach(function () { - cy.mockFeatureFlags({ IS_IMPROVED_ONBOARDING_ENABLED: true }); - cy.initializeSession().as('session'); - cy.makeBlueprints(); - }); - - it('should have all tabs and default to in-app', () => { - cy.visit(BASE_ROUTE); - - // check all tabs exist - cy.contains('button[role="tab"]', 'In-app').should('exist'); - cy.contains('button[role="tab"]', 'Multi-channel').should('exist'); - cy.contains('button[role="tab"]', 'Digest').should('exist'); - cy.contains('button[role="tab"]', 'Delay').should('exist'); - cy.contains('button[role="tab"]', 'Translate').should('exist'); - - // Check that In-app is defaulted and selected - cy.get('button[role="tab"][aria-selected="true"]').contains('In-app').should('exist'); - }); - - it('should load the page with a specific tab selected when the appropriate URL search param is passed', () => { - cy.visit(`${BASE_ROUTE}?tab=multi-channel`); - - // Check that In-app is defaulted and selected - cy.get('button[role="tab"][aria-selected="true"]').contains('Multi-channel').should('exist'); - }); - - it('should navigate to the In-app tab, and have the correct content', () => { - visitTabAndVerifyContent({ - tabName: 'In-app', - tabTitle: 'In-app notifications', - numTimelineSteps: 4, - linkSteps: [ - { - label: 'Create In-app provider', - type: 'a', - urlRegex: new RegExp('/integrations/[A-Z0-9]+', 'i'), - }, - { - label: 'Customize', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}/, - windowOpenCallIndex: 0, - }, - { - label: 'Test the trigger', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}\/test-workflow/, - windowOpenCallIndex: 1, - }, - { - label: 'activity feed', - type: 'a', - urlRegex: new RegExp('/activities'), - }, - ], - }); - }); - - it('should navigate to the Multi-channel tab, and have the correct content', () => { - visitTabAndVerifyContent({ - tabName: 'Multi-channel', - tabTitle: 'Multi-channel notifications', - numTimelineSteps: 4, - linkSteps: [ - { - label: 'Integration store', - type: 'a', - urlRegex: new RegExp('/integrations/create'), - }, - { - label: 'Customize', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}/, - windowOpenCallIndex: 0, - }, - { - label: 'Test the trigger', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}\/test-workflow/, - windowOpenCallIndex: 1, - }, - { - label: 'activity feed', - type: 'a', - urlRegex: new RegExp('/activities'), - }, - ], - }); - }); - - it('should navigate to the Digest tab, and have the correct content', () => { - visitTabAndVerifyContent({ - tabName: 'Digest', - tabTitle: 'Digest multiple events', - numTimelineSteps: 5, - linkSteps: [ - { - label: 'Integration store', - type: 'a', - urlRegex: new RegExp('/integrations/create'), - }, - { - label: 'Customize', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}/, - windowOpenCallIndex: 0, - }, - { - label: 'Customize digest node', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}\/digest\/\w{1,}/, - windowOpenCallIndex: 1, - }, - { - label: 'Test the trigger', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}\/test-workflow/, - windowOpenCallIndex: 2, - }, - { - label: 'activity feed', - type: 'a', - urlRegex: new RegExp('/activities'), - }, - ], - }); - }); - - it('should navigate to the Delay tab, and have the correct content', () => { - visitTabAndVerifyContent({ - tabName: 'Delay', - tabTitle: 'Delay step execution', - numTimelineSteps: 5, - linkSteps: [ - { - label: 'Integration store', - type: 'a', - urlRegex: new RegExp('/integrations/create'), - }, - { - label: 'Customize', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}/, - windowOpenCallIndex: 0, - }, - { - label: 'Customize delay', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}\/delay\/\w{1,}/, - windowOpenCallIndex: 1, - }, - { - label: 'Test the trigger', - type: 'button', - urlRegex: /\/workflows\/edit\/\w{1,}\/test-workflow/, - windowOpenCallIndex: 2, - }, - { - label: 'activity feed', - type: 'a', - urlRegex: new RegExp('/activities'), - }, - ], - }); - }); - - it('should navigate to the Translate tab, and have the correct content', () => { - visitTabAndVerifyContent({ - tabName: 'Translate', - tabTitle: 'Translate content', - numTimelineSteps: 4, - linkSteps: [ - { - label: 'Integration store', - type: 'a', - urlRegex: new RegExp('/integrations/create'), - }, - { - label: 'Translations page', - type: 'a', - urlRegex: new RegExp('/translations'), - }, - ], - }); - }); -}); diff --git a/apps/web/cypress/tests/integrations-list-modal.spec.ts b/apps/web/cypress/tests/integrations-list-modal.spec.ts deleted file mode 100644 index fe8c2b4e0d59..000000000000 --- a/apps/web/cypress/tests/integrations-list-modal.spec.ts +++ /dev/null @@ -1,981 +0,0 @@ -// @ts-nocheck -import { - inAppProviders, - emailProviders, - chatProviders, - pushProviders, - smsProviders, - EmailProviderIdEnum, - InAppProviderIdEnum, - ChannelTypeEnum, - SmsProviderIdEnum, -} from '@novu/shared'; - -Cypress.on('window:before:load', (win) => { - win._cypress = { - ...win._cypress, - }; - win.isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; -}); - -/** - * The tests from this file were moved to the corresponding Playwright file apps/web/tests/integrations-list-modal.spec.ts. - * @deprecated - */ -describe.skip('Integrations List Modal', function () { - let session: any; - - beforeEach(function () { - cy.initializeSession() - .then((result) => { - session = result; - }) - .as('session'); - }); - - const navigateToGetStarted = (card = 'channel-card-email') => { - cy.visit('/get-started'); - cy.location('pathname').should('equal', '/get-started'); - cy.getByTestId(card).find('button').contains('Change Provider').click(); - cy.getByTestId('integrations-list-modal').should('be.visible').contains('Integrations Store'); - }; - - const checkTableLoading = () => { - cy.getByTestId('integration-name-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-provider-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-channel-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-environment-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-status-cell-loading').should('have.length', 10).first().should('be.visible'); - }; - - const checkTableRow = ( - { - name, - isFree, - provider, - channel, - environment, - status, - }: { - name: string; - isFree?: boolean; - provider: string; - channel: string; - environment?: string; - status: string; - }, - nth: number - ) => { - cy.getByTestId('integrations-list-table').as('integrations-table'); - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-name-cell') - .should('be.visible') - .contains(name); - - if (isFree) { - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-name-cell') - .should('be.visible') - .contains('Test Provider'); - } - - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-provider-cell') - .should('be.visible') - .contains(provider); - - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-channel-cell') - .should('be.visible') - .contains(channel); - - if (environment) { - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-environment-cell') - .should('be.visible') - .contains(environment); - } - - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-status-cell') - .should('be.visible') - .contains(status); - }; - - const clickOnListRow = (name: string) => { - cy.getByTestId('integrations-list-table').as('integrations-table'); - cy.get('@integrations-table') - .get('tr') - .getByTestId('integration-name-cell') - .should('be.visible') - .contains(name) - .click(); - }; - - it('should show the table loading skeleton and empty state', () => { - cy.intercept('*/integrations', { - data: [], - delay: 3500, - }).as('getIntegrations'); - cy.intercept('*/environments', async () => { - await new Promise((resolve) => setTimeout(resolve, 3500)); - }).as('getEnvironments'); - navigateToGetStarted('channel-card-sms'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible').click(); - - checkTableLoading(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('no-integrations-placeholder').should('be.visible'); - cy.contains('Choose a channel you want to start sending notifications'); - - cy.getByTestId('integration-channel-card-in_app').should('be.enabled').contains('In-App'); - cy.getByTestId('integration-channel-card-email').should('be.enabled').contains('Email'); - cy.getByTestId('integration-channel-card-chat').should('be.enabled').contains('Chat'); - cy.getByTestId('integration-channel-card-push').should('be.enabled').contains('Push'); - cy.getByTestId('integration-channel-card-sms').should('be.enabled').contains('SMS'); - }); - - it('should show the table loading skeleton and then table', function () { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 3500)); - }).as('getIntegrations'); - - navigateToGetStarted('channel-card-sms'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible').click(); - - checkTableLoading(); - - cy.wait('@getIntegrations'); - cy.getByTestId('add-provider').should('be.enabled').contains('Add a provider'); - - checkTableRow( - { - name: 'SendGrid', - provider: 'SendGrid', - channel: 'Email', - environment: 'Development', - status: 'Active', - }, - 0 - ); - checkTableRow( - { - name: 'Twilio', - provider: 'Twilio', - channel: 'SMS', - environment: 'Development', - status: 'Active', - }, - 1 - ); - checkTableRow( - { - name: 'Slack', - provider: 'Slack', - channel: 'Chat', - environment: 'Development', - status: 'Active', - }, - 2 - ); - checkTableRow( - { - name: 'Discord', - provider: 'Discord', - channel: 'Chat', - environment: 'Development', - status: 'Active', - }, - 3 - ); - checkTableRow( - { - name: 'Firebase Cloud Messaging', - provider: 'Firebase Cloud Messaging', - channel: 'Push', - environment: 'Development', - status: 'Active', - }, - 4 - ); - checkTableRow( - { - name: 'Novu In-App', - isFree: false, - provider: 'Novu In-App', - channel: 'In-App', - environment: 'Development', - status: 'Active', - }, - 5 - ); - /* - checkTableRow( - { - name: 'SendGrid', - provider: 'SendGrid', - channel: 'Email', - environment: 'Production', - status: 'Active', - }, - 6 - ); - checkTableRow( - { - name: 'Twilio', - provider: 'Twilio', - channel: 'SMS', - environment: 'Production', - status: 'Active', - }, - 7 - ); - checkTableRow( - { - name: 'Slack', - provider: 'Slack', - channel: 'Chat', - environment: 'Production', - status: 'Active', - }, - 8 - ); - checkTableRow( - { - name: 'Discord', - provider: 'Discord', - channel: 'Chat', - environment: 'Production', - status: 'Active', - }, - 9 - ); - checkTableRow( - { - name: 'Firebase Cloud Messaging', - provider: 'Firebase Cloud Messaging', - channel: 'Push', - environment: 'Production', - status: 'Active', - }, - 10 - ); - checkTableRow( - { - name: 'Novu In-App', - isFree: true, - provider: 'Novu In-App', - channel: 'In-App', - environment: 'Production', - status: 'Active', - }, - 11 - ); - checkTableRow( - { - name: 'Novu Email', - provider: 'Novu Email', - channel: 'Email', - status: 'Disabled', - }, - 12 - ); - checkTableRow( - { - name: 'Novu SMS', - provider: 'Novu SMS', - channel: 'SMS', - status: 'Disabled', - }, - 13 - ); - */ - }); - - it('should show the select provider sidebar', () => { - cy.task('deleteProvider', { - providerId: InAppProviderIdEnum.Novu, - channel: ChannelTypeEnum.IN_APP, - environmentId: session.environment.id, - organizationId: session.organization.id, - }).then(() => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - - cy.getByTestId('select-provider-sidebar').should('be.visible').as('selectProviderSidebar'); - - cy.get('@selectProviderSidebar').getByTestId('sidebar-close').should('be.visible'); - cy.get('@selectProviderSidebar').contains('Select a provider'); - cy.get('@selectProviderSidebar').contains('Select a provider to create instance for a channel'); - cy.get('@selectProviderSidebar') - .find('input[type="search"]') - .should('have.attr', 'placeholder', 'Search a provider...'); - - cy.get('@selectProviderSidebar').find('[role="tablist"]').as('channelTabs'); - cy.get('@channelTabs').find('[data-active="true"]').contains('Email'); - cy.get('@channelTabs').contains('In-App'); - cy.get('@channelTabs').contains('Email'); - cy.get('@channelTabs').contains('Chat'); - cy.get('@channelTabs').contains('Push'); - cy.get('@channelTabs').contains('SMS'); - - cy.getByTestId('providers-group-in_app').contains('In-App').as('inAppGroup'); - cy.getByTestId('providers-group-email').contains('Email').as('emailGroup'); - cy.getByTestId('providers-group-chat').contains('Chat').as('chatGroup'); - cy.getByTestId('providers-group-push').contains('Push').as('pushGroup'); - cy.getByTestId('providers-group-sms').contains('SMS').as('smsGroup'); - - inAppProviders.forEach((provider) => { - cy.get('@inAppGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - emailProviders - .filter((provider) => provider.id !== EmailProviderIdEnum.Novu) - .forEach((provider) => { - cy.get('@emailGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - chatProviders.forEach((provider) => { - cy.get('@chatGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - pushProviders.forEach((provider) => { - cy.get('@pushGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - smsProviders - .filter((provider) => provider.id !== SmsProviderIdEnum.Novu) - .forEach((provider) => { - cy.get('@smsGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - - cy.getByTestId('select-provider-sidebar-cancel').contains('Cancel'); - cy.getByTestId('select-provider-sidebar-next').should('be.disabled').contains('Next'); - }); - }); - - it('should allow for searching', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible').as('selectProviderSidebar'); - - cy.get('@selectProviderSidebar').find('input[type="search"]').type('Mail'); - - cy.getByTestId('select-provider-sidebar').find('[role="tablist"]').as('channelTabs'); - cy.get('@channelTabs').contains('In-App').should('not.be.visible'); - cy.get('@channelTabs').contains('Email').should('be.visible'); - cy.get('@channelTabs').contains('Chat').should('not.be.visible'); - cy.get('@channelTabs').contains('Push').should('not.be.visible'); - cy.get('@channelTabs').contains('SMS').should('not.be.visible'); - - cy.getByTestId('providers-group-email').contains('Email').as('emailGroup'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.Mailgun}`).contains('Mailgun'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.MailerSend}`).contains('MailerSend'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.EmailWebhook}`).contains('Email Webhook'); - cy.getByTestId('select-provider-sidebar-next').should('be.disabled').contains('Next'); - }); - - it('should show empty search results', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible').as('selectProviderSidebar'); - - cy.get('@selectProviderSidebar').find('input[type="search"]').type('safasdfasdfasdfasdfas'); - - cy.getByTestId('providers-group-in_app').should('not.exist'); - cy.getByTestId('providers-group-email').should('not.exist'); - cy.getByTestId('providers-group-chat').should('not.exist'); - cy.getByTestId('providers-group-push').should('not.exist'); - cy.getByTestId('providers-group-sms').should('not.exist'); - - cy.getByTestId('select-provider-no-search-results-img').should('be.visible'); - cy.getByTestId('select-provider-sidebar-next').should('be.disabled').contains('Next'); - }); - - it('should allow selecting a provider', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - }); - cy.getByTestId('selected-provider-name').should('be.visible').contains('Mailjet'); - - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next'); - }); - - it('should allow moving to create sidebar', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.getByTestId('create-provider-instance-sidebar') - .should('be.visible') - .contains('Specify assignment preferences to automatically allocate the provider instance to the Email channel.'); - cy.getByTestId('create-provider-instance-sidebar-back').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - }); - cy.getByTestId('provider-instance-name').should('be.visible').should('have.value', 'Mailjet'); - cy.getByTestId('create-provider-instance-sidebar').contains('Environment'); - cy.getByTestId('create-provider-instance-sidebar').contains('Provider instance executes only for'); - cy.getByTestId('create-provider-instance-sidebar').find('[role="radiogroup"]').as('environmentRadios'); - cy.get('@environmentRadios').find('[data-checked="true"]').contains('Development'); - cy.get('@environmentRadios').contains('Production'); - cy.getByTestId('create-provider-instance-sidebar-cancel').should('not.be.disabled').contains('Cancel'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create'); - }); - - it('should allow moving back from create provider sidebar to select provider sidebar', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.getByTestId('create-provider-instance-sidebar-back').should('be.visible').click(); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - }); - - it('should create a new mailjet integration', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('createIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - cy.getByTestId('create-provider-instance-sidebar-create').should('be.disabled'); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').click(); - - checkTableRow( - { - name: 'Mailjet Integration', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Disabled', - }, - 6 - ); - }); - - it('should update the mailjet integration', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('createIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('provider-instance-channel').should('contain', 'Email'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar').contains('Set up credentials to start sending notifications.'); - cy.getByTestId('update-provider-sidebar').getByTestId('is_active_id').should('have.value', 'false'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-name') - .should('have.value', 'Mailjet Integration'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-identifier') - .should('contain.value', 'mailjet'); - - cy.getByTestId('update-provider-sidebar-update').should('be.disabled'); - cy.getByTestId('provider-instance-name').first().clear().type('Mailjet Integration Updated'); - cy.getByTestId('is_active_id').check({ force: true }); - cy.getByTestId('apiKey').type('fake-api-key'); - cy.getByTestId('secretKey').type('fake-secret-key'); - cy.getByTestId('from').type('info@novu.co'); - cy.getByTestId('senderName').type('Novu'); - cy.getByTestId('update-provider-sidebar-update').should('not.be.disabled').contains('Update').click(); - - cy.get('.mantine-Modal-close').click(); - cy.getByTestId('sidebar-close').click(); - checkTableRow( - { - name: 'Mailjet Integration Updated', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Active', - }, - 6 - ); - }); - - it('should update the mailjet integration from the list', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('createIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible').click(); - - cy.wait('@getIntegrations'); - - clickOnListRow('Mailjet Integration'); - - cy.getByTestId('update-provider-sidebar').contains('Set up credentials to start sending notifications.'); - cy.getByTestId('update-provider-sidebar').getByTestId('is_active_id').should('have.value', 'false'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-name') - .should('have.value', 'Mailjet Integration'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-identifier') - .should('contain.value', 'mailjet'); - - cy.getByTestId('update-provider-sidebar-update').should('be.disabled'); - cy.getByTestId('provider-instance-name').first().clear().type('Mailjet Integration Updated'); - cy.getByTestId('is_active_id').check({ force: true }); - cy.getByTestId('apiKey').type('fake-api-key'); - cy.getByTestId('secretKey').type('fake-secret-key'); - cy.getByTestId('from').type('info@novu.co'); - cy.getByTestId('senderName').type('Novu'); - cy.getByTestId('update-provider-sidebar-update').should('not.be.disabled').click(); - - cy.get('.mantine-Modal-close').click(); - cy.getByTestId('sidebar-close').click(); - checkTableRow( - { - name: 'Mailjet Integration Updated', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Active', - }, - 6 - ); - }); - - it('should allow to delete the mailjet integration', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible').click(); - - cy.wait('@getIntegrations'); - - clickOnListRow('Mailjet Integration'); - - cy.getByTestId('update-provider-sidebar').find('[aria-haspopup="menu"]').click(); - cy.getByTestId('update-provider-sidebar').find('button[data-menu-item="true"]').contains('Delete').click(); - - cy.getByTestId('delete-provider-instance-modal').should('be.visible'); - cy.getByTestId('delete-provider-instance-modal').contains('Delete Mailjet Integration instance?'); - cy.getByTestId('delete-provider-instance-modal').contains( - 'Deleting a provider instance will fail workflows relying on its configuration, leading to undelivered notifications.' - ); - cy.getByTestId('delete-provider-instance-modal').find('button').contains('Cancel').should('be.visible'); - cy.getByTestId('delete-provider-instance-modal') - .find('button') - .contains('Delete instance') - .should('be.visible') - .click(); - - cy.wait('@deleteIntegration'); - - cy.getByTestId('integrations-list-table') - .getByTestId('integration-name-cell') - .contains('Mailjet Integration') - .should('not.exist'); - }); - - it('should show the Novu in-app integration', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - clickOnListRow('Novu In-App'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible'); - cy.getByTestId('provider-instance-channel').should('contain', 'In-App'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar').contains( - 'Select a framework to set up credentials to start sending notifications.' - ); - cy.getByTestId('update-provider-sidebar') - .find('a[href^="https://docs.novu.co/notification-center/introduction"]') - .contains('Explore set-up guide'); - cy.getByTestId('is_active_id').should('have.value', 'true'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${InAppProviderIdEnum.Novu}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${InAppProviderIdEnum.Novu}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${InAppProviderIdEnum.Novu}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${InAppProviderIdEnum.Novu}.svg` - ); - }); - cy.getByTestId('provider-instance-name').should('be.visible').should('have.value', 'Novu In-App'); - cy.getByTestId('provider-instance-identifier').should('contain.value', 'novu-in-app'); - cy.getByTestId('hmac').should('not.be.checked'); - cy.getByTestId('novu-in-app-frameworks').contains('Integrate In-App using a framework below'); - cy.getByTestId('novu-in-app-frameworks').contains('React'); - cy.getByTestId('novu-in-app-frameworks').contains('Angular'); - cy.getByTestId('novu-in-app-frameworks').contains('Web Component'); - cy.getByTestId('novu-in-app-frameworks').contains('Headless'); - cy.getByTestId('novu-in-app-frameworks').contains('Vue'); - cy.getByTestId('novu-in-app-frameworks').contains('iFrame'); - cy.getByTestId('update-provider-sidebar-update').should('be.disabled').contains('Update'); - }); - - it('should show the Novu in-app integration - React guide', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - clickOnListRow('Novu In-App'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('novu-in-app-frameworks').contains('React').click(); - - cy.getByTestId('update-provider-sidebar').contains('React integration guide'); - cy.getByTestId('sidebar-back').should('be.visible'); - cy.getByTestId('setup-timeline').should('be.visible'); - cy.getByTestId('update-provider-sidebar-update').should('be.disabled').contains('Update'); - }); - - it('should show the Novu Email integration sidebar', () => { - cy.intercept('GET', '*/integrations', async (req) => { - req.continue((res) => { - const { - body: { data }, - } = res; - const [firstIntegration] = data; - - res.body.data = [ - ...data, - { - _id: EmailProviderIdEnum.Novu, - _environmentId: firstIntegration._environmentId, - providerId: EmailProviderIdEnum.Novu, - active: true, - channel: ChannelTypeEnum.EMAIL, - name: 'Novu Email', - identifier: EmailProviderIdEnum.Novu, - }, - ]; - }); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('GET', '*/integrations/email/limit', async () => { - await new Promise((resolve) => setTimeout(resolve, 200)); - }).as('getNovuEmailLimit'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('sidebar-close').should('be.visible').click(); - - clickOnListRow('Novu Email'); - - cy.wait('@getNovuEmailLimit'); - cy.getByTestId('update-provider-sidebar-novu').should('be.visible'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/dark/square/novu-email.svg"]' - ); - return; - } - - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/light/square/novu-email.svg"]' - ); - }); - cy.getByTestId('provider-instance-channel').should('contain', 'Email'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar-novu') - .getByTestId('provider-instance-name') - .should('have.value', 'Novu Email'); - cy.getByTestId('update-provider-sidebar-novu').contains('Test Provider'); - cy.getByTestId('novu-provider-limits').then((el) => { - expect(el.get(0).innerText).to.eq( - 'Novu provider allows sending max 300 emails per month,\nto send more messages, configure a different provider' - ); - }); - cy.getByTestId('limitbar-limit').then((el) => { - expect(el.get(0).innerText).to.eq('300 emails per month'); - }); - }); - - it('should show the Novu SMS integration sidebar', () => { - cy.intercept('GET', '*/integrations', async (req) => { - req.continue((res) => { - const { - body: { data }, - } = res; - const [firstIntegration] = data; - - res.body.data = [ - ...data, - { - _id: SmsProviderIdEnum.Novu, - _environmentId: firstIntegration._environmentId, - providerId: SmsProviderIdEnum.Novu, - active: true, - channel: ChannelTypeEnum.SMS, - name: 'Novu SMS', - identifier: SmsProviderIdEnum.Novu, - }, - ]; - }); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('GET', '*/integrations/sms/limit', async () => { - await new Promise((resolve) => setTimeout(resolve, 200)); - }).as('getNovuSmsLimit'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - navigateToGetStarted(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('sidebar-close').should('be.visible').click(); - - clickOnListRow('Novu SMS'); - - cy.wait('@getNovuSmsLimit'); - cy.getByTestId('update-provider-sidebar-novu').should('be.visible'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/dark/square/novu-sms.svg"]' - ); - return; - } - - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/light/square/novu-sms.svg"]' - ); - }); - - cy.getByTestId('provider-instance-channel').should('contain', 'SMS'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar-novu') - .getByTestId('provider-instance-name') - .should('have.value', 'Novu SMS'); - cy.getByTestId('update-provider-sidebar-novu').contains('Test Provider'); - cy.getByTestId('novu-provider-limits').then((el) => { - expect(el.get(0).innerText).to.eq( - 'Novu provider allows sending max 20 messages per month,\nto send more messages, configure a different provider' - ); - }); - cy.getByTestId('limitbar-limit').then((el) => { - expect(el.get(0).innerText).to.eq('20 messages per month'); - }); - }); -}); diff --git a/apps/web/cypress/tests/integrations-list-page.spec.ts b/apps/web/cypress/tests/integrations-list-page.spec.ts deleted file mode 100644 index 57a31fa54bb5..000000000000 --- a/apps/web/cypress/tests/integrations-list-page.spec.ts +++ /dev/null @@ -1,1231 +0,0 @@ -// @ts-nocheck -import { - inAppProviders, - emailProviders, - chatProviders, - pushProviders, - smsProviders, - EmailProviderIdEnum, - InAppProviderIdEnum, - ChannelTypeEnum, - SmsProviderIdEnum, -} from '@novu/shared'; - -Cypress.on('window:before:load', (win) => { - win._cypress = { - ...win._cypress, - }; - win.isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; -}); - -/** - * The tests from this file were moved to the corresponding Playwright file apps/web/tests/integrations-list-page.spec.ts. - * @deprecated - */ -describe.skip('Integrations List Page', function () { - let session: any; - - beforeEach(function () { - cy.initializeSession() - .then((result) => { - session = result; - }) - .as('session'); - }); - - const interceptIntegrationRequests = () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - }; - - const checkTableLoading = () => { - cy.getByTestId('integration-name-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-provider-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-channel-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-environment-cell-loading').should('have.length', 10).first().should('be.visible'); - cy.getByTestId('integration-status-cell-loading').should('have.length', 10).first().should('be.visible'); - }; - - const checkTableRow = ( - { - name, - isFree, - provider, - channel, - environment, - status, - conditions, - }: { - name: string; - isFree?: boolean; - provider: string; - channel: string; - environment?: string; - status: string; - conditions?: number; - }, - nth: number - ) => { - cy.getByTestId('integrations-list-table').as('integrations-table'); - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-name-cell') - .should('be.visible') - .contains(name); - - if (isFree) { - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-name-cell') - .should('be.visible') - .contains('Test Provider'); - } - - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-provider-cell') - .should('be.visible') - .contains(provider); - - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-channel-cell') - .should('be.visible') - .contains(channel); - - if (environment) { - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-environment-cell') - .should('be.visible') - .contains(environment); - } - if (conditions) { - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-conditions-cell') - .should('be.visible') - .contains(conditions); - } - - cy.get('@integrations-table') - .get('tr') - .eq(nth) - .getByTestId('integration-status-cell') - .should('be.visible') - .contains(status); - }; - - const clickOnListRow = (name: string) => { - cy.getByTestId('integrations-list-table').as('integrations-table'); - cy.get('@integrations-table') - .get('tr') - .getByTestId('integration-name-cell') - .should('be.visible') - .contains(name) - .click(); - }; - - it('should show the table loading skeleton and empty state', () => { - cy.intercept('*/integrations', { - data: [], - delay: 1000, - }).as('getIntegrations'); - cy.intercept('*/environments', async () => { - await new Promise((resolve) => setTimeout(resolve, 3500)); - }).as('getEnvironments'); - - cy.visit('/integrations'); - cy.location('pathname').should('equal', '/integrations'); - - checkTableLoading(); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('no-integrations-placeholder').should('be.visible'); - cy.contains('Choose a channel you want to start sending notifications'); - - cy.getByTestId('integration-channel-card-in_app').should('be.enabled').contains('In-App'); - cy.getByTestId('integration-channel-card-email').should('be.enabled').contains('Email'); - cy.getByTestId('integration-channel-card-chat').should('be.enabled').contains('Chat'); - cy.getByTestId('integration-channel-card-push').should('be.enabled').contains('Push'); - cy.getByTestId('integration-channel-card-sms').should('be.enabled').contains('SMS'); - }); - - it('should show the table loading skeleton and then table', function () { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - - cy.visit('/integrations'); - cy.location('pathname').should('equal', '/integrations'); - - checkTableLoading(); - - cy.wait('@getIntegrations'); - cy.getByTestId('add-provider').should('be.enabled').contains('Add a provider'); - - checkTableRow( - { - name: 'SendGrid', - provider: 'SendGrid', - channel: 'Email', - environment: 'Development', - status: 'Active', - }, - 0 - ); - checkTableRow( - { - name: 'Twilio', - provider: 'Twilio', - channel: 'SMS', - environment: 'Development', - status: 'Active', - }, - 1 - ); - checkTableRow( - { - name: 'Slack', - provider: 'Slack', - channel: 'Chat', - environment: 'Development', - status: 'Active', - }, - 2 - ); - checkTableRow( - { - name: 'Discord', - provider: 'Discord', - channel: 'Chat', - environment: 'Development', - status: 'Active', - }, - 3 - ); - checkTableRow( - { - name: 'Firebase Cloud Messaging', - provider: 'Firebase Cloud Messaging', - channel: 'Push', - environment: 'Development', - status: 'Active', - }, - 4 - ); - checkTableRow( - { - name: 'Novu In-App', - isFree: false, - provider: 'Novu In-App', - channel: 'In-App', - environment: 'Development', - status: 'Active', - }, - 5 - ); - /* - checkTableRow( - { - name: 'SendGrid', - provider: 'SendGrid', - channel: 'Email', - environment: 'Production', - status: 'Active', - }, - 6 - ); - checkTableRow( - { - name: 'Twilio', - provider: 'Twilio', - channel: 'SMS', - environment: 'Production', - status: 'Active', - }, - 7 - ); - checkTableRow( - { - name: 'Slack', - provider: 'Slack', - channel: 'Chat', - environment: 'Production', - status: 'Active', - }, - 8 - ); - checkTableRow( - { - name: 'Discord', - provider: 'Discord', - channel: 'Chat', - environment: 'Production', - status: 'Active', - }, - 9 - ); - checkTableRow( - { - name: 'Firebase Cloud Messaging', - provider: 'Firebase Cloud Messaging', - channel: 'Push', - environment: 'Production', - status: 'Active', - }, - 10 - ); - checkTableRow( - { - name: 'Novu In-App', - isFree: true, - provider: 'Novu In-App', - channel: 'In-App', - environment: 'Production', - status: 'Active', - }, - 11 - ); - checkTableRow( - { - name: 'Novu Email', - provider: 'Novu Email', - channel: 'Email', - status: 'Disabled', - }, - 12 - ); - checkTableRow( - { - name: 'Novu SMS', - provider: 'Novu SMS', - channel: 'SMS', - status: 'Disabled', - }, - 13 - ); - */ - }); - - it('should show the select provider sidebar', () => { - cy.task('deleteProvider', { - providerId: InAppProviderIdEnum.Novu, - channel: ChannelTypeEnum.IN_APP, - environmentId: session.environment.id, - organizationId: session.organization.id, - }).then(() => { - cy.intercept('*/integrations', async (...args) => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - cy.location('pathname').should('equal', '/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').contains('Add a provider').click(); - - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible').as('selectProviderSidebar'); - - cy.get('@selectProviderSidebar').getByTestId('sidebar-close').should('be.visible'); - cy.get('@selectProviderSidebar').contains('Select a provider'); - cy.get('@selectProviderSidebar').contains('Select a provider to create instance for a channel'); - cy.get('@selectProviderSidebar') - .find('input[type="search"]') - .should('have.attr', 'placeholder', 'Search a provider...'); - - cy.get('@selectProviderSidebar').find('[role="tablist"]').as('channelTabs'); - cy.get('@channelTabs').find('[data-active="true"]').contains('In-App'); - cy.get('@channelTabs').contains('In-App'); - cy.get('@channelTabs').contains('Email'); - cy.get('@channelTabs').contains('Chat'); - cy.get('@channelTabs').contains('Push'); - cy.get('@channelTabs').contains('SMS'); - - cy.getByTestId('providers-group-in_app').contains('In-App').as('inAppGroup'); - cy.getByTestId('providers-group-email').contains('Email').as('emailGroup'); - cy.getByTestId('providers-group-chat').contains('Chat').as('chatGroup'); - cy.getByTestId('providers-group-push').contains('Push').as('pushGroup'); - cy.getByTestId('providers-group-sms').contains('SMS').as('smsGroup'); - - inAppProviders.forEach((provider) => { - cy.get('@inAppGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - emailProviders - .filter((provider) => provider.id !== EmailProviderIdEnum.Novu) - .forEach((provider) => { - cy.get('@emailGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - chatProviders.forEach((provider) => { - cy.get('@chatGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - pushProviders.forEach((provider) => { - cy.get('@pushGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - smsProviders - .filter((provider) => provider.id !== SmsProviderIdEnum.Novu) - .forEach((provider) => { - cy.get('@smsGroup').getByTestId(`provider-${provider.id}`).contains(provider.displayName); - }); - - cy.getByTestId('select-provider-sidebar-cancel').contains('Cancel'); - cy.getByTestId('select-provider-sidebar-next').should('be.disabled').contains('Next'); - }); - }); - - it('should allow for searching', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.getByTestId('select-provider-sidebar').should('be.visible').as('selectProviderSidebar'); - - cy.get('@selectProviderSidebar').find('input[type="search"]').type('Mail'); - - cy.getByTestId('select-provider-sidebar').find('[role="tablist"]').as('channelTabs'); - cy.get('@channelTabs').contains('In-App').should('not.be.visible'); - cy.get('@channelTabs').contains('Email').should('be.visible'); - cy.get('@channelTabs').contains('Chat').should('not.be.visible'); - cy.get('@channelTabs').contains('Push').should('not.be.visible'); - cy.get('@channelTabs').contains('SMS').should('not.be.visible'); - - cy.getByTestId('providers-group-email').contains('Email').as('emailGroup'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.Mailgun}`).contains('Mailgun'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.MailerSend}`).contains('MailerSend'); - cy.get('@emailGroup').getByTestId(`provider-${EmailProviderIdEnum.EmailWebhook}`).contains('Email Webhook'); - cy.getByTestId('select-provider-sidebar-next').should('be.disabled').contains('Next'); - }); - - it('should show empty search results', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.getByTestId('select-provider-sidebar').should('be.visible').as('selectProviderSidebar'); - - cy.get('@selectProviderSidebar').find('input[type="search"]').type('safasdfasdfasdfasdfas'); - - cy.getByTestId('providers-group-in_app').should('not.exist'); - cy.getByTestId('providers-group-email').should('not.exist'); - cy.getByTestId('providers-group-chat').should('not.exist'); - cy.getByTestId('providers-group-push').should('not.exist'); - cy.getByTestId('providers-group-sms').should('not.exist'); - - cy.getByTestId('select-provider-no-search-results-img').should('be.visible'); - cy.getByTestId('select-provider-sidebar-next').should('be.disabled').contains('Next'); - }); - - it('should allow selecting a provider', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - }); - cy.getByTestId('selected-provider-name').should('be.visible').contains('Mailjet'); - - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next'); - }); - - it('should allow moving to create sidebar', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('create-provider-instance-sidebar') - .should('be.visible') - .contains('Specify assignment preferences to automatically allocate the provider instance to the Email channel.'); - cy.getByTestId('create-provider-instance-sidebar-back').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Mailjet}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${EmailProviderIdEnum.Mailjet}.svg` - ); - }); - cy.getByTestId('provider-instance-name').should('be.visible').should('have.value', 'Mailjet'); - cy.getByTestId('create-provider-instance-sidebar').contains('Environment'); - cy.getByTestId('create-provider-instance-sidebar').contains('Provider instance executes only for'); - cy.getByTestId('create-provider-instance-sidebar').find('[role="radiogroup"]').as('environmentRadios'); - cy.get('@environmentRadios').find('[data-checked="true"]').contains('Development'); - cy.get('@environmentRadios').contains('Production'); - cy.getByTestId('create-provider-instance-sidebar-cancel').should('not.be.disabled').contains('Cancel'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create'); - }); - - it('should allow moving back from create provider sidebar to select provider sidebar', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - - cy.getByTestId('create-provider-instance-sidebar-back').should('be.visible').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - }); - - it('should create a new mailjet integration', () => { - interceptIntegrationRequests(); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - cy.getByTestId('create-provider-instance-sidebar-create').should('be.disabled'); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.location('pathname').should('contain', '/integrations/'); - - checkTableRow( - { - name: 'Mailjet Integration', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Disabled', - }, - 6 - ); - }); - - it('should create a new mailjet integration with conditions', () => { - interceptIntegrationRequests(); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('add-conditions-btn').click(); - cy.getByTestId('conditions-form-title').contains('Conditions for Mailjet Integration provider instance'); - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-on').should('have.value', 'Tenant'); - cy.getByTestId('conditions-form-key').type('identifier'); - cy.getByTestId('conditions-form-operator').should('have.value', 'Equal'); - cy.getByTestId('conditions-form-value').type('tenant123'); - cy.getByTestId('apply-conditions-btn').click(); - cy.getByTestId('add-conditions-btn').contains('Edit conditions'); - - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - cy.getByTestId('create-provider-instance-sidebar-create').should('be.disabled'); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('header-add-conditions-btn').contains('1').click(); - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-key').last().type('identifier'); - cy.getByTestId('conditions-form-value').last().type('tenant456'); - cy.getByTestId('apply-conditions-btn').click(); - cy.getByTestId('header-add-conditions-btn').contains('2'); - cy.location('pathname').should('contain', '/integrations/'); - - checkTableRow( - { - name: 'Mailjet Integration', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Disabled', - conditions: 1, - }, - 6 - ); - }); - - it('should remove as primary when adding conditions', () => { - interceptIntegrationRequests(); - - cy.getByTestId('integrations-list-table') - .getByTestId('integration-name-cell') - .contains('SendGrid') - .getByTestId('integration-name-cell-primary') - .should('be.visible'); - - clickOnListRow('SendGrid'); - cy.getByTestId('header-add-conditions-btn').click(); - - cy.getByTestId('remove-primary-flag-modal').should('be.visible'); - cy.getByTestId('remove-primary-flag-modal').contains('Primary flag will be removed'); - cy.getByTestId('remove-primary-flag-modal').contains( - 'Adding conditions to the primary provider instance removes its primary status when a user applies changes by' - ); - cy.getByTestId('remove-primary-flag-modal').find('button').contains('Cancel').should('be.visible'); - cy.getByTestId('remove-primary-flag-modal').find('button').contains('Got it').should('be.visible').click(); - - cy.getByTestId('conditions-form-title').contains('Conditions for SendGrid provider instance'); - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-on').should('have.value', 'Tenant'); - cy.getByTestId('conditions-form-key').type('identifier'); - cy.getByTestId('conditions-form-operator').should('have.value', 'Equal'); - cy.getByTestId('conditions-form-value').type('tenant123'); - - cy.getByTestId('apply-conditions-btn').click(); - cy.getByTestId('provider-instance-name').first().clear().type('SendGrid test'); - - cy.getByTestId('from').type('info@novu.co'); - cy.getByTestId('senderName').type('Novu'); - - cy.getByTestId('update-provider-sidebar-update').should('not.be.disabled').contains('Update').click(); - cy.get('.mantine-Modal-modal button').contains('Make primary'); - - cy.get('.mantine-Modal-close').click(); - }); - - it('should remove conditions when set to primary', () => { - interceptIntegrationRequests(); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('add-conditions-btn').click(); - cy.getByTestId('conditions-form-title').contains('Conditions for Mailjet Integration provider instance'); - - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-key').type('identifier'); - cy.getByTestId('conditions-form-value').type('tenant123'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('header-add-conditions-btn').contains('1'); - - cy.getByTestId('header-make-primary-btn').click(); - - cy.getByTestId('remove-conditions-modal').should('be.visible'); - cy.getByTestId('remove-conditions-modal').contains('Conditions will be removed'); - cy.getByTestId('remove-conditions-modal').contains('Marking this instance as primary will remove all conditions'); - cy.getByTestId('remove-conditions-modal').find('button').contains('Cancel').should('be.visible'); - cy.getByTestId('remove-conditions-modal').find('button').contains('Remove conditions').should('be.visible').click(); - - cy.getByTestId('header-make-primary-btn').should('not.exist'); - - cy.getByTestId('integrations-list-table') - .getByTestId('integration-name-cell') - .contains('Mailjet Integration') - .getByTestId('integration-name-cell-primary') - .should('be.visible'); - }); - - it('should update the mailjet integration', () => { - interceptIntegrationRequests(); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('provider-instance-channel').should('contain', 'Email'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar').contains('Set up credentials to start sending notifications.'); - cy.getByTestId('update-provider-sidebar').getByTestId('is_active_id').should('have.value', 'false'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-name') - .should('have.value', 'Mailjet Integration'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-identifier') - .should('contain.value', 'mailjet'); - - cy.getByTestId('update-provider-sidebar-update').should('be.disabled'); - cy.location('pathname').should('contain', '/integrations/'); - cy.getByTestId('provider-instance-name').first().clear().type('Mailjet Integration Updated'); - cy.getByTestId('is_active_id').check({ force: true }); - cy.getByTestId('apiKey').type('fake-api-key'); - cy.getByTestId('secretKey').type('fake-secret-key'); - cy.getByTestId('from').type('info@novu.co'); - cy.getByTestId('senderName').type('Novu'); - cy.getByTestId('update-provider-sidebar-update').should('not.be.disabled').contains('Update').click(); - - cy.get('.mantine-Modal-close').click(); - - checkTableRow( - { - name: 'Mailjet Integration Updated', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Active', - }, - 6 - ); - }); - - it('should update the mailjet integration from the list', () => { - interceptIntegrationRequests(); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible').click(); - cy.location('pathname').should('equal', '/integrations'); - - cy.wait('@getIntegrations'); - - clickOnListRow('Mailjet Integration'); - cy.location('pathname').should('contain', '/integrations/'); - - cy.getByTestId('update-provider-sidebar').contains('Set up credentials to start sending notifications.'); - cy.getByTestId('update-provider-sidebar').getByTestId('is_active_id').should('have.value', 'false'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-name') - .should('have.value', 'Mailjet Integration'); - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-instance-identifier') - .should('contain.value', 'mailjet'); - - cy.getByTestId('update-provider-sidebar-update').should('be.disabled'); - cy.location('pathname').should('contain', '/integrations/'); - cy.getByTestId('provider-instance-name').first().clear().type('Mailjet Integration Updated'); - cy.getByTestId('is_active_id').check({ force: true }); - cy.getByTestId('apiKey').type('fake-api-key'); - cy.getByTestId('secretKey').type('fake-secret-key'); - cy.getByTestId('from').type('info@novu.co'); - cy.getByTestId('senderName').type('Novu'); - cy.getByTestId('update-provider-sidebar-update').should('not.be.disabled').click(); - - cy.get('.mantine-Modal-close').click(); - - checkTableRow( - { - name: 'Mailjet Integration Updated', - provider: 'Mailjet', - channel: 'Email', - environment: 'Development', - status: 'Active', - }, - 6 - ); - }); - - it('should allow to delete the mailjet integration', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.location('pathname').should('equal', '/integrations/create'); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Mailjet}`).contains('Mailjet').click(); - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - - cy.location('pathname').should('equal', '/integrations/create/email/mailjet'); - cy.getByTestId('provider-instance-name').clear().type('Mailjet Integration'); - cy.getByTestId('create-provider-instance-sidebar-create').should('not.be.disabled').contains('Create').click(); - - cy.wait('@createIntegration'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible').click(); - cy.location('pathname').should('equal', '/integrations'); - - cy.wait('@getIntegrations'); - - clickOnListRow('Mailjet Integration'); - cy.location('pathname').should('contain', '/integrations/'); - - cy.getByTestId('update-provider-sidebar').find('[aria-haspopup="menu"]').click(); - cy.getByTestId('update-provider-sidebar').find('button[data-menu-item="true"]').contains('Delete').click(); - - cy.getByTestId('delete-provider-instance-modal').should('be.visible'); - cy.getByTestId('delete-provider-instance-modal').contains('Delete Mailjet Integration instance?'); - cy.getByTestId('delete-provider-instance-modal').contains( - 'Deleting a provider instance will fail workflows relying on its configuration, leading to undelivered notifications.' - ); - cy.getByTestId('delete-provider-instance-modal').find('button').contains('Cancel').should('be.visible'); - cy.getByTestId('delete-provider-instance-modal') - .find('button') - .contains('Delete instance') - .should('be.visible') - .click(); - - cy.wait('@deleteIntegration'); - - cy.getByTestId('integrations-list-table') - .getByTestId('integration-name-cell') - .contains('Mailjet Integration') - .should('not.exist'); - }); - - it('should show the Novu in-app integration', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - clickOnListRow('Novu In-App'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('sidebar-close').should('be.visible'); - cy.location('pathname').should('contain', '/integrations/'); - cy.getByTestId('provider-instance-channel').should('contain', 'In-App'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar').contains( - 'Select a framework to set up credentials to start sending notifications.' - ); - cy.getByTestId('update-provider-sidebar') - .find('a[href^="https://docs.novu.co/notification-center/introduction"]') - .contains('Explore set-up guide'); - cy.getByTestId('is_active_id').should('have.value', 'true'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${InAppProviderIdEnum.Novu}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${InAppProviderIdEnum.Novu}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${InAppProviderIdEnum.Novu}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${InAppProviderIdEnum.Novu}.svg` - ); - }); - - cy.getByTestId('provider-instance-name').should('be.visible').should('have.value', 'Novu In-App'); - cy.getByTestId('provider-instance-identifier').should('contain.value', 'novu-in-app'); - cy.getByTestId('hmac').should('not.be.checked'); - cy.getByTestId('novu-in-app-frameworks').contains('Integrate In-App using a framework below'); - cy.getByTestId('novu-in-app-frameworks').contains('React'); - cy.getByTestId('novu-in-app-frameworks').contains('Angular'); - cy.getByTestId('novu-in-app-frameworks').contains('Web Component'); - cy.getByTestId('novu-in-app-frameworks').contains('Headless'); - cy.getByTestId('novu-in-app-frameworks').contains('Vue'); - cy.getByTestId('novu-in-app-frameworks').contains('iFrame'); - cy.getByTestId('update-provider-sidebar-update').should('be.disabled').contains('Update'); - }); - - it('should show the Novu in-app integration - React guide', () => { - cy.intercept('GET', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - clickOnListRow('Novu In-App'); - - cy.getByTestId('update-provider-sidebar').should('be.visible'); - cy.getByTestId('novu-in-app-frameworks').contains('React').click(); - - cy.getByTestId('update-provider-sidebar').contains('React integration guide'); - cy.getByTestId('sidebar-back').should('be.visible'); - cy.getByTestId('setup-timeline').should('be.visible'); - cy.getByTestId('update-provider-sidebar-update').should('be.disabled').contains('Update'); - }); - - it('should show the Novu Email integration sidebar', () => { - cy.intercept('GET', '*/integrations', async (req) => { - req.continue((res) => { - const { - body: { data }, - } = res; - const [firstIntegration] = data; - - res.body.data = [ - ...data, - { - _id: EmailProviderIdEnum.Novu, - _environmentId: firstIntegration._environmentId, - providerId: EmailProviderIdEnum.Novu, - active: true, - channel: ChannelTypeEnum.EMAIL, - name: 'Novu Email', - identifier: EmailProviderIdEnum.Novu, - }, - ]; - }); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('GET', '*/integrations/email/limit', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getNovuEmailLimit'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - clickOnListRow('Novu Email'); - - cy.wait('@getNovuEmailLimit'); - cy.getByTestId('update-provider-sidebar-novu').should('be.visible'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/dark/square/novu-email.svg"]' - ); - return; - } - - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/light/square/novu-email.svg"]' - ); - }); - - cy.getByTestId('provider-instance-channel').should('contain', 'Email'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar-novu') - .getByTestId('provider-instance-name') - .should('have.value', 'Novu Email'); - cy.getByTestId('update-provider-sidebar-novu').contains('Test Provider'); - cy.getByTestId('novu-provider-limits').then((el) => { - expect(el.get(0).innerText).to.eq( - 'Novu provider allows sending max 300 emails per month,\nto send more messages, configure a different provider' - ); - }); - cy.getByTestId('limitbar-limit').then((el) => { - expect(el.get(0).innerText).to.eq('300 emails per month'); - }); - }); - - it('should show the Novu SMS integration sidebar', () => { - cy.intercept('GET', '*/integrations', async (req) => { - req.continue((res) => { - const { - body: { data }, - } = res; - const [firstIntegration] = data; - - res.body.data = [ - ...data, - { - _id: SmsProviderIdEnum.Novu, - _environmentId: firstIntegration._environmentId, - providerId: SmsProviderIdEnum.Novu, - active: true, - channel: ChannelTypeEnum.SMS, - name: 'Novu SMS', - identifier: SmsProviderIdEnum.Novu, - }, - ]; - }); - }).as('getIntegrations'); - cy.intercept('POST', '*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('createIntegration'); - cy.intercept('GET', '*/integrations/sms/limit', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getNovuSmsLimit'); - cy.intercept('DELETE', '*/integrations/*', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('deleteIntegration'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - clickOnListRow('Novu SMS'); - - cy.wait('@getNovuSmsLimit'); - cy.getByTestId('update-provider-sidebar-novu').should('be.visible'); - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/dark/square/novu-sms.svg"]' - ); - return; - } - - cy.getByTestId('update-provider-sidebar-novu').find( - 'img[src="/static/images/providers/light/square/novu-sms.svg"]' - ); - }); - cy.getByTestId('provider-instance-channel').should('contain', 'SMS'); - cy.getByTestId('provider-instance-environment').should('contain', 'Development'); - cy.getByTestId('update-provider-sidebar-novu') - .getByTestId('provider-instance-name') - .should('have.value', 'Novu SMS'); - cy.getByTestId('update-provider-sidebar-novu').contains('Test Provider'); - cy.getByTestId('novu-provider-limits').then((el) => { - expect(el.get(0).innerText).to.eq( - 'Novu provider allows sending max 20 messages per month,\nto send more messages, configure a different provider' - ); - }); - cy.getByTestId('limitbar-limit').then((el) => { - expect(el.get(0).innerText).to.eq('20 messages per month'); - }); - }); - - it('should not allow creating a novu provider for the same environment if it already exists', () => { - cy.intercept('*/integrations', async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }).as('getIntegrations'); - cy.intercept('*/environments').as('getEnvironments'); - - cy.visit('/integrations'); - - cy.wait('@getIntegrations'); - cy.wait('@getEnvironments'); - - cy.getByTestId('add-provider').should('be.enabled').click(); - cy.getByTestId('select-provider-sidebar').should('be.visible'); - - cy.getByTestId(`provider-${EmailProviderIdEnum.Novu}`).contains('Novu').click(); - - cy.window().then((win) => { - if (win.isDarkTheme) { - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Novu}`).should( - 'have.attr', - 'src', - `/static/images/providers/dark/square/${EmailProviderIdEnum.Novu}.svg` - ); - return; - } - - cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Novu}`).should( - 'have.attr', - 'src', - `/static/images/providers/light/square/${EmailProviderIdEnum.Novu}.svg` - ); - }); - cy.getByTestId('selected-provider-name').should('be.visible').contains('Novu'); - - cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click(); - cy.getByTestId('novu-provider-error').contains('You can only create one Novu Email per environment.'); - cy.getByTestId('create-provider-instance-sidebar-create').should('be.disabled'); - }); - - it('should show the Webhook URL for the Email integration', () => { - interceptIntegrationRequests(); - cy.intercept( - { - method: 'GET', - url: '*/integrations/webhook/provider/*/status', - }, - { data: true } - ).as('getWebhookStatus'); - - cy.getByTestId('integrations-list-table') - .getByTestId('integration-name-cell') - .contains('SendGrid') - .getByTestId('integration-name-cell-primary') - .should('be.visible'); - - clickOnListRow('SendGrid'); - - cy.wait('@getWebhookStatus'); - - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-webhook-url') - .invoke('val') - .then((val) => { - expect(val).match( - /^http:\/\/(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost):\d{4}\/webhooks\/organizations\/\w{1,}\/environments\/\w{1,}\/email\/\w{1,}/ - ); - }); - }); - - it('should show the Webhook URL for the SMS integration', () => { - interceptIntegrationRequests(); - cy.intercept( - { - method: 'GET', - url: '*/integrations/webhook/provider/*/status', - }, - { data: true } - ).as('getWebhookStatus'); - - cy.getByTestId('integrations-list-table') - .getByTestId('integration-name-cell') - .contains('SendGrid') - .getByTestId('integration-name-cell-primary') - .should('be.visible'); - - clickOnListRow('Twilio'); - - cy.wait('@getWebhookStatus'); - - cy.getByTestId('update-provider-sidebar') - .getByTestId('provider-webhook-url') - .invoke('val') - .then((val) => { - expect(val).match( - /^http:\/\/(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost):\d{4}\/webhooks\/organizations\/\w{1,}\/environments\/\w{1,}\/sms\/\w{1,}/ - ); - }); - }); -}); diff --git a/apps/web/cypress/tests/invites.spec.ts b/apps/web/cypress/tests/invites.spec.ts deleted file mode 100644 index 22c7244d90f6..000000000000 --- a/apps/web/cypress/tests/invites.spec.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { FeatureFlagsKeysEnum } from '@novu/shared'; -import * as capitalize from 'lodash.capitalize'; - -describe('Invites module', function () { - beforeEach(function () { - cy.mockFeatureFlags({ [FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED]: false }); - cy.task('clearDatabase'); - }); - - it('should accept invite to organization', function () { - cy.inviteUser('testing-amazing@user.com').then(() => { - doRegister(this.token); - - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-organization-name').contains(capitalize(this.organization.name.split(' ')[0])); - }); - }); - - it.skip('should allow to login with GitHub if invited existing user', function () { - const isCI = Cypress.env('IS_CI'); - if (!isCI) return; - - const gitHubUserEmail = Cypress.env('GITHUB_USER_EMAIL'); - - cy.inviteUser(gitHubUserEmail).then(() => { - cy.visit('/auth/invitation/' + this.token); - - cy.getByTestId('auth-container-title').contains('Get Started'); - cy.getByTestId('invitation-description').then((el) => { - cy.then(() => el.text()).should( - 'equal', - `You've been invited by ${this.inviter.firstName} to join ${this.organization.name}.` - ); - }); - - cy.loginWithGitHub(); - - cy.url().should('include', '/workflows'); - }); - }); - - it.skip('invite a new user, sign up a new account with GitHub and then accept an invite', function () { - const isCI = Cypress.env('IS_CI'); - if (!isCI) return; - - const gitHubUserEmail = Cypress.env('GITHUB_USER_EMAIL'); - - cy.inviteUser(gitHubUserEmail).then(() => { - cy.visit('/auth/signup'); - cy.loginWithGitHub(); - - cy.location('pathname').should('equal', '/auth/application'); - cy.getByTestId('questionnaire-company-name').type('Organization Name'); - cy.getByTestId('submit-btn').click(); - cy.url().should('include', '/quickstart'); - - // check if user is logged in - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-organization-name').contains(capitalize('Organization Name'.split(' ')[0])); - cy.getByTestId('header-dropdown-username').contains('Johnny Depp'); - - // logout - cy.getByTestId('logout-button').click(); - - // visit invite link - cy.visit('/auth/invitation/' + this.token); - - // check the invite page - cy.getByTestId('auth-container-title').contains('Sign In & Accept Invite'); - cy.getByTestId('invitation-description').then((el) => { - cy.then(() => el.text()).should( - 'equal', - `You've been invited by ${this.inviter.firstName} to join ${this.organization.name}.` - ); - }); - cy.getByTestId('github-button').click(); - - cy.url().should('include', '/workflows'); - }); - }); - - it('should allow to login if invited new user', function () { - cy.inviteUser('testing-amazing@user.com').then(() => { - doRegister(this.token); - }); - - cy.inviteUser('testing-amazing@user.com').then(() => { - cy.visit('/auth/invitation/' + this.token); - - cy.getByTestId('email').should('have.value', 'testing-amazing@user.com'); - cy.getByTestId('password').type('asd#Faf4fd'); - cy.getByTestId('submit-btn').click(); - - cy.url().should('include', '/workflows'); - - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('organization-switch').focus(); - cy.get('.mantine-Select-item').contains(capitalize(this.organization.name)).click(); - }); - }); - - it.skip('should also accept invite if already logged in with right user', function () { - cy.inviteUser('testing-amazing@user.com').then(() => { - doRegister(this.token); - }); - - cy.inviteUser('testing-amazing@user.com').then(() => { - doLogin('testing-amazing@user.com', 'asd#Faf4fd'); - - cy.visit('/auth/invitation/' + this.token); - - cy.url().should('include', '/workflows'); - - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('organization-switch').focus(); - cy.get('.mantine-Select-item').contains(capitalize(this.organization.name)).click({ force: true }); - }); - }); - - it('should redirect to invitation page again if invitation open with an active user session', function () { - cy.inviteUser('testing-amazing@user.com').then(() => { - cy.initializeSession().as('session'); - - const invitationPath = `/auth/invitation/${this.token}`; - cy.waitLoadFeatureFlags(() => { - cy.visit(invitationPath); - }); - cy.getByTestId('success-screen-reset').click(); - - // checking if token is removed from local storage - cy.getLocalStorage('auth_token').should('be.null'); - // checking if user is redirected to the given invitation page - cy.location('pathname').should('equal', invitationPath); - }); - }); -}); - -function doRegister(token: string) { - cy.visit('/auth/invitation/' + token); - cy.getByTestId('fullName').type('Invited to org user'); - cy.getByTestId('password').type('asd#Faf4fd'); - cy.getByTestId('accept-cb').click({ force: true }); - cy.getByTestId('submitButton').click(); - - cy.url().should('include', '/workflows'); -} - -function doLogin(email: string, password: string) { - cy.visit('/auth/login'); - cy.getByTestId('email').type(email); - cy.getByTestId('password').type(password); - cy.getByTestId('submit-btn').click(); - - cy.url().should('include', '/workflows'); -} diff --git a/apps/web/cypress/tests/layout/header.spec.ts b/apps/web/cypress/tests/layout/header.spec.ts deleted file mode 100644 index 7d9616063fe9..000000000000 --- a/apps/web/cypress/tests/layout/header.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -describe('App Header', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - cy.visit('/'); - }); - - it('should switch to dark mode', function () {}); - - it('should display correct user photo', function () { - cy.getByTestId('header-profile-avatar') - .find('img') - .should('have.attr', 'src') - .should('include', this.session.user.profilePicture); - }); - - it('should display user name in dropdown', function () { - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-username').should('contain', this.session.user.firstName); - cy.getByTestId('header-dropdown-username').should('contain', this.session.user.lastName); - }); - - it('should display organization name in dropdown', function () { - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('header-dropdown-organization-name').contains(this.session.organization.name, { - matchCase: false, - }); - }); - - it('logout user successfully', function () { - cy.getByTestId('header-profile-avatar').click(); - cy.getByTestId('logout-button').click(); - cy.location('pathname').should('equal', '/auth/login'); - - cy.window() - .then((win) => { - return win.localStorage.getItem('auth_token'); - }) - .should('not.be.ok'); - - cy.visit('/'); - cy.location('pathname').should('equal', '/auth/login'); - }); -}); diff --git a/apps/web/cypress/tests/layout/main-nav.spec.ts b/apps/web/cypress/tests/layout/main-nav.spec.ts deleted file mode 100644 index 15d73a6a1920..000000000000 --- a/apps/web/cypress/tests/layout/main-nav.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -describe('Main Nav (Sidebar)', () => { - /** - * TODO: when Information Architecture is fully-enabled, the tests below should be merged - * with the other describe block - */ - describe('Legacy tests (Information Architecture disabled)', () => { - beforeEach(() => { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: false }); - cy.initializeSession().as('session'); - cy.visit('/'); - }); - - it('should navigate correctly to notification-templates', () => { - cy.getByTestId('side-nav-templates-link').should('have.attr', 'href').should('include', '/workflows'); - }); - - it('should show bottom support, docs and share feedback', () => { - cy.getByTestId('side-nav-bottom-links').scrollIntoView().should('be.visible'); - cy.getByTestId('side-nav-bottom-link-support') - .should('have.attr', 'href') - .should('eq', 'https://discord.novu.co'); - cy.getByTestId('side-nav-bottom-link-documentation') - .should('have.attr', 'href') - .should('eq', 'https://docs.novu.co?utm_campaign=in-app'); - - cy.getByTestId('side-nav-bottom-link-share-feedback') - .should('have.attr', 'href') - .should('eq', 'https://github.com/novuhq/novu/issues/new/choose'); - }); - - // TODO: when Information Architecture is fully-enabled, this test can be removed (it's redundant). - it('should navigate correctly to settings', function () { - cy.getByTestId('side-nav-settings-link').should('have.attr', 'href').should('include', '/settings'); - }); - }); - - describe('Information Architecture enabled', () => { - beforeEach(() => { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - cy.visit('/'); - }); - - it('should render all navigation links', () => { - // Check if all expected navigation links are present - cy.getByTestId('side-nav-quickstart-link').should('exist'); - cy.getByTestId('side-nav-integrations-link').should('exist'); - cy.getByTestId('side-nav-settings-link').should('exist'); - cy.getByTestId('side-nav-templates-link').should('exist'); - cy.getByTestId('side-nav-activities-link').should('exist'); - cy.getByTestId('side-nav-changes-link').should('exist'); - cy.getByTestId('side-nav-subscribers-link').should('exist'); - cy.getByTestId('side-nav-tenants-link').should('exist'); - cy.getByTestId('side-nav-translations-link').should('exist'); - }); - - it('should navigate to correct routes when clicking links', () => { - // Check if clicking on a navigation link navigates to the correct route - cy.getByTestId('side-nav-quickstart-link').click(); - cy.url().should('include', '/get-started'); - - cy.getByTestId('side-nav-integrations-link').click(); - cy.url().should('include', '/integrations'); - - cy.getByTestId('side-nav-settings-link').click(); - cy.url().should('include', '/settings'); - }); - - it('should hide "Get started" link after clicking the visibility button', () => { - // Click the visibility button next to the "Get started" link - cy.getByTestId('side-nav-quickstart-link').should('exist').trigger('mouseover').find('button').click(); - - // Check if the "Get started" link is no longer visible - cy.getByTestId('side-nav-quickstart-link').should('not.exist'); - }); - - it('should display settings menu when navigating to settings page', () => { - // Navigate to the settings page - cy.getByTestId('side-nav-settings-link').click(); - - // Check if the settings menu is displayed - cy.getByTestId('side-nav-settings-user-profile').should('exist'); - cy.getByTestId('side-nav-settings-organization-link').should('exist'); - cy.getByTestId('side-nav-settings-security-link').should('exist'); - cy.getByTestId('side-nav-settings-team-link').should('exist'); - cy.getByTestId('side-nav-settings-branding-link').should('exist'); - cy.getByTestId('side-nav-settings-billing-link').should('exist'); - cy.getByTestId('side-nav-settings-api-keys').should('exist'); - cy.getByTestId('side-nav-settings-inbound-webhook').should('exist'); - /** TODO: we will reinstate the toggle buttons w/ different envs once we have APIs to support the pages */ - // cy.getByTestId('side-nav-settings-toggle-development').should('exist'); - // cy.getByTestId('side-nav-settings-toggle-production').should('exist'); - }); - - /** TODO: we will reinstate the toggle buttons w/ different envs once we have APIs to support the pages */ - it.skip('should display correct environment settings when toggling development/production', () => { - // Navigate to the settings page - cy.getByTestId('side-nav-settings-link').click(); - - // Toggle development environment - cy.getByTestId('side-nav-settings-toggle-development').click(); - cy.getByTestId('side-nav-settings-api-keys-development').should('exist'); - cy.getByTestId('side-nav-settings-inbound-webhook-development').should('exist'); - - // Toggle production environment - cy.getByTestId('side-nav-settings-toggle-production').click(); - cy.getByTestId('side-nav-settings-api-keys-production').should('exist'); - cy.getByTestId('side-nav-settings-inbound-webhook-production').should('exist'); - }); - }); -}); diff --git a/apps/web/cypress/tests/notification-editor/create-notification.spec.ts b/apps/web/cypress/tests/notification-editor/create-notification.spec.ts deleted file mode 100644 index bc1c379c4248..000000000000 --- a/apps/web/cypress/tests/notification-editor/create-notification.spec.ts +++ /dev/null @@ -1,488 +0,0 @@ -import { addAndEditChannel, clickWorkflow, dragAndDrop, editChannel, fillBasicNotificationDetails, goBack } from '.'; - -describe('Creation functionality', function () { - beforeEach(function () { - cy.initializeSession({ showOnBoardingTour: false }).as('session'); - }); - - it('should create in-app notification', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('title').clear().first().type('Test Notification Title'); - cy.getByTestId('description').type('This is a test description for a test title'); - cy.get('body').click(); - cy.getByTestId('trigger-code-snippet').should('not.exist'); - cy.getByTestId('groupSelector').should('have.value', 'General'); - - addAndEditChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.get('.monaco-editor textarea:first', { timeout: 10000 }) - .parent() - .click() - .find('textarea') - .type('

{{firstName}} someone assigned you to {{taskName}}

', { - parseSpecialCharSequences: false, - force: true, - }); - cy.getByTestId('inAppRedirect').type('/example/test'); - cy.getByTestId('editor-mode-switch').find('label').last().click(); - cy.getByTestId('in-app-content-preview').contains('firstName someone assigned you to taskName', { timeout: 1000 }); - - goBack(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.getByTestId('get-snippet-btn').click(); - cy.getByTestId('trigger-code-snippet').should('be.visible'); - cy.getByTestId('trigger-code-snippet').contains('test-notification-title'); - cy.getByTestId('trigger-code-snippet').contains("import { Novu } from '@novu/node'"); - - cy.get('.mantine-Tabs-tabsList').contains('Curl').click(); - cy.getByTestId('trigger-curl-snippet').contains("--header 'Authorization: ApiKey"); - cy.getByTestId('trigger-curl-snippet').contains('taskName'); - }); - - it('should manage variables', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - cy.getByTestId('settings-page').click(); - cy.getByTestId('title').clear().first().type('Test Notification Title'); - cy.getByTestId('description').type('This is a test description for a test title'); - cy.get('body').click(); - - addAndEditChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('email-editor').getByTestId('editor-row').click(); - cy.getByTestId('control-add').click(); - cy.getByTestId('add-btn-block').click(); - cy.getByTestId('button-block-wrapper').should('be.visible'); - cy.getByTestId('button-block-wrapper').find('button').click(); - cy.getByTestId('button-text-input').clear().type('Example Text Of {{ctaName}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('button-block-wrapper').find('button').contains('Example Text Of {{ctaName}}'); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - - cy.getByTestId('email-editor').getByTestId('editor-row').eq(1).click(); - cy.getByTestId('control-add').click(); - cy.getByTestId('add-text-block').click({ force: true }); - cy.getByTestId('editable-text-content').eq(1).clear().type('This another text will be {{customVariable}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('editable-text-content').eq(1).click(); - - cy.getByTestId('settings-row-btn').eq(1).invoke('show').click(); - cy.getByTestId('remove-row-btn').click(); - cy.getByTestId('button-block-wrapper').should('not.exist'); - - cy.getByTestId('emailSubject').type('this is email subject'); - cy.getByTestId('emailPreheader').type('this is email preheader'); - - cy.getByTestId('var-label').first().contains('System Variables').click(); - cy.getByTestId('var-label').last().contains('Translation Variables'); - cy.getByTestId('var-items-step').contains('step'); - cy.getByTestId('var-items-step').contains('object'); - cy.getByTestId('var-items-branding').contains('branding'); - cy.getByTestId('var-items-subscriber').contains('subscriber'); - cy.getByTestId('var-item-firstName-string').contains('firstName'); - cy.getByTestId('var-item-firstName-string').contains('string'); - cy.getByTestId('var-item-customVariable-string').contains('customVariable'); - cy.getByTestId('var-item-customVariable-string').contains('string'); - cy.getByTestId('var-items-subscriber').click(); - cy.getByTestId('var-item-phone-string').contains('string'); - - cy.getByTestId('editor-mode-switch').find('label').eq(1).click(); - cy.getByTestId('preview-subject').contains('this is email subject'); - cy.getByTestId('preview-content') - .invoke('attr', 'srcdoc') - .then((value) => { - expect(value).to.contain('This text is written from a test'); - }); - - cy.getByTestId('preview-mode-switch').find('label').last().click(); - cy.getByTestId('preview-subject').contains('this is email subject'); - cy.getByTestId('preview-content') - .invoke('attr', 'srcdoc') - .then((value) => { - expect(value).to.contain('This text is written from a test'); - }); - - cy.getByTestId('preview-json-param').clear().type(`{ - "firstName": "Novu", - "customVariable": "notCustomVariable" -}`); - cy.getByTestId('apply-variables').click(); - - cy.getByTestId('preview-content') - .invoke('attr', 'srcdoc') - .then((value) => { - expect(value).to.contain('This text is written from a test Novu'); - expect(value).to.contain('This another text will be notCustomVariable'); - }); - - cy.getByTestId('editor-mode-switch').find('label').last().click(); - - cy.getByTestId('test-email-json-param').clear().type(`{ - "firstName": "Novu", - "customVariable": "notCustomVariable" -}`); - - cy.getByTestId('test-send-email-btn').click(); - cy.get('.mantine-Notification-root').contains('Test sent successfully!'); - }); - - it('should not clear the set default values of variables on update', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - cy.waitForNetworkIdle(500); - - dragAndDrop('email'); - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode('node-emailSelector'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('edit-action').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('emailSubject').type('this is email subject'); - cy.getByTestId('email-editor').getByTestId('editor-row').click(); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - - cy.getByTestId('open-edit-variables-btn').click(); - cy.getByTestId('variable-default-value').type('Test Var Value'); - cy.getByTestId('close-var-manager-modal').click(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('open-edit-variables-btn').click(); - cy.getByTestId('variable-default-value').should('have.value', 'Test Var Value'); - cy.getByTestId('close-var-manager-modal').click(); - goBack(); - dragAndDrop('sms'); - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode('node-smsSelector'); - cy.getByTestId('edit-action').click(); - cy.waitForNetworkIdle(500); - - cy.get('.monaco-editor textarea:first', { timeout: 7000 }) - .parent() - .click() - .find('textarea') - .type('This text is written from a test {{var}}', { - parseSpecialCharSequences: false, - force: true, - }); - cy.getByTestId('open-variable-management').click(); - cy.getByTestId('open-edit-variables-btn').click(); - cy.getByTestId('variable-default-value').type('Test'); - cy.getByTestId('close-var-manager-modal').click(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('open-variable-management').click(); - cy.getByTestId('open-edit-variables-btn').click(); - cy.getByTestId('variable-default-value').should('have.value', 'Test'); - }); - - it('should not throw error for using array variable with index greater than 0', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - - dragAndDrop('email'); - cy.waitForNetworkIdle(500); - - editChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('emailSubject').type('this is email subject'); - cy.getByTestId('email-editor').getByTestId('editor-row').click(); - cy.getByTestId('editable-text-content').clear().type('This tests array var {{array.[1].name}}', { - parseSpecialCharSequences: false, - }); - - cy.getByTestId('var-items-array').contains('array'); - cy.getByTestId('var-items-array').contains('object'); - }); - - it('should create email notification', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - - cy.getByTestId('settings-page').click(); - - cy.getByTestId('title').first().clear().type('Test Notification Title'); - cy.getByTestId('description').type('This is a test description for a test title'); - cy.get('body').click(); - - addAndEditChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('email-editor').getByTestId('editor-row').click(); - cy.getByTestId('control-add').click(); - cy.getByTestId('add-btn-block').click(); - cy.getByTestId('button-block-wrapper').should('be.visible'); - cy.getByTestId('button-block-wrapper').find('button').click(); - cy.getByTestId('button-text-input').clear().type('Example Text Of {{ctaName}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('button-block-wrapper').find('button').contains('Example Text Of {{ctaName}}'); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - - cy.getByTestId('email-editor').getByTestId('editor-row').eq(1).click(); - cy.getByTestId('control-add').click(); - cy.getByTestId('add-text-block').click({ force: true }); - cy.getByTestId('editable-text-content').eq(1).clear().type('This another text will be {{customVariable}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('editable-text-content').eq(1).click(); - - cy.getByTestId('settings-row-btn').eq(1).invoke('show').click(); - cy.getByTestId('remove-row-btn').click(); - cy.getByTestId('button-block-wrapper').should('not.exist'); - - cy.getByTestId('emailSubject').type('this is email subject'); - - goBack(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.getByTestId('node-emailSelector') - .getByTestId('workflow-node-subtitle') - .should('contain.text', 'This text is written from a test {{firstName}}'); - cy.getByTestId('get-snippet-btn').click(); - cy.getByTestId('trigger-code-snippet').should('be.visible'); - cy.getByTestId('trigger-code-snippet').contains('test-notification-title'); - cy.getByTestId('trigger-code-snippet').contains('firstName:'); - cy.getByTestId('trigger-code-snippet').contains('customVariable:'); - }); - - it('should not reset action steps values on update', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - - dragAndDrop('digest'); - - cy.clickWorkflowNode('node-digestSelector'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('digest-send-options').click(); - cy.getByTestId('time-amount').clear().type('30'); - cy.getByTestId('time-unit').click(); - cy.get('.mantine-Select-item').contains('sec (s)').click(); - - cy.getByTestId('digest-group-by-options').click(); - - cy.getByTestId('batch-key').type('id'); - - cy.getByTestId('digest-send-options').click(); - - cy.clickWorkflowNode('node-digestSelector'); - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('digest-send-options').click(); - - cy.getByTestId('time-amount').should('have.value', '30'); - cy.getByTestId('batch-key').should('have.value', 'id'); - cy.getByTestId('time-unit').should('have.value', 'sec (s)'); - }); - - it('should add digest node', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - - cy.getByTestId('settings-page').click(); - cy.getByTestId('title').first().clear().type('Test Notification Title'); - cy.getByTestId('description').type('This is a test description for a test title'); - cy.get('body').click(); - - addAndEditChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('email-editor').getByTestId('editor-row').click(); - cy.getByTestId('control-add').click(); - cy.getByTestId('add-btn-block').click(); - cy.getByTestId('button-block-wrapper').should('be.visible'); - cy.getByTestId('button-block-wrapper').find('button').click(); - cy.getByTestId('button-text-input').clear().type('Example Text Of {{ctaName}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('button-block-wrapper').find('button').contains('Example Text Of {{ctaName}}'); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - - cy.getByTestId('email-editor').getByTestId('editor-row').eq(1).click(); - cy.getByTestId('control-add').click(); - cy.getByTestId('add-text-block').click({ force: true }); - cy.getByTestId('editable-text-content').eq(1).clear().type('This another text will be {{customVariable}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('editable-text-content').eq(1).click(); - - cy.getByTestId('settings-row-btn').eq(1).invoke('show').click(); - cy.getByTestId('remove-row-btn').click(); - cy.getByTestId('button-block-wrapper').should('not.exist'); - - cy.getByTestId('emailSubject').type('this is email subject'); - - goBack(); - cy.waitForNetworkIdle(500); - - dragAndDrop('digest'); - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode('node-digestSelector'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('digest-send-options').click(); - cy.getByTestId('time-amount').clear().type('20'); - cy.getByTestId('time-unit').click(); - cy.get('.mantine-Select-item').contains('min (s)').click(); - - cy.getByTestId('digest-group-by-options').click(); - - cy.getByTestId('batch-key').type('id'); - - cy.getByTestId('digest-send-options').click(); - - cy.getByTestId('backoff-switch').click({ - force: true, - }); - - cy.getByTestId('backoff-amount').clear().type('20'); - - cy.getByTestId('time-unit-backoff').click(); - cy.get('.mantine-Select-item').contains('min (s)').click(); - - goBack(); - - cy.clickWorkflowNode('node-digestSelector'); - - cy.getByTestId('digest-send-options').click(); - - cy.getByTestId('time-amount').should('have.value', '20'); - cy.getByTestId('batch-key').should('have.value', 'id'); - cy.getByTestId('backoff-amount').should('have.value', '20'); - cy.getByTestId('time-unit').should('have.value', 'min (s)'); - cy.getByTestId('time-unit-backoff').should('have.value', 'min (s)'); - cy.getByTestId('node-digestSelector') - .getByTestId('workflow-node-subtitle') - .should('contain.text', 'after 20 minutes'); - }); - - it('should create and edit group id', function () { - const template = this.session.templates[0]; - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('groupSelector').click(); - cy.getByTestId('groupSelector').clear(); - cy.getByTestId('groupSelector').type('New Test Category'); - cy.getByTestId('submit-category-btn').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('groupSelector').should('have.value', 'New Test Category'); - - goBack(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - - cy.visit('/workflows'); - cy.getByTestId('template-edit-link'); - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - cy.getByTestId('settings-page').click(); - cy.getByTestId('groupSelector').should('have.value', 'New Test Category'); - }); - - it('should show delay settings in side menu', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test Added Delay'); - goBack(); - cy.waitForNetworkIdle(500); - cy.getByTestId('button-add').click(); - cy.getByTestId('add-delay-node').click(); - cy.clickWorkflowNode('node-delaySelector'); - cy.getByTestId('delay-type').should('be.visible'); - }); - - it('should be able to add huge amount of nodes.', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Test 15 Nodes'); - goBack(); - cy.waitForNetworkIdle(500); - - for (let i = 0; i < 15; i++) { - cy.getByTestId('button-add').last().click({ force: true }); - cy.getByTestId('add-email-node').click(); - } - - editChannel('email', true); - - cy.waitForNetworkIdle(500); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('emailSubject').type('this is email subject'); - cy.getByTestId('emailPreheader').type('this is email preheader'); - cy.waitForNetworkIdle(500); - goBack(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('node-emailSelector').should('have.length', 15); - - editChannel('email', true); - cy.waitForNetworkIdle(500); - - cy.getByTestId('editable-text-content').contains('This text is written from a test'); - cy.getByTestId('emailSubject').should('have.value', 'this is email subject'); - cy.getByTestId('emailPreheader').should('have.value', 'this is email preheader'); - }); -}); - -function awaitGetContains(getSelector: string, contains: string) { - return cy - .waitUntil( - () => - cy - .get(getSelector) - .contains(contains) - .as('awaitedElement') - .wait(1) // for some reason this is needed, otherwise next line returns `true` even if click() fails due to detached element in the next step - .then(($el) => { - return Cypress.dom.isAttached($el); - }), - { timeout: 5000, interval: 500 } - ) - .get('@awaitedElement'); -} diff --git a/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts b/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts deleted file mode 100644 index 33d5fadbfaf6..000000000000 --- a/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -describe('Debugging - test trigger', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should open test trigger modal', function () { - const template = this.session.templates[0]; - const userId = this.session.user.id; - - cy.intercept('GET', '/v1/notification-templates/*').as('notification-templates'); - - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/edit/' + template._id); - }); - - cy.wait('@notification-templates'); - - cy.getByTestId('node-triggerSelector').click({ force: true }); - cy.getByTestId('workflow-sidebar').should('be.visible'); - cy.getByTestId('workflow-sidebar').getByTestId('test-trigger-to-param').contains(`"subscriberId": "${userId}"`); - }); - - it('should not test trigger on error ', function () { - const template = this.session.templates[0]; - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - cy.getByTestId('node-triggerSelector').click({ force: true }); - - cy.getByTestId('workflow-sidebar').should('be.visible'); - cy.getByTestId('workflow-sidebar').getByTestId('test-trigger-to-param').type('{backspace}'); - cy.getByTestId('workflow-sidebar').getByTestId('test-trigger-payload-param').click(); - cy.getByTestId('workflow-sidebar').getByTestId('test-trigger-btn').should('be.disabled'); - cy.getByTestId('workflow-sidebar').should('be.visible'); - cy.getByTestId('workflow-sidebar') - .getByTestId('test-trigger-to-param') - .should('have.class', 'mantine-JsonInput-invalid'); - }); -}); diff --git a/apps/web/cypress/tests/notification-editor/drag-and-drop.spec.ts b/apps/web/cypress/tests/notification-editor/drag-and-drop.spec.ts deleted file mode 100644 index acccb1e9ed35..000000000000 --- a/apps/web/cypress/tests/notification-editor/drag-and-drop.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { clickWorkflow, dragAndDrop, fillBasicNotificationDetails, goBack } from '.'; - -describe('Workflow Editor - Drag and Drop', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should drag and drop channel', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test drag and drop channel'); - goBack(); - dragAndDrop('inApp'); - dragAndDrop('inApp'); - - cy.getByTestId('node-inAppSelector').last().parent().click(); - }); - - it('should not be able to drop when not on last node', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test only drop on last node'); - goBack(); - dragAndDrop('inApp'); - dragAndDrop('email'); - cy.getByTestId('node-emailSelector').should('not.exist'); - cy.get('.react-flow__node').should('have.length', 3); - }); - - it('should add a step with plus button', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test Plus Button'); - goBack(); - cy.getByTestId('button-add').click(); - cy.getByTestId('add-sms-node').click(); - cy.get('.react-flow__node').should('have.length', 3); - cy.get('.react-flow__node').first().should('contain', 'Workflow trigger').next().should('contain', 'SMS'); - }); -}); diff --git a/apps/web/cypress/tests/notification-editor/index.ts b/apps/web/cypress/tests/notification-editor/index.ts deleted file mode 100644 index 38cb273ef5b6..000000000000 --- a/apps/web/cypress/tests/notification-editor/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -export type Channel = 'inApp' | 'email' | 'sms' | 'chat' | 'push' | 'digest' | 'delay'; - -export function addAndEditChannel(channel: Channel) { - cy.waitForNetworkIdle(500); - - goBack(); - dragAndDrop(channel); - cy.waitForNetworkIdle(500); - editChannel(channel, true); -} - -export function dragAndDrop(channel: Channel, dropTestId = 'addNodeButton') { - const dataTransfer = new DataTransfer(); - - cy.getByTestId(`dnd-${channel}Selector`).trigger('dragstart', { dataTransfer }); - cy.getByTestId(dropTestId).parent().trigger('drop', { dataTransfer }); -} - -export function editChannel(channel: Channel, last = false) { - cy.clickWorkflowNode(`node-${channel}Selector`, last); - if (['inApp', 'email', 'sms', 'chat', 'push'].includes(channel)) { - cy.getByTestId('edit-action').click(); - } -} - -export function goBack() { - cy.getByTestId('sidebar-close').click(); - cy.waitForNetworkIdle(500); -} - -export function fillBasicNotificationDetails(title?: string) { - cy.waitForNetworkIdle(500); - cy.getByTestId('settings-page').click(); - cy.getByTestId('title') - .first() - .clear() - .type(title || 'Test Notification Title') - .blur(); - cy.getByTestId('description').type('This is a test description for a test title').blur(); -} - -export function clickWorkflow() { - cy.getByTestId('workflowButton').click(); -} - -export function awaitGetContains(getSelector: string, contains: string) { - return cy - .waitUntil( - () => - cy - .get(getSelector) - .contains(contains) - .as('awaitedElement') - .wait(1) // for some reason this is needed, otherwise next line returns `true` even if click() fails due to detached element in the next step - .then(($el) => { - return Cypress.dom.isAttached($el); - }), - { timeout: 5000, interval: 500 } - ) - .get('@awaitedElement'); -} diff --git a/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts b/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts deleted file mode 100644 index 12a5af166fdc..000000000000 --- a/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts +++ /dev/null @@ -1,597 +0,0 @@ -import { addAndEditChannel, clickWorkflow, dragAndDrop, editChannel, fillBasicNotificationDetails, goBack } from '.'; - -/** - * The tests from this file were moved to the corresponding Playwright file apps/web/tests/main-functionality.spec.ts. - * @deprecated - */ -describe.skip('Workflow Editor - Main Functionality', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should not reset data when switching channel types', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test not reset data when switching channel types'); - cy.waitForNetworkIdle(500); - addAndEditChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.get('.monaco-editor textarea:first', { timeout: 7000 }) - .parent() - .click() - .find('textarea') - .type('{{firstName}} someone assigned you to {{taskName}}', { - parseSpecialCharSequences: false, - force: true, - }); - - goBack(); - cy.waitForNetworkIdle(500); - - dragAndDrop('email'); - cy.waitForNetworkIdle(500); - editChannel('email'); - cy.waitForNetworkIdle(500); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('emailSubject').type('this is email subject'); - cy.getByTestId('emailPreheader').type('this is email preheader'); - cy.waitForNetworkIdle(500); - goBack(); - - editChannel('inApp'); - cy.waitForNetworkIdle(500); - cy.get('.monaco-editor textarea:first') - .parent() - .click() - .contains('{{firstName}} someone assigned you to {{taskName}}'); - - goBack(); - cy.waitForNetworkIdle(500); - editChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('editable-text-content').contains('This text is written from a test'); - cy.getByTestId('emailSubject').should('have.value', 'this is email subject'); - cy.getByTestId('emailPreheader').should('have.value', 'this is email preheader'); - }); - - it('should update to empty data when switching from editor to customHtml', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test Notification'); - cy.waitForNetworkIdle(500); - - addAndEditChannel('email'); - - cy.waitForNetworkIdle(500); - cy.getByTestId('editable-text-content').clear().type('This text is written from a test {{firstName}}', { - parseSpecialCharSequences: false, - }); - cy.getByTestId('emailSubject').type('this is email subject'); - cy.waitForNetworkIdle(500); - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('editor-type-selector') - .find('.mantine-Tabs-tabsList') - .contains('Custom Code', { matchCase: false }) - .click(); - - cy.getByTestId('emailSubject').clear().type('new email subject'); - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('side-nav-templates-link').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('template-edit-link'); - cy.getByTestId('notifications-template') - .get('tbody tr td') - .contains('Test notification', { - matchCase: false, - }) - .click(); - cy.waitForNetworkIdle(500); - - editChannel('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('editor-type-selector') - .find('.mantine-Tabs-tabsList') - .find('[data-active="true"]') - .contains('Custom Code', { matchCase: false }); - }); - - it('should save avatar enabled and content for in app', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - fillBasicNotificationDetails('Test save avatar'); - cy.waitForNetworkIdle(500); - addAndEditChannel('inApp'); - cy.waitForNetworkIdle(500); - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').type('new content for notification', { - force: true, - }); - - cy.getByTestId('enable-add-avatar').click(); - cy.getByTestId('choose-avatar-btn').click(); - cy.getByTestId('avatar-icon-info').click(); - - cy.getByTestId('notification-template-submit-btn').click(); - - cy.getByTestId('enabled-avatar').should('be.checked'); - cy.getByTestId('avatar-icon-info').should('be.visible'); - }); - - it('should edit in-app notification', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('name-input').first().should('have.value', template.name); - - editChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.get('.monaco-editor textarea:first').parent().click().contains('Test content for {{firstName}}'); - - goBack(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('name-input').clear().type('This is the new notification title'); - - editChannel('inApp', true); - cy.waitForNetworkIdle(500); - - cy.getByTestId('use-feeds-checkbox').click(); - cy.getByTestId('feed-button-1').click({ force: true }); - - cy.get('.monaco-editor textarea:first') - .parent() - .click() - .type('{cmd}a') - .find('textarea') - .clear({ - force: true, - }) - .type('new content for notification', { - force: true, - }); - goBack(); - cy.getByTestId('notification-template-submit-btn').click(); - - cy.visit('/workflows'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('template-edit-link'); - cy.getByTestId('notifications-template').get('tbody tr td').contains('This is the new', { - matchCase: false, - }); - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/edit/' + template._id); - }); - - cy.waitForNetworkIdle(500); - - editChannel('inApp', true); - cy.waitForNetworkIdle(500); - - cy.getByTestId('feed-button-1-checked'); - cy.getByTestId('create-feed-input').type('test4'); - cy.getByTestId('add-feed-button').click(); - cy.getByTestId('feed-button-2-checked'); - }); - - it('should unset feedId for in app step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - editChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('use-feeds-checkbox').should('be.checked'); - cy.getByTestId('use-feeds-checkbox').click(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.visit('/workflows'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('notifications-template').get('tbody tr td').contains(template.name, { - matchCase: false, - }); - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/edit/' + template._id); - }); - - cy.waitForNetworkIdle(500); - editChannel('inApp'); - cy.getByTestId('use-feeds-checkbox').should('be.checked'); - }); - - it('should edit email notification', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - editChannel('email'); - - // edit email editor content - cy.getByTestId('email-editor').getByTestId('editor-row').first().click().type('{selectall}{backspace}Hello world!'); - }); - - it('should update notification active status', function () { - const template = this.session.templates[0]; - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('active-toggle-switch').get('label').contains('Active'); - cy.getByTestId('active-toggle-switch').click({ force: true }); - cy.getByTestId('active-toggle-switch').get('label').contains('Inactive'); - - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.getByTestId('settings-page').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('active-toggle-switch').get('label').contains('Inactive'); - }); - - it('should toggle active states of channels', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Test toggle active states of channels'); - - goBack(); - // Enable email from button click - dragAndDrop('email'); - cy.waitForNetworkIdle(500); - - editChannel(`email`); - - cy.getByTestId(`step-active-switch`).should('have.value', 'on'); - cy.getByTestId(`step-active-switch`).click({ force: true }); - - // enable email selector - cy.getByTestId(`step-active-switch`).click({ force: true }); - goBack(); - - dragAndDrop('inApp'); - editChannel('inApp'); - - cy.getByTestId(`step-active-switch`).should('have.value', 'on'); - }); - - it('should show trigger snippet block when editing', function () { - const template = this.session.templates[0]; - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.getByTestId('get-snippet-btn').click(); - cy.getByTestId('trigger-code-snippet').contains('test-event'); - }); - - it('should show error on node if message field is missing ', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails(); - goBack(); - dragAndDrop('email'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('node-emailSelector').getByTestId('error-circle').should('be.visible'); - editChannel('email'); - cy.waitForNetworkIdle(500); - cy.getByTestId('emailSubject').should('have.class', 'mantine-TextInput-invalid'); - - cy.getByTestId('emailSubject').type('this is email subject'); - goBack(); - cy.waitForNetworkIdle(500); - cy.getByTestId('node-emailSelector').getByTestId('error-circle').should('not.exist'); - }); - - it('should allow uploading a logo from email editor', function () { - cy.intercept('*/organizations', (r) => { - r.continue((res) => { - if (res.body) { - delete res.body.data[0].branding.logo; - } - - res.send({ body: res.body }); - }); - }); - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Test allow uploading a logo from email editor'); - - addAndEditChannel('email'); - - cy.getByTestId('upload-image-button').click(); - cy.get('.mantine-Modal-modal button').contains('Yes').click(); - - cy.location('pathname').should('equal', '/brand'); - }); - - it('should show the brand logo on main page', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Test show the brand logo on main page'); - addAndEditChannel('email'); - - cy.getByTestId('email-editor') - .getByTestId('brand-logo') - .should('have.attr', 'src', 'https://web.novu.co/static/images/logo-light.png'); - }); - - it('should support RTL text content', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Test support RTL text content'); - goBack(); - cy.waitForNetworkIdle(500); - dragAndDrop('email'); - editChannel('email'); - - cy.getByTestId('settings-row-btn').eq(0).invoke('show').click(); - cy.getByTestId('editable-text-content').should('have.css', 'text-align', 'left'); - cy.getByTestId('align-right-btn').click(); - cy.getByTestId('editable-text-content').should('have.css', 'text-align', 'right'); - }); - - it('should create an SMS channel message', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Test SMS Notification Title'); - cy.waitForNetworkIdle(500); - - addAndEditChannel('sms'); - cy.waitForNetworkIdle(500); - - cy.get('.monaco-editor textarea:first', { timeout: 7000 }) - .parent() - .click() - .find('textarea') - .type('{{firstName}} someone assigned you to {{taskName}}', { - parseSpecialCharSequences: false, - force: true, - }); - - goBack(); - cy.waitForNetworkIdle(500); - cy.getByTestId('notification-template-submit-btn').click(); - - cy.getByTestId('get-snippet-btn').click(); - cy.getByTestId('workflow-sidebar').should('be.visible'); - cy.getByTestId('workflow-sidebar').getByTestId('trigger-code-snippet').contains('test-sms-notification-title'); - cy.getByTestId('workflow-sidebar') - .getByTestId('trigger-code-snippet') - .contains("import { Novu } from '@novu/node'"); - - cy.getByTestId('workflow-sidebar').getByTestId('trigger-code-snippet').contains('taskName'); - - cy.getByTestId('workflow-sidebar').getByTestId('trigger-code-snippet').contains('firstName'); - }); - - it('should save HTML template email', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('Custom Code HTML Notification Title'); - addAndEditChannel('email'); - - cy.getByTestId('emailSubject').type('this is email subject'); - - cy.getByTestId('editor-type-selector') - .find('.mantine-Tabs-tabsList') - .contains('Custom Code', { matchCase: false }) - .click(); - - cy.get('.monaco-editor textarea:first') - .parent() - .click() - .find('textarea') - .type('Hello world code {{name}}
Test
', { parseSpecialCharSequences: false, force: true }); - - goBack(); - - editChannel('email'); - - cy.get('.monaco-editor textarea:first').parent().click().contains('Hello world code {{name}}
Test
'); - }); - - it('should redirect to the templates page when switching environments', function () { - cy.intercept('GET', '*/notification-templates?*').as('getTemplates'); - cy.intercept('GET', '*/notification-templates/*').as('getTemplate'); - cy.intercept('POST', '*/notification-templates?__source=editor').as('createTemplate'); - cy.intercept('POST', '*/changes/*/apply').as('applyChanges'); - const title = 'Environment Switching'; - - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - fillBasicNotificationDetails(title); - goBack(); - cy.getByTestId('notification-template-submit-btn').click(); - - cy.wait('@createTemplate').then((res) => { - cy.intercept('GET', '/v1/changes?promoted=false').as('unpromoted-changes'); - cy.visit('/changes'); - - cy.getByTestId('promote-btn').eq(0).click({ force: true }); - cy.wait('@applyChanges'); - - cy.getByTestId('environment-switch').find(`input[value="Production"]`).click({ force: true }); - cy.location('pathname').should('equal', `/workflows`); - cy.wait('@getTemplates'); - - cy.getByTestId('notifications-template').find('tbody tr').contains(title).click(); - cy.wait('@getTemplate'); - cy.waitForNetworkIdle(500); - cy.location('pathname').should('not.equal', `/workflows`); - - cy.getByTestId('environment-switch').find(`input[value="Development"]`).click({ force: true }); - cy.location('pathname').should('equal', `/workflows`); - }); - }); - - it('New template button should be disabled in the Production', function () { - cy.visit('/workflows'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('environment-switch').find(`input[value="Production"] ~ label`).click(); - - cy.getByTestId('create-workflow-btn').should('be.disabled'); - }); - - it('Should not allow to go to New Template page in Production', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('environment-switch').find('.mantine-SegmentedControl-controlActive'); - cy.getByTestId('environment-switch').find(`input[value="Production"] ~ label`).click(); - - cy.location('pathname').should('equal', `/workflows`); - }); - - it('should save Cta buttons state in inApp channel', function () { - cy.visit('/workflows/create'); - cy.waitForNetworkIdle(500); - - fillBasicNotificationDetails('In App CTA Button'); - cy.waitForNetworkIdle(500); - addAndEditChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.get('.monaco-editor textarea:first', { timeout: 7000 }).parent().click().find('textarea').type('Text content', { - force: true, - }); - - cy.getByTestId('control-add').first().click(); - cy.getByTestId('template-container-click-area').eq(0).click(); - - goBack(); - cy.waitForNetworkIdle(500); - cy.getByTestId('notification-template-submit-btn').click(); - - cy.visit('/workflows'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('notifications-template') - .get('tbody tr td') - .contains('In App CTA Button', { - matchCase: false, - }) - .click(); - - cy.waitForNetworkIdle(500); - - editChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('template-container').first().find('input').should('have.length', 1); - - cy.getByTestId('remove-button-icon').click(); - - goBack(); - cy.waitForNetworkIdle(500); - - editChannel('inApp'); - cy.waitForNetworkIdle(500); - - cy.getByTestId('control-add').first(); - }); - - it('should load successfully the recently created notification template, when going back from editor -> templates list -> editor', function () { - cy.intercept('GET', '*/notification-templates**').as('getNotificationTemplates'); - cy.intercept('GET', '*/notification-templates/*').as('getNotificationTemplate'); - cy.visit('/workflows'); - cy.wait('@getNotificationTemplates'); - - cy.getByTestId('create-workflow-btn').click(); - cy.getByTestId('create-workflow-blank').click(); - cy.wait('@getNotificationTemplate'); - - fillBasicNotificationDetails('Test notification'); - - addAndEditChannel('inApp'); - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').type('Test in-app', { - force: true, - }); - - addAndEditChannel('email'); - cy.getByTestId('editable-text-content').clear().type('Test email'); - cy.getByTestId('emailSubject').type('this is email subject'); - cy.getByTestId('emailPreheader').type('this is email preheader'); - goBack(); - - cy.getByTestId('notification-template-submit-btn').click(); - - cy.getByTestId('side-nav-templates-link').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('template-edit-link'); - cy.getByTestId('notifications-template') - .get('tbody tr td') - .contains('Test notification', { - matchCase: false, - }) - .click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId(`node-inAppSelector`).should('exist'); - cy.getByTestId(`node-emailSelector`).should('exist'); - }); - - it('should load successfully the same notification template, when going back from templates list -> editor -> templates list -> editor', function () { - cy.intercept('GET', '*/notification-templates**').as('getNotificationTemplates'); - cy.intercept('GET', '*/notification-templates/*').as('getNotificationTemplate'); - cy.visit('/workflows'); - cy.wait('@getNotificationTemplates'); - - const template = this.session.templates[0]; - cy.getByTestId('notifications-template') - .get('tbody tr td') - .contains(template.name, { - matchCase: false, - }) - .click(); - cy.wait('@getNotificationTemplate'); - cy.getByTestId(`node-inAppSelector`).should('exist'); - cy.getByTestId(`node-emailSelector`).should('exist'); - - cy.getByTestId('side-nav-templates-link').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('notifications-template') - .get('tbody tr td') - .contains(template.name, { - matchCase: false, - }) - .click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId(`node-inAppSelector`).should('exist'); - cy.getByTestId(`node-emailSelector`).should('exist'); - }); -}); diff --git a/apps/web/cypress/tests/notification-editor/steps-actions.spec.ts b/apps/web/cypress/tests/notification-editor/steps-actions.spec.ts deleted file mode 100644 index abd459104ff4..000000000000 --- a/apps/web/cypress/tests/notification-editor/steps-actions.spec.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { clickWorkflow, dragAndDrop, editChannel, goBack } from '.'; - -describe('Workflow Editor - Steps Actions', function () { - beforeEach(function () { - cy.initializeSession({ showOnBoardingTour: false }).as('session'); - }); - - const interceptEditTemplateRequests = () => { - cy.intercept('**/notification-templates/**').as('getTemplateToEdit'); - cy.intercept('**/notification-groups').as('getNotificationGroups'); - }; - - const waitForEditTemplateRequests = () => { - cy.wait('@getTemplateToEdit'); - cy.wait('@getNotificationGroups'); - }; - - it('should be able to delete a step', function () { - interceptEditTemplateRequests(); - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - waitForEditTemplateRequests(); - - cy.get('.react-flow__node').should('have.length', 4); - cy.clickWorkflowNode(`node-inAppSelector`); - cy.waitForNetworkIdle(500); - cy.getByTestId('step-actions-menu').click().getByTestId('delete-step-action').click(); - cy.get('.mantine-Modal-modal button').contains('Delete step').click(); - cy.getByTestId(`node-inAppSelector`).should('not.exist'); - cy.get('.react-flow__node').should('have.length', 3); - cy.get('.react-flow__node').first().should('contain', 'Workflow trigger').next().should('contain', 'Email'); - cy.getByTestId('notification-template-submit-btn').click(); - - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.get('.react-flow__node').should('have.length', 3); - }); - - it('should show add step in sidebar after delete', function () { - interceptEditTemplateRequests(); - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - waitForEditTemplateRequests(); - - cy.get('.react-flow__node').should('have.length', 4); - cy.getByTestId('node-inAppSelector') - .getByTestId('channel-node') - .first() - .trigger('mouseover', { force: true }) - .getByTestId('step-actions-menu') - .click(); - cy.getByTestId('node-inAppSelector').getByTestId('channel-node').first().getByTestId('delete-step-action').click(); - cy.get('.mantine-Modal-modal button').contains('Delete step').click(); - cy.getByTestId(`node-inAppSelector`).should('not.exist'); - cy.get('.react-flow__node').should('have.length', 3); - cy.getByTestId('drag-side-menu').contains('Channels'); - }); - - it('should show add step in sidebar after a delete of a step with side settings ', function () { - interceptEditTemplateRequests(); - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - waitForEditTemplateRequests(); - dragAndDrop('digest'); - cy.get('.react-flow__node').should('have.length', 5); - - cy.clickWorkflowNode('node-digestSelector'); - cy.getByTestId('editor-sidebar-delete').click(); - - cy.get('.mantine-Modal-modal button').contains('Delete step').click(); - cy.getByTestId(`node-digestSelector`).should('not.exist'); - cy.get('.react-flow__node').should('have.length', 4); - cy.getByTestId('drag-side-menu').contains('Channels'); - }); - - it('should keep steps order on reload', function () { - interceptEditTemplateRequests(); - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - waitForEditTemplateRequests(); - - dragAndDrop('sms'); - - editChannel('sms'); - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').type('new content for sms', { - parseSpecialCharSequences: false, - force: true, - }); - - cy.getByTestId('notification-template-submit-btn').click(); - - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - - cy.get('.react-flow__node').should('have.length', 5); - cy.get('.react-flow__node') - .first() - .should('contain', 'Workflow trigger') - .next() - .should('contain', 'In-App') - .next() - .should('contain', 'Email') - .next() - .should('contain', 'SMS'); - }); - - it('should be able to disable step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - editChannel(`inApp`); - cy.getByTestId(`step-active-switch`).get('label').contains('Active'); - cy.getByTestId(`step-active-switch`).click({ force: true }); - goBack(); - - editChannel(`inApp`); - - cy.getByTestId(`step-active-switch`).get('label').contains('Inactive'); - }); - - it('should be able to toggle ShouldStopOnFailSwitch', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - editChannel(`inApp`); - cy.getByTestId(`step-should-stop-on-fail-switch`).get('label').contains('Stop if step fails'); - cy.getByTestId(`step-should-stop-on-fail-switch`).click({ force: true }); - goBack(); - - editChannel(`inApp`); - cy.getByTestId(`step-should-stop-on-fail-switch`).should('be.checked'); - }); - - it('should be able to add filters to a digest step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - dragAndDrop('digest'); - - cy.clickWorkflowNode(`node-digestSelector`); - - cy.getByTestId('editor-sidebar-add-conditions').click(); - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - - cy.get('.mantine-Select-item').contains('Subscriber').click(); - - cy.getByTestId('conditions-form-key').type('filter-key'); - cy.getByTestId('conditions-form-operator').click(); - cy.get('.mantine-Select-item').contains('Equal').click(); - cy.getByTestId('conditions-form-value').type('filter-value'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - cy.clickWorkflowNode(`node-digestSelector`); - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - }); - - it('should be able to add filters to a delay step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - dragAndDrop('delay'); - - cy.clickWorkflowNode(`node-delaySelector`); - - cy.getByTestId('editor-sidebar-add-conditions').click(); - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - - cy.get('.mantine-Select-item').contains('Subscriber').click(); - - cy.getByTestId('conditions-form-key').type('filter-key'); - cy.getByTestId('conditions-form-operator').click(); - cy.get('.mantine-Select-item').contains('Equal').click(); - cy.getByTestId('conditions-form-value').type('filter-value'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.waitForNetworkIdle(500); - cy.visit('/workflows/edit/' + template._id); - cy.waitForNetworkIdle(500); - cy.clickWorkflowNode(`node-delaySelector`); - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - }); - - it('should be able to add filters to a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Subscriber').click(); - - cy.getByTestId('conditions-form-key').type('filter-key'); - cy.getByTestId('conditions-form-operator').click(); - cy.get('.mantine-Select-item').contains('Equal').click(); - cy.getByTestId('conditions-form-value').type('filter-value'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('1'); - }); - - it('should be able to add read/seen filters to a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-emailSelector`); - - cy.getByTestId('add-conditions-action').click(); - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Previous step').click(); - - cy.getByTestId('previous-step-dropdown').click(); - cy.get('.mantine-Select-item').contains('In-App').click(); - cy.getByTestId('previous-step-type-dropdown').click(); - cy.get('.mantine-Select-item').contains('Seen').click(); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('1'); - }); - - it('should be able to not add read/seen filters to first step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Previous step').should('not.exist'); - }); - - it('should be able to remove filters for a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-key').type('filter-key'); - cy.getByTestId('conditions-form-operator').click(); - cy.get('.mantine-Select-item').contains('Equal').click(); - cy.getByTestId('conditions-form-value').type('filter-value'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('1'); - - cy.getByTestId('add-conditions-action').click(); - cy.getByTestId('conditions-row-btn').click(); - cy.getByTestId('conditions-row-delete').click(); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').should('not.contain', '1'); - }); - - it('should be able to add webhook filter for a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Webhook').click(); - - cy.getByTestId('webhook-filter-url-input').type('www.example.com'); - cy.getByTestId('conditions-form-key').type('filter-key'); - cy.getByTestId('conditions-form-operator').click(); - cy.get('.mantine-Select-item').contains('Equal').click(); - cy.getByTestId('conditions-form-value').type('filter-value'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('1'); - }); - - it('should be able to add online right now filter for a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Is online').click(); - cy.getByTestId('online-now-value-dropdown').click(); - cy.get('.mantine-Select-item').contains('Yes').click(); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('1'); - }); - - it('should be able to add online in the last X time period filter for a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - - cy.getByTestId('add-new-condition').click(); - - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Last time was online').click(); - cy.getByTestId('online-in-last-operator-dropdown').click(); - cy.get('.mantine-Select-item').contains('Hours').click(); - cy.getByTestId('online-in-last-value-input').type('1'); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('1'); - }); - - it('should be able to add multiple filters to a particular step', function () { - const template = this.session.templates[0]; - - cy.visit('/workflows/edit/' + template._id); - - cy.waitForNetworkIdle(500); - - cy.clickWorkflowNode(`node-inAppSelector`); - - cy.getByTestId('add-conditions-action').click(); - - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-on').click(); - cy.get('.mantine-Select-item').contains('Subscriber').click(); - - cy.getByTestId('conditions-form-key').type('filter-key'); - cy.getByTestId('conditions-form-operator').click(); - cy.get('.mantine-Select-item').contains('Equal').click(); - cy.getByTestId('conditions-form-value').type('filter-value'); - - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-on').eq(1).click(); - cy.get('.mantine-Select-item').contains('Is online').click(); - cy.getByTestId('online-now-value-dropdown').click(); - cy.get('.mantine-Select-item').contains('Yes').click(); - - cy.getByTestId('apply-conditions-btn').click(); - - cy.getByTestId('add-conditions-action').contains('2'); - }); - - it('should re-render content on between step click', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.waitForNetworkIdle(500); - - dragAndDrop('sms'); - - dragAndDrop('delay'); - - dragAndDrop('sms'); - - cy.waitForNetworkIdle(500); - - const firstContent = 'first content for sms'; - const lastContent = 'last content for sms'; - - cy.clickWorkflowNode(`node-smsSelector`); - cy.getByTestId('edit-action').click(); - - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').type(firstContent, { - force: true, - }); - - cy.clickWorkflowNode(`node-smsSelector`, true); - cy.getByTestId('edit-action').click(); - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').type(lastContent, { - force: true, - }); - cy.clickWorkflowNode(`node-smsSelector`); - cy.getByTestId('edit-action').click(); - cy.get('.monaco-editor textarea:first').parent().click().contains(firstContent); - - cy.clickWorkflowNode(`node-smsSelector`, true); - cy.getByTestId('edit-action').click(); - cy.get('.monaco-editor textarea:first').parent().click().contains(lastContent); - }); -}); diff --git a/apps/web/cypress/tests/notification-editor/variants.spec.ts b/apps/web/cypress/tests/notification-editor/variants.spec.ts deleted file mode 100644 index e282017a3233..000000000000 --- a/apps/web/cypress/tests/notification-editor/variants.spec.ts +++ /dev/null @@ -1,1082 +0,0 @@ -import { Channel, dragAndDrop, editChannel, goBack } from '.'; - -const EDITOR_TEXT = 'Hello, world!'; -const VARIANT_EDITOR_TEXT = 'Hello, world from Variant!'; -const SUBJECT_LINE = 'Novu test'; -const PUSH_TITLE = 'Push test'; - -describe('Workflow Editor - Variants', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - const createWorkflow = (title: string) => { - cy.intercept('GET', '**/notification-templates/**').as('getWorkflow'); - cy.intercept('PUT', '**/notification-templates/**').as('updateWorkflow'); - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - cy.wait('@getWorkflow'); - cy.getByTestId('name-input').first().clear().type(title).blur(); - }; - - const fillInAppEditorContentWith = (text: string) => { - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').clear({ force: true }).type(text, { - parseSpecialCharSequences: false, - force: true, - }); - }; - - const fillEmailEditorContentWith = (subjectLine: string, content: string) => { - cy.getByTestId('emailSubject').clear().type(subjectLine, { - parseSpecialCharSequences: false, - force: true, - }); - cy.getByTestId('editable-text-content').clear().type(content, { - parseSpecialCharSequences: false, - }); - }; - - const fillSmsEditorContentWith = (content: string) => { - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').clear({ force: true }).type(content, { - parseSpecialCharSequences: false, - force: true, - }); - }; - - const fillChatEditorContentWith = (content: string) => { - cy.get('.monaco-editor textarea:first').parent().click().find('textarea').clear({ force: true }).type(content, { - parseSpecialCharSequences: false, - force: true, - }); - }; - - const fillPushEditorContentWith = (title: string, content: string) => { - cy.get('[data-test-id=push-title-container] .monaco-editor textarea:first') - .parent() - .click() - .find('textarea') - .clear({ force: true }) - .type(title, { - parseSpecialCharSequences: false, - force: true, - }); - cy.get('[data-test-id=push-content-container] .monaco-editor textarea:first') - .parent() - .click() - .find('textarea') - .clear({ force: true }) - .type(content, { - parseSpecialCharSequences: false, - force: true, - }); - }; - - const showStepActions = (channel: Channel) => { - cy.getByTestId(`node-${channel}Selector`).parent().trigger('mouseover'); - }; - - const addVariantActionClick = (channel: Channel) => { - cy.getByTestId(`node-${channel}Selector`) - .getByTestId('step-actions-menu') - .click() - .getByTestId('add-variant-action') - .click(); - }; - - const addConditions = () => { - cy.getByTestId('add-new-condition').click(); - cy.getByTestId('conditions-form-key').last().type('test'); - cy.getByTestId('conditions-form-value').last().type('test'); - cy.getByTestId('apply-conditions-btn').click({ force: true }); - }; - - const checkTheVariantsList = (title: string, content: string) => { - cy.getByTestId('variants-list-sidebar').should('be.visible'); - cy.getByTestId(`variant-item-card-0`).contains(title); - cy.getByTestId(`variant-item-card-0`).contains(content); - cy.getByTestId(`variant-item-card-0`).getByTestId('conditions-action').contains('1'); - cy.getByTestId(`variant-root-card`).should('be.visible'); - }; - - const fillEditorContent = (channel: Channel, isVariant = false) => { - switch (channel) { - case 'inApp': - fillInAppEditorContentWith(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'email': - fillEmailEditorContentWith(SUBJECT_LINE, isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'sms': - fillSmsEditorContentWith(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'chat': - fillChatEditorContentWith(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'push': - fillPushEditorContentWith(PUSH_TITLE, isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - } - }; - - const checkEditorContent = (channel: Channel, isVariant = false) => { - switch (channel) { - case 'inApp': - cy.get('.monaco-editor textarea:first') - .parent() - .click() - .contains(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'email': - cy.getByTestId('emailSubject').should('have.value', SUBJECT_LINE); - cy.getByTestId('email-editor').contains(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'sms': - cy.get('.monaco-editor textarea:first') - .parent() - .click() - .contains(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'chat': - cy.get('.monaco-editor textarea:first') - .parent() - .click() - .contains(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - case 'push': - cy.get('[data-test-id=push-title-container] .monaco-editor textarea:first') - .parent() - .click() - .contains(PUSH_TITLE); - cy.get('[data-test-id=push-content-container] .monaco-editor textarea:first') - .parent() - .click() - .contains(isVariant ? VARIANT_EDITOR_TEXT : EDITOR_TEXT); - break; - } - }; - - const clearEditorContent = (channel: Channel) => { - switch (channel) { - case 'inApp': - cy.get('.monaco-editor textarea:first').parent().click().type('{cmd}a').find('textarea').clear({ - force: true, - }); - break; - case 'email': - cy.getByTestId('emailSubject').clear(); - cy.getByTestId('email-editor').clear(); - break; - case 'sms': - cy.get('.monaco-editor textarea:first').parent().click().type('{cmd}a').find('textarea').clear({ - force: true, - }); - break; - case 'chat': - cy.get('.monaco-editor textarea:first').parent().click().type('{cmd}a').find('textarea').clear({ - force: true, - }); - break; - case 'push': - cy.get('[data-test-id=push-title-container] .monaco-editor textarea:first') - .parent() - .click() - .find('textarea') - .type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true }) - .clear({ - force: true, - }); - - cy.get('[data-test-id=push-content-container] .monaco-editor textarea:first') - .parent() - .click() - .find('textarea') - .type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true }) - .clear({ - force: true, - }); - break; - } - }; - - const addVariantForChannel = (channel: Channel, variantName: string) => { - createWorkflow(`Test Add Variant Flow for ${channel}`); - - // drag and edit the channel - dragAndDrop(channel); - editChannel(channel); - - // fill the editor content - fillEditorContent(channel); - goBack(); - - // add the variant - showStepActions(channel); - addVariantActionClick(channel); - - // add conditions for the variant - addConditions(); - - // should land in the editor and has the root step content shown - checkEditorContent(channel); - fillEditorContent(channel, true); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - goBack(); - - // should have the variant shown in the list - checkTheVariantsList(variantName, VARIANT_EDITOR_TEXT); - - cy.reload(); - - // should successfully save the variants - checkTheVariantsList(variantName, VARIANT_EDITOR_TEXT); - goBack(); - - cy.getByTestId(`node-${channel}Selector`).getByTestId('variants-count').contains('1 variant'); - }; - - const checkStepActions = (channel: Channel) => { - showStepActions(channel); - cy.getByTestId('step-actions').should('be.visible'); - cy.getByTestId('add-conditions-action').should('be.visible'); - cy.getByTestId('edit-action').should('be.visible'); - cy.getByTestId('step-actions-menu').should('be.visible').click(); - cy.getByTestId('step-actions-menu').getByTestId('add-variant-action').should('be.visible'); - cy.getByTestId('step-actions-menu').getByTestId('delete-step-action').should('be.visible'); - }; - - const checkEditorActions = (isVariant = false) => { - if (!isVariant) { - cy.getByTestId('editor-sidebar-add-variant').should('be.visible'); - cy.getByTestId('editor-sidebar-add-conditions').should('be.visible'); - } else { - cy.getByTestId('editor-sidebar-edit-conditions').should('be.visible'); - } - cy.getByTestId('editor-sidebar-delete').should('be.visible'); - }; - - const navigateAndPromoteAllChanges = () => { - cy.getByTestId('side-nav-changes-link').click(); - cy.waitForNetworkIdle(500); - cy.awaitAttachedGetByTestId('promote-all-btn').click(); - }; - - const navigateAndOpenFirstWorkflow = () => { - cy.getByTestId('side-nav-templates-link').click(); - cy.waitForNetworkIdle(500); - cy.getByTestId('notifications-template').find('tbody tr').first().click(); - cy.wait('@getWorkflow'); - }; - - const switchEnvironment = (environment: 'Production' | 'Development') => { - cy.getByTestId('environment-switch').find(`input[value="${environment}"]`).click({ force: true }); - cy.waitForNetworkIdle(500); - }; - - const checkVariantListCard = ({ - selector, - message, - hasBorder = false, - }: { - message: string; - selector: string; - hasBorder?: boolean; - }) => { - cy.getByTestId(selector).contains(message); - if (hasBorder) { - cy.getByTestId(selector) - .find('div[role="button"]') - .first() - .should('have.css', 'border-style', 'solid') - .and('have.css', 'border-width', '1px'); - } - }; - - const checkVariantConditions = ({ selector, contains }: { selector: string; contains: string }) => { - cy.getByTestId(selector).getByTestId('conditions-action').should('be.visible').contains(contains); - }; - - describe('Add variant flow', function () { - it('should allow creating the variants for the in-app channel', function () { - addVariantForChannel('inApp', 'V1 In-App'); - }); - - it('should allow creating the variants for the email channel', function () { - addVariantForChannel('email', 'V1 Email'); - }); - - it('should allow creating the variants for the sms channel', function () { - addVariantForChannel('sms', 'V1 SMS'); - }); - - it('should allow creating the variants for the chat channel', function () { - addVariantForChannel('chat', 'V1 Chat'); - }); - - it('should allow creating the variants for the push channel', function () { - addVariantForChannel('push', 'V1 Push'); - }); - - it('should allow creating variant from the step editor', function () { - createWorkflow('Add variant flow from variant editor'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - - cy.getByTestId('editor-sidebar-add-variant').click(); - addConditions(); - fillEditorContent('inApp', true); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - checkEditorContent('inApp', true); - }); - - it('should allow creating multiple variants', function () { - const channel = 'inApp'; - createWorkflow('Add multiple variants'); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - - cy.getByTestId('editor-sidebar-add-variant').click(); - addConditions(); - fillEditorContent(channel, true); - goBack(); - - cy.getByTestId('variant-sidebar-add-variant').should('be.visible').click(); - addConditions(); - fillEditorContent(channel, true); - goBack(); - goBack(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - cy.getByTestId(`node-${channel}Selector`).getByTestId('variants-count').contains('2 variants'); - cy.clickWorkflowNode(`node-${channel}Selector`); - - checkVariantListCard({ selector: 'variant-item-card-1', message: VARIANT_EDITOR_TEXT }); - checkVariantConditions({ selector: 'variant-item-card-1', contains: '1' }); - - checkVariantListCard({ selector: 'variant-item-card-0', message: VARIANT_EDITOR_TEXT }); - checkVariantConditions({ selector: 'variant-item-card-0', contains: '1' }); - - checkVariantListCard({ selector: 'variant-root-card', message: EDITOR_TEXT }); - checkVariantConditions({ selector: 'variant-item-card-0', contains: 'No' }); - }); - - it('should not allow creating variant for digest step', function () { - const channel = 'digest'; - createWorkflow('Add variant not available'); - - dragAndDrop(channel); - showStepActions(channel); - - cy.getByTestId(`node-${channel}Selector`) - .getByTestId('step-actions-menu') - .click() - .getByTestId('add-variant-action') - .should('not.exist'); - - editChannel(channel); - cy.getByTestId('editor-sidebar-add-variant').should('not.exist'); - }); - - it('should not allow creating variant for delay step', function () { - const channel = 'delay'; - createWorkflow('Add variant not available'); - - dragAndDrop(channel); - showStepActions(channel); - - cy.getByTestId(`node-${channel}Selector`) - .getByTestId('step-actions-menu') - .click() - .getByTestId('add-variant-action') - .should('not.exist'); - - editChannel(channel); - cy.getByTestId('editor-sidebar-add-variant').should('not.exist'); - }); - }); - - describe('Step actions', function () { - it('should show the step actions', function () { - createWorkflow(`Test Step Actions`); - - dragAndDrop('inApp'); - - checkStepActions('inApp'); - }); - - it('should show the root step actions', function () { - createWorkflow(`Test Root Step Actions`); - - // create in-app channel and add variant - dragAndDrop('inApp'); - showStepActions('inApp'); - addVariantActionClick('inApp'); - addConditions(); - goBack(); - goBack(); - - // show the root step actions and check available actions - checkStepActions('inApp'); - }); - - it('in production should show only the edit step action', function () { - const channel = 'sms'; - createWorkflow(`Production Test Step Actions`); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - navigateAndPromoteAllChanges(); - - switchEnvironment('Production'); - - navigateAndOpenFirstWorkflow(); - - cy.getByTestId(`node-${channel}Selector`).getByTestId('conditions-action').should('not.exist'); - showStepActions(channel); - cy.getByTestId(`node-${channel}Selector`).getByTestId('edit-action').should('be.visible'); - cy.getByTestId(`node-${channel}Selector`).getByTestId('add-conditions-action').should('not.exist'); - cy.getByTestId(`node-${channel}Selector`).getByTestId('step-actions-menu').should('not.exist'); - }); - - it('in production should show the step actions: edit and conditions', function () { - const channel = 'sms'; - createWorkflow(`Production Test Step Actions`); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - goBack(); - - showStepActions(channel); - cy.getByTestId('add-conditions-action').should('be.visible').click(); - addConditions(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - navigateAndPromoteAllChanges(); - - switchEnvironment('Production'); - - navigateAndOpenFirstWorkflow(); - - cy.getByTestId(`node-${channel}Selector`).getByTestId('conditions-action').should('be.visible').contains('1'); - showStepActions(channel); - cy.getByTestId(`node-${channel}Selector`).getByTestId('edit-action').should('be.visible'); - cy.getByTestId(`node-${channel}Selector`).getByTestId('add-conditions-action').should('be.visible'); - cy.getByTestId(`node-${channel}Selector`).getByTestId('step-actions-menu').should('not.exist'); - }); - }); - - describe('Editor actions', function () { - it('check the step editor actions', function () { - createWorkflow('Test Editor Actions'); - - dragAndDrop('email'); - editChannel('email'); - - checkEditorActions(); - }); - - it('check the variant editor actions', function () { - createWorkflow('Test Editor Actions'); - - dragAndDrop('email'); - showStepActions('email'); - addVariantActionClick('email'); - addConditions(); - - checkEditorActions(true); - }); - - it('in production should show only the close action', function () { - const channel = 'sms'; - createWorkflow(`Production Test Editor Actions`); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - navigateAndPromoteAllChanges(); - - switchEnvironment('Production'); - - navigateAndOpenFirstWorkflow(); - - editChannel(channel); - cy.getByTestId('editor-sidebar-add-variant').should('not.exist'); - cy.getByTestId('editor-sidebar-add-conditions').should('not.exist'); - cy.getByTestId('editor-sidebar-edit-conditions').should('not.exist'); - cy.getByTestId('editor-sidebar-delete').should('not.exist'); - }); - - it('in production should only show the view conditions action', function () { - const channel = 'sms'; - createWorkflow(`Production Test Editor Actions`); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - goBack(); - - showStepActions(channel); - cy.getByTestId('add-conditions-action').should('be.visible').click(); - addConditions(); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - navigateAndPromoteAllChanges(); - - switchEnvironment('Production'); - - navigateAndOpenFirstWorkflow(); - - editChannel(channel); - cy.getByTestId('editor-sidebar-add-variant').should('not.exist'); - cy.getByTestId('editor-sidebar-add-conditions').should('not.exist'); - cy.getByTestId('editor-sidebar-edit-conditions').should('be.visible'); - cy.getByTestId('editor-sidebar-delete').should('not.exist'); - }); - }); - - describe('Add conditions action', function () { - it('should allow adding the conditions on the step', function () { - createWorkflow('Test Conditions Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - cy.getByTestId('add-conditions-action').should('be.visible').click(); - addConditions(); - - cy.getByTestId(`node-inAppSelector`).getByTestId('conditions-action').should('be.visible').contains('1'); - showStepActions('inApp'); - cy.getByTestId(`node-inAppSelector`).getByTestId('add-conditions-action').should('be.visible').contains('1'); - }); - - it('should allow adding the conditions from the variants list sidebar header', function () { - createWorkflow('Test Conditions Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - addVariantActionClick('inApp'); - addConditions(); - goBack(); - - cy.getByTestId('variants-list-sidebar') - .getByTestId('variant-sidebar-add-conditions') - .should('be.visible') - .click(); - addConditions(); - - cy.getByTestId('variants-list-sidebar') - .getByTestId('variant-sidebar-edit-conditions') - .should('be.visible') - .contains('1'); - }); - - it('should allow adding the conditions on the variant in the variants list', function () { - createWorkflow('Test Conditions Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - addVariantActionClick('inApp'); - addConditions(); - goBack(); - - cy.getByTestId('variant-item-card-0').getByTestId('conditions-action').should('be.visible').contains('1'); - cy.getByTestId('variant-item-card-0').should('be.visible').trigger('mouseover'); - cy.getByTestId('variant-item-card-0').getByTestId('add-conditions-action').should('be.visible').click(); - addConditions(); - - cy.getByTestId('variant-item-card-0').getByTestId('conditions-action').should('be.visible').contains('2'); - }); - - it('should allow adding the conditions in the step editor', function () { - createWorkflow('Test Conditions Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - cy.getByTestId('editor-sidebar-add-conditions').click(); - addConditions(); - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - checkEditorContent('inApp'); - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - }); - - it('should allow adding the conditions in the variant editor', function () { - createWorkflow('Test Conditions Action'); - - dragAndDrop('email'); - editChannel('email'); - fillEditorContent('email'); - goBack(); - - showStepActions('email'); - addVariantActionClick('email'); - addConditions(); - - cy.getByTestId('editor-sidebar-edit-conditions').contains('1'); - cy.getByTestId('editor-sidebar-edit-conditions').click(); - addConditions(); - - cy.getByTestId('editor-sidebar-edit-conditions').contains('2'); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - cy.getByTestId('editor-sidebar-edit-conditions').contains('2'); - }); - - it('in production should only allow edit and view conditions on variant list item', function () { - const channel = 'chat'; - createWorkflow(`Production Variant Actions`); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - goBack(); - - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - navigateAndPromoteAllChanges(); - - switchEnvironment('Production'); - - navigateAndOpenFirstWorkflow(); - - cy.clickWorkflowNode(`node-${channel}Selector`); - - cy.getByTestId('variant-item-card-0').getByTestId('conditions-action').should('be.visible').contains('1'); - cy.getByTestId('variant-item-card-0').trigger('mouseover'); - cy.getByTestId('variant-item-card-0').getByTestId('edit-action').should('be.visible'); - cy.getByTestId('variant-item-card-0').getByTestId('add-conditions-action').should('be.visible'); - cy.getByTestId('variant-item-card-0').getByTestId('step-actions-menu').should('not.exist'); - }); - - it('in production should only allow edit the variant root list item', function () { - const channel = 'chat'; - createWorkflow(`Production Variant Actions`); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - goBack(); - - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - navigateAndPromoteAllChanges(); - - switchEnvironment('Production'); - - navigateAndOpenFirstWorkflow(); - - cy.clickWorkflowNode(`node-${channel}Selector`); - - cy.getByTestId('variant-root-card').getByTestId('conditions-action').should('be.visible').contains('No'); - cy.getByTestId('variant-root-card').trigger('mouseover'); - cy.getByTestId('variant-root-card').getByTestId('edit-step-action').should('be.visible'); - cy.getByTestId('variant-root-card').getByTestId('add-conditions-action').should('not.exist'); - cy.getByTestId('variant-root-card').getByTestId('step-actions-menu').should('not.exist'); - }); - }); - - describe('Edit action', function () { - it('should allow editing step', function () { - createWorkflow('Test Edit Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - cy.getByTestId('edit-action').should('be.visible').click(); - - checkEditorContent('inApp'); - }); - - it('should allow editing root step from the variants list', function () { - createWorkflow('Test Edit Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - addVariantActionClick('inApp'); - addConditions(); - checkEditorContent('inApp'); - goBack(); - - cy.getByTestId('variant-root-card').getByTestId('conditions-action').should('be.visible').contains('No'); - cy.getByTestId('variant-root-card').should('be.visible').trigger('mouseover'); - cy.getByTestId('variant-root-card').getByTestId('edit-step-action').should('be.visible').click(); - - checkEditorContent('inApp'); - }); - - it('should allow editing variant from the variants list', function () { - createWorkflow('Test Edit Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - addVariantActionClick('inApp'); - addConditions(); - checkEditorContent('inApp'); - goBack(); - - cy.getByTestId('variant-item-card-0').getByTestId('conditions-action').should('be.visible').contains('1'); - cy.getByTestId('variant-item-card-0').should('be.visible').trigger('mouseover'); - cy.getByTestId('variant-item-card-0').getByTestId('edit-action').should('be.visible').click(); - - checkEditorContent('inApp'); - }); - }); - - describe('Delete action', function () { - it('should allow deleting step', function () { - createWorkflow('Test Delete Action'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - goBack(); - - showStepActions('inApp'); - cy.getByTestId('step-actions-menu').should('be.visible').click(); - cy.getByTestId('step-actions-menu').getByTestId('delete-step-action').should('be.visible').click(); - - cy.get('.mantine-Modal-modal').contains('Delete step?'); - cy.get('.mantine-Modal-modal button').contains('Delete step').click(); - cy.getByTestId(`node-inAppSelector`).should('not.exist'); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - cy.getByTestId(`node-inAppSelector`).should('not.exist'); - }); - - it('should allow deleting step from the variants list', function () { - createWorkflow('Test Delete Action'); - - dragAndDrop('email'); - editChannel('email'); - fillEditorContent('email'); - goBack(); - - showStepActions('email'); - addVariantActionClick('email'); - addConditions(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - goBack(); - - cy.reload(); - cy.wait('@getWorkflow'); - - cy.getByTestId('variant-sidebar-delete').click(); - - cy.get('.mantine-Modal-modal').contains('Delete step?'); - cy.get('.mantine-Modal-modal button').contains('Delete step').click(); - cy.getByTestId(`node-emailSelector`).should('not.exist'); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - cy.getByTestId(`node-emailSelector`).should('not.exist'); - }); - - it('should allow deleting variant', function () { - createWorkflow('Test Delete Action'); - - dragAndDrop('email'); - editChannel('email'); - fillEditorContent('email'); - goBack(); - - showStepActions('email'); - addVariantActionClick('email'); - addConditions(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - goBack(); - - cy.getByTestId('variant-item-card-0').should('be.visible').trigger('mouseover'); - cy.getByTestId('variant-item-card-0').getByTestId('step-actions-menu').should('be.visible').click(); - cy.getByTestId('variant-item-card-0').getByTestId('delete-step-action').click(); - - cy.get('.mantine-Modal-modal').contains('Delete variant?'); - cy.get('.mantine-Modal-modal button').contains('Delete variant').click(); - cy.getByTestId('variant-item-card-0').should('not.exist'); - goBack(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - cy.getByTestId('variants-count').should('not.exist'); - }); - - it('should not allow removing all conditions from a variant', function () { - createWorkflow('Test Removing All Conditions'); - - dragAndDrop('inApp'); - editChannel('inApp'); - fillEditorContent('inApp'); - goBack(); - - showStepActions('inApp'); - addVariantActionClick('inApp'); - addConditions(); - - cy.getByTestId('notification-template-submit-btn').click(); - cy.wait('@updateWorkflow'); - - cy.reload(); - cy.wait('@getWorkflow'); - - // edit the variant condition - cy.getByTestId('editor-sidebar-edit-conditions').click(); - - // open conditions row menu - cy.getByTestId('conditions-row-btn').click(); - - // delete the condition - cy.contains('Delete').click(); - - // submit ("Apply conditions") should be disabled - cy.getByTestId('apply-conditions-btn').should('be.disabled').click({ force: true }); - - // tooltip should warn the user - cy.get('div[role="tooltip"]').contains('At least one condition is required'); - }); - }); - - describe('Variants List Errors', function () { - const checkCurrentError = ({ message, count }: { message: string; count: string }) => { - cy.getByTestId('variants-list-current-error').contains(message); - cy.getByTestId('variants-list-errors-count').contains(count); - }; - - it('should show the push variant errors', function () { - const channel = 'push'; - const messageTitleMissing = 'Message title is missing!'; - const messageContentMissing = 'Message content is missing!'; - createWorkflow('Variants List Errors'); - - dragAndDrop(channel); - editChannel(channel); - fillEditorContent(channel); - goBack(); - - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - clearEditorContent(channel); - goBack(); - - checkCurrentError({ message: messageTitleMissing, count: '1/2' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageTitleMissing, hasBorder: true }); - - cy.getByTestId('variants-list-errors-down').click(); - checkCurrentError({ message: messageContentMissing, count: '2/2' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageContentMissing, hasBorder: true }); - - cy.getByTestId('variants-list-errors-up').click(); - checkCurrentError({ message: messageTitleMissing, count: '1/2' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageTitleMissing, hasBorder: true }); - }); - - it('should show the push variant errors and root errors', function () { - const channel = 'push'; - const messageTitleMissing = 'Message title is missing!'; - const messageContentMissing = 'Message content is missing!'; - createWorkflow('Variants List Errors'); - - dragAndDrop(channel); - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - goBack(); - - checkCurrentError({ message: messageTitleMissing, count: '1/4' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageTitleMissing, hasBorder: true }); - checkVariantListCard({ selector: 'variant-root-card', message: messageTitleMissing }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageContentMissing, count: '2/4' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageContentMissing, hasBorder: true }); - checkVariantListCard({ selector: 'variant-root-card', message: messageTitleMissing }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageTitleMissing, count: '3/4' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageTitleMissing }); - checkVariantListCard({ selector: 'variant-root-card', message: messageTitleMissing, hasBorder: true }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageContentMissing, count: '4/4' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageTitleMissing }); - checkVariantListCard({ selector: 'variant-root-card', message: messageContentMissing, hasBorder: true }); - }); - - it('should show the email variant and root errors', function () { - const channel = 'email'; - const messageSubjectMissing = 'Email subject is missing!'; - createWorkflow('Variants List Errors'); - - dragAndDrop(channel); - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - goBack(); - goBack(); - - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - fillEditorContent(channel, true); - goBack(); - goBack(); - - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - goBack(); - - checkCurrentError({ message: messageSubjectMissing, count: '1/3' }); - checkVariantListCard({ selector: 'variant-item-card-2', message: messageSubjectMissing, hasBorder: true }); - checkVariantListCard({ selector: 'variant-item-card-1', message: VARIANT_EDITOR_TEXT }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageSubjectMissing }); - checkVariantListCard({ selector: 'variant-root-card', message: messageSubjectMissing }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageSubjectMissing, count: '2/3' }); - checkVariantListCard({ selector: 'variant-item-card-2', message: messageSubjectMissing }); - checkVariantListCard({ selector: 'variant-item-card-1', message: VARIANT_EDITOR_TEXT }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageSubjectMissing, hasBorder: true }); - checkVariantListCard({ selector: 'variant-root-card', message: messageSubjectMissing }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageSubjectMissing, count: '3/3' }); - checkVariantListCard({ selector: 'variant-item-card-2', message: messageSubjectMissing }); - checkVariantListCard({ selector: 'variant-item-card-1', message: VARIANT_EDITOR_TEXT }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageSubjectMissing }); - checkVariantListCard({ selector: 'variant-root-card', message: messageSubjectMissing, hasBorder: true }); - }); - - it('should show the provider missing error', function () { - cy.intercept('*/integrations', { - data: [], - delay: 0, - }).as('getIntegrations'); - cy.intercept('*/integrations/active', { - data: [], - delay: 0, - }).as('getActiveIntegrations'); - - const channel = 'email'; - const messageSubjectMissing = 'Email subject is missing!'; - const messageProviderMissing = 'Provider is missing!'; - createWorkflow('Variants List Errors'); - - dragAndDrop(channel); - showStepActions(channel); - addVariantActionClick(channel); - addConditions(); - goBack(); - - checkCurrentError({ message: messageSubjectMissing, count: '1/3' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageSubjectMissing, hasBorder: true }); - checkVariantListCard({ selector: 'variant-root-card', message: messageProviderMissing }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageProviderMissing, count: '2/3' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageSubjectMissing }); - checkVariantListCard({ selector: 'variant-root-card', message: messageProviderMissing, hasBorder: true }); - - cy.getByTestId('variants-list-errors-down').click(); - - checkCurrentError({ message: messageSubjectMissing, count: '3/3' }); - checkVariantListCard({ selector: 'variant-item-card-0', message: messageSubjectMissing }); - checkVariantListCard({ selector: 'variant-root-card', message: messageSubjectMissing, hasBorder: true }); - }); - }); -}); diff --git a/apps/web/cypress/tests/notifications.spec.ts b/apps/web/cypress/tests/notifications.spec.ts deleted file mode 100644 index 311d395cc40b..000000000000 --- a/apps/web/cypress/tests/notifications.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -describe('Notification Templates Screen', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should display notification templates list', function () { - cy.visit('/workflows'); - cy.getByTestId('notifications-template') - .find('tbody tr') - .first() - .getByTestId('template-edit-link') - .then((a: any) => { - const found = this.session.templates.find((i) => a.attr('href').includes(i._id)); - expect(found).to.be.ok; - return expect(a.attr('href')).to.equal(`/workflows/edit/${found._id}`); - }); - - cy.getByTestId('notifications-template') - .find('tbody tr') - .first() - .getByTestId('active-status-label') - .should('be.visible'); - - cy.getByTestId('create-workflow-btn').should('not.be.disabled'); - cy.getByTestId('category-label').contains('General'); - }); - - it('should show the create template dropdown', function () { - cy.intercept('**/notification-templates**').as('notificationTemplates'); - cy.visit('/workflows'); - cy.wait('@notificationTemplates'); - - cy.getByTestId('create-workflow-btn').should('not.be.disabled').click(); - cy.getByTestId('create-workflow-all-templates').contains('All templates'); - cy.getByTestId('create-workflow-blank').contains('Blank workflow'); - }); - - it('when no workflow templates created it should show the page placeholder', function () { - cy.initializeSession({ noTemplates: true }).as('session'); - cy.intercept('**/notification-templates**').as('notificationTemplates'); - cy.visit('/workflows'); - cy.wait('@notificationTemplates'); - - cy.getByTestId('no-workflow-templates-placeholder').should('be.visible'); - cy.getByTestId('create-workflow-tile').should('exist'); - }); - - it('when clicking on create workflow it should redirect to create template page', function () { - cy.initializeSession({ noTemplates: true }).as('session'); - cy.intercept('**/notification-templates**').as('notificationTemplates'); - cy.visit('/workflows'); - cy.wait('@notificationTemplates'); - - cy.getByTestId('no-workflow-templates-placeholder').should('be.visible'); - cy.getByTestId('create-workflow-tile').should('exist'); - cy.getByTestId('create-workflow-tile').click(); - - cy.url().should('include', '/workflows/create'); - }); -}); diff --git a/apps/web/cypress/tests/onboarding.spec.ts b/apps/web/cypress/tests/onboarding.spec.ts deleted file mode 100644 index 1be8184323ba..000000000000 --- a/apps/web/cypress/tests/onboarding.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -describe('Getting Started Screen', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it.skip('should change status of templates in on-boarding', function () { - cy.intercept('GET', '*/notification-templates', (r) => { - r.continue((res) => { - if (res.body) { - const sessionTemplates = this.session.templates.map((template) => template._id); - const createdTemplate = res.body.data.filter((template) => !sessionTemplates.includes(template._id)); - res.body.data = createdTemplate; - } - - res.send({ body: res.body }); - }); - }); - cy.visit('/quickstart'); - cy.getByTestId('create-workflow-btn').should('exist').click({ force: true }); - - cy.location('pathname').should('equal', '/workflows/create'); - cy.getByTestId('title').type('Test Notification Title'); - cy.getByTestId('description').type('This is a test description for a test title'); - cy.getByTestId('notification-template-submit-btn').click({ force: true }); - cy.get('.mantine-Notification-root').contains('Template saved successfully'); - - cy.getByTestId('side-nav-quickstart-link').click({ force: true }); - cy.getByTestId('create-workflow-btn').should('not.exist'); - cy.getByTestId('template-created').contains('Created'); - }); -}); diff --git a/apps/web/cypress/tests/organization-brand.spec.ts b/apps/web/cypress/tests/organization-brand.spec.ts deleted file mode 100644 index 0310821e6fa3..000000000000 --- a/apps/web/cypress/tests/organization-brand.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -describe('Organization Brand Screen', function () { - beforeEach(function () { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - cy.visit('settings/brand'); - cy.intercept('*/organizations/branding').as('updateBrandingSettings'); - }); - - it('should update logo', function () { - cy.intercept('*/storage/upload-url*').as('uploadLogo'); - - cy.fixture('test-logo.png', {}).then((contents) => { - cy.getByTestId('upload-image-button') - .find('input') - .selectFile( - { - contents: Buffer.from(contents), - fileName: 'test-logo.png', - mimeType: 'image/png', - }, - { force: true } - ); - }); - - cy.wait('@uploadLogo'); - - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', '.png'); - - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', this.session.organization._id); - cy.getByTestId('font-color-picker').click({ force: true }); - cy.get('button[aria-label="#BA68C8"]').click(); - cy.get('body').click(); - cy.getByTestId('submit-branding-settings').click(); - - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', '.png'); - cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', this.session.organization._id); - }); - - it('should change look and feel settings', function () { - cy.getByTestId('font-color-picker').click({ force: true }); - cy.get('button[aria-label="#BA68C8"]').click(); - cy.getByTestId('font-color-picker').should('have.value', '#BA68C8'); - cy.getByTestId('font-color-picker').click({ force: true }); - - cy.get('body').click(); - - cy.getByTestId('font-family-selector').should('have.value', 'Montserrat'); - cy.getByTestId('font-family-selector').click({ force: true }); - cy.get('.mantine-Select-dropdown .mantine-Select-item').contains('Lato').click(); - cy.getByTestId('font-family-selector').should('have.value', 'Lato'); - - cy.getByTestId('brand-color-picker').click({ force: true }); - cy.get('button[aria-label="#f47373"]').click(); - cy.getByTestId('brand-color-picker').should('have.value', '#f47373'); - cy.getByTestId('brand-color-picker').click({ force: true }); - - cy.get('body').click(); - - cy.getByTestId('submit-branding-settings').click({ force: true }); - cy.wait('@updateBrandingSettings'); - - cy.reload(); - cy.getByTestId('brand-color-picker').should('have.value', '#f47373'); - cy.getByTestId('font-color-picker').should('have.value', '#BA68C8'); - cy.getByTestId('font-family-selector').should('have.value', 'Lato'); - }); -}); diff --git a/apps/web/cypress/tests/organization-page.spec.ts b/apps/web/cypress/tests/organization-page.spec.ts deleted file mode 100644 index 9e20795c092f..000000000000 --- a/apps/web/cypress/tests/organization-page.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -describe('Organization Page', function () { - beforeEach(function () { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - cy.visit('/settings/organization'); - cy.intercept('*/organizations').as('renameOrganization'); - }); - - it('update the organization name', function () { - cy.getByTestId('organization-name-input').should('have.value', this.session.organization.name); - cy.getByTestId('organization-name-input').clear().type('New Name'); - cy.getByTestId('organization-update-button').click(); - - cy.wait('@renameOrganization'); - - cy.reload(); - cy.getByTestId('organization-name-input').should('have.value', 'New Name'); - }); -}); diff --git a/apps/web/cypress/tests/organization-settings.spec.ts b/apps/web/cypress/tests/organization-settings.spec.ts deleted file mode 100644 index 29608fdda985..000000000000 --- a/apps/web/cypress/tests/organization-settings.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -describe('Settings Screen', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - cy.visit('/team'); - }); - - it('should send organization invitation', function () { - cy.getByTestId('invite-email-field').type('test-user@email.com'); - cy.getByTestId('submit-btn').click(); - }); -}); diff --git a/apps/web/cypress/tests/organization-switch.spec.ts b/apps/web/cypress/tests/organization-switch.spec.ts deleted file mode 100644 index cf9b76eba134..000000000000 --- a/apps/web/cypress/tests/organization-switch.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as capitalize from 'lodash.capitalize'; - -describe('Organization Switch', function () { - beforeEach(function () { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: false }); - cy.initializeSession().as('session'); - }); - - it('should display switch when page is loaded', function () { - cy.visit('/workflows'); - - cy.getByTestId('organization-switch') - .scrollIntoView() - .should('be.visible') - .should('have.value', capitalize(this.session.organization.name)); - }); - - it('should use different jwt token after switches', function () { - const originToken = this.session.token; - cy.task('addOrganization', this.session.user.id).then((newOrg: any) => { - cy.visit('/workflows'); - - cy.getByTestId('organization-switch').scrollIntoView().focus(); - - cy.get('.mantine-Select-item').contains(capitalize(newOrg.name)).click({ force: true }); - - cy.wait(1000); - - cy.task('getSession', {}).then((response: any) => { - expect(response.token).not.to.equal(originToken); - }); - }); - }); -}); diff --git a/apps/web/cypress/tests/settings.spec.ts b/apps/web/cypress/tests/settings.spec.ts deleted file mode 100644 index b38113ee95ff..000000000000 --- a/apps/web/cypress/tests/settings.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -describe('Settings Screen', function () { - beforeEach(function () { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: false }); - - cy.initializeSession().as('session'); - cy.intercept('*/channels/email/settings').as('updateEmailSettings'); - cy.intercept('*/organizations/branding').as('updateBrandingSettings'); - - cy.waitLoadFeatureFlags(() => { - cy.visit('/'); - }); - // avoid reloading the page (instead of visit('/settings') due to conditional routes behind the feature flag - cy.contains('a', 'Settings').click(); - }); - - it('should display the api key of the app', function () { - cy.get('.mantine-Tabs-tabsList').contains('API Keys').click(); - cy.getByTestId('api-key-container').should('have.value', this.session.environment.apiKeys[0].key); - }); -}); diff --git a/apps/web/cypress/tests/settings/api-keys.spec.ts b/apps/web/cypress/tests/settings/api-keys.spec.ts deleted file mode 100644 index f1b39ffe5c56..000000000000 --- a/apps/web/cypress/tests/settings/api-keys.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -describe('API Keys Page', () => { - const testClipboardInput = (inputTestId: string) => { - cy.getByTestId(`${inputTestId}-copy`).focus().click(); - cy.getClipboardValue().then((clipVal) => { - cy.getByTestId(`${inputTestId}`) - .focus() - .invoke('val') - .then((inputVal) => { - expect(inputVal).to.equal(clipVal); - }); - }); - }; - - beforeEach(() => { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - - cy.intercept('GET', '/v1/environments/api-keys').as('getApiKeys'); - - cy.waitLoadEnv(() => { - cy.visit('/settings/api-keys/Development'); - }); - cy.wait('@getApiKeys'); - }); - - it('should render the API key container', () => { - cy.getByTestId('api-key').should('exist'); - }); - - it('should show and hide the API key', () => { - cy.getByTestId('api-key').should('have.attr', 'type', 'password'); - cy.get('#api-key-toggle-visibility-btn').click(); - cy.getByTestId('api-key').should('have.attr', 'type', 'text'); - cy.get('#api-key-toggle-visibility-btn').click(); - cy.getByTestId('api-key').should('have.attr', 'type', 'password'); - }); - - it('should copy the API key', () => { - testClipboardInput('api-key'); - }); - - it('should render the Application Identifier container', () => { - cy.getByTestId('application-identifier').should('exist'); - }); - - it('should copy the Application Identifier', () => { - testClipboardInput('application-identifier'); - }); - - it('should render the Environment ID container', () => { - cy.getByTestId('environment-id').should('exist'); - }); - - it('should copy the Environment ID', () => { - testClipboardInput('environment-id'); - }); - - it('should open the Regeneration Modal', () => { - cy.get('#api-key-regenerate-btn').click(); - cy.getByTestId('regenerate-api-key-modal').should('exist'); - cy.getByTestId('regenerate-api-key-modal-button').should('exist'); - }); -}); diff --git a/apps/web/cypress/tests/settings/inbound-webhook.spec.ts b/apps/web/cypress/tests/settings/inbound-webhook.spec.ts deleted file mode 100644 index bcaa8265686f..000000000000 --- a/apps/web/cypress/tests/settings/inbound-webhook.spec.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { IEnvironment } from '@novu/shared'; - -const validateExternalLink = ({ label, href }: { label: string | RegExp; href: string }) => { - cy.contains('a', label).should('have.attr', 'target', '_blank'); - cy.contains('a', label).should('have.attr', 'rel', 'noopener noreferrer'); - cy.contains('a', label).should('have.attr', 'href').should('eq', href); -}; - -interface IMockEnv { - envName?: string; - mxRecordConfigured?: boolean; - inboundParseDomain?: string; -} - -const mockEnvs = ({ envName = 'Development', mxRecordConfigured = false, inboundParseDomain = '' }: IMockEnv = {}) => { - const dns: IEnvironment['dns'] = { - mxRecordConfigured, - inboundParseDomain, - }; - - const env = { - _id: 'envId', - name: envName, - _organizationId: 'orgId', - dns, - }; - - cy.intercept('GET', '/v1/environments', { - data: [env], - }).as('webhook-envs'); - - cy.intercept('GET', '/v1/environments/me', { - data: env, - }).as('webhook-current-env'); - - cy.intercept('PUT', '/v1/environments/**', { - data: { success: true }, - }); -}; - -describe('Inbound Webhook Page', () => { - const TEST_URL = '/settings/webhook/Development'; - const launchTestPage = () => - cy.waitLoadEnv(() => { - cy.visit(TEST_URL); - }); - - const testClipboardInput = (inputTestId: string) => { - cy.getByTestId(`${inputTestId}-copy`).focus().click(); - cy.getClipboardValue().then((clipVal) => { - cy.getByTestId(`${inputTestId}`) - .focus() - .invoke('val') - .then((inputVal) => { - expect(inputVal).to.equal(clipVal); - }); - }); - }; - - beforeEach(() => { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - - // prevent repeated request - cy.intercept('GET', 'https://clientstream.launchdarkly.com/**', {}).as('launchDarklyEval'); - cy.intercept('POST', 'https://events.launchdarkly.com/**', {}).as('launchDarklyEvents'); - }); - - it('should have all elements in the original state', () => { - const envName = 'Development'; - - mockEnvs({ envName }); - launchTestPage(); - - // title - cy.contains('h1', 'Inbound parse webhook').should('exist'); - - // status but no refresh button - cy.contains('svg + p', 'Not claimed').should('exist'); - cy.get('button#refresh-status-button').should('not.exist'); - - // validate the titles of the timeline - cy.contains(`Specify a domain name for ${envName} environment`).should('exist'); - cy.contains(`Create a new MX record for ${envName} environment`).should('exist'); - cy.contains(`Assign MX record a priority`).should('exist'); - cy.contains(`Enable parse and set webhook URL`).should('exist'); - - cy.get('form button[type="submit"]').should('be.disabled'); - cy.get('#inbound-parse-domain-input').should('have.value', ''); - }); - - it('should handle the base / unclaimed flow', () => { - const envName = 'Development'; - const testDomain = 'testing-cypress.novu.com'; - - mockEnvs({ envName }); - launchTestPage(); - - // status should be unclaimed - cy.contains('svg + p', 'Not claimed').should('exist'); - cy.get('button#refresh-status-button').should('not.exist'); - - // validate default form state - cy.get('form button[type="submit"]').should('be.disabled'); - cy.get('#inbound-parse-domain-input').should('have.value', ''); - - // attempt illegal character - cy.get('#inbound-parse-domain-input').type(',' + testDomain); - cy.get('form button[type="submit"]').should('be.enabled').click(); - - // ensure invalid state is marked - cy.get('#inbound-parse-domain-input').should('have.attr', 'aria-invalid').should('eq', 'true'); - cy.get('#inbound-parse-domain-input-error').should('exist'); - - // clearing and re-entering should remove error - cy.get('#inbound-parse-domain-input').clear().type(testDomain).blur(); - cy.get('#inbound-parse-domain-input').should('have.attr', 'aria-invalid').should('eq', 'false'); - cy.get('#inbound-parse-domain-input-error').should('not.exist'); - - // submit form - cy.get('form button[type="submit"]').should('be.enabled').click(); - - // success message should appear - cy.contains('Domain info updated successfully').should('exist'); - }); - - it('should handle the pending state', () => { - const envName = 'Development'; - const testDomain = 'testing-cypress.novu.com'; - - mockEnvs({ envName, inboundParseDomain: testDomain }); - launchTestPage(); - - cy.intercept('GET', '/v1/inbound-parse/mx/status', { data: { mxRecordConfigured: false } }).as('mx-status-pending'); - - // validate form state - cy.get('form button[type="submit"]').should('be.disabled'); - cy.get('#inbound-parse-domain-input').should('have.value', testDomain); - - // status should be pending with a refresh button - cy.contains('svg + p', 'Pending').should('exist'); - cy.get('button#refresh-status-button').should('exist').should('be.enabled').click().should('be.disabled'); - cy.get('button#refresh-status-button').should('be.disabled'); - - cy.wait('@mx-status-pending'); - - cy.get('button#refresh-status-button').should('exist').should('be.enabled').click().should('be.disabled'); - cy.get('button#refresh-status-button').should('be.disabled'); - - cy.intercept('GET', '/v1/inbound-parse/mx/status', { data: { mxRecordConfigured: true } }).as('mx-status-success'); - mockEnvs({ envName, inboundParseDomain: testDomain, mxRecordConfigured: true }); - cy.wait(['@mx-status-success', '@webhook-envs', '@webhook-current-env']); - - // claimed status should be reflected - cy.contains('svg + p', 'Claimed').should('exist'); - cy.get('button#refresh-status-button').should('not.exist'); - }); - - it('should handle the claimed state', () => { - const envName = 'Development'; - const testDomain = 'testing-cypress.novu.com'; - - mockEnvs({ envName, inboundParseDomain: testDomain, mxRecordConfigured: true }); - launchTestPage(); - - // validate form state - cy.get('form button[type="submit"]').should('be.disabled'); - cy.get('#inbound-parse-domain-input').should('have.value', testDomain); - - // status should be pending with a refresh button - cy.contains('svg + p', 'Claimed').should('exist'); - cy.get('button#refresh-status-button').should('not.exist'); - }); - - it('should copy the mail server info', () => { - launchTestPage(); - cy.getByTestId('mail-server-domain').should('have.attr', 'readonly'); - testClipboardInput('mail-server-domain'); - }); - - it('should have a documentation link', () => { - launchTestPage(); - validateExternalLink({ - label: /^learn about.*/i, - href: 'https://docs.novu.co/platform/inbound-parse-webhook', - }); - }); -}); diff --git a/apps/web/cypress/tests/settings/user-profile.spec.ts b/apps/web/cypress/tests/settings/user-profile.spec.ts deleted file mode 100644 index fdd51f84ebc4..000000000000 --- a/apps/web/cypress/tests/settings/user-profile.spec.ts +++ /dev/null @@ -1,153 +0,0 @@ -describe('User Profile Settings Page', () => { - describe('Set Password Flow', () => { - const USER_DATA = { - email: 'testing@test.com', - hasPassword: false, - }; - - beforeEach(() => { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - - cy.intercept('GET', '/v1/users/me', { - data: USER_DATA, - }).as('userInfo'); - - // don't send actual reset request to avoid spamming the server - cy.intercept('POST', '/v1/auth/reset/request?src=USER_PROFILE').as('passwordResetRequest'); - - cy.intercept('POST', '/v1/auth/reset', { - data: { - token: 'testToken', - }, - }).as('passwordResetSetPassword'); - - // prevent repeated request - cy.intercept('GET', 'https://clientstream.launchdarkly.com/**', {}).as('launchDarklyEval'); - cy.intercept('POST', 'https://events.launchdarkly.com/**', {}).as('launchDarklyEvents'); - }); - - it('should render the page elements', () => { - cy.visit('/settings/profile'); - - cy.get('h1').contains('User profile'); - cy.get('h2').contains('Profile security'); - cy.get('button').contains('Set password').should('exist'); - }); - - it('should open and validate the sidebar for the non-token flow', () => { - cy.visit('/settings/profile'); - - cy.get('button').contains('Set password').click(); - - // ensure sidebar state is persisted in the URL - cy.url().should('include', 'view=password'); - - // sidebar title - cy.contains('h2', 'Set password').should('exist'); - - // timer - cy.contains('strong', /\d{1,2}/).should('exist'); - - cy.contains('button', 'Resend link').should('exist').should('be.disabled'); - - cy.getByTestId('sidebar-close').click(); - - // ensure sidebar state is removed from the URL on close - cy.url().should('not.include', 'view=password'); - cy.contains('div > form h2', 'Set password').should('not.exist'); - }); - - it('should handle password entry as if redirected from a verification email', () => { - cy.visit('/settings/profile?view=password&token=abc123'); - - // sidebar title - cy.contains('h2', 'Set password').should('exist'); - - cy.get('form#set-password-form button[type="submit"]').should('be.disabled'); - - const password = 'hell0MyFriends!'; - - // blur is required due to the Password Strength popover - cy.getByTestId('password').should('exist').type(password).blur(); - cy.getByTestId('password-repeat') - .should('exist') - .type(password + 'blah'); - - cy.get('form#set-password-form button[type="submit"]').should('not.be.disabled').click(); - - // ensure at least one field is marked as error - cy.get('div [aria-invalid="true"] input[type="password"]').should('have.length.at.least', 1); - - cy.getByTestId('password-repeat').clear().type(password); - - cy.get('form#set-password-form button[type="submit"]').should('not.be.disabled'); - }); - }); - - describe('Update Password Flow', () => { - const USER_DATA = { - email: 'testing@test.com', - hasPassword: true, - }; - - beforeEach(() => { - cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); - cy.initializeSession().as('session'); - - cy.intercept('GET', '/v1/users/me', { - data: USER_DATA, - }).as('userInfo'); - - // don't send actual reset request to avoid spamming the server - cy.intercept('POST', '/v1/auth/reset/request?src=USER_PROFILE').as('passwordResetRequest'); - - cy.intercept('POST', '/v1/auth/update-password', { - data: {}, - }).as('passwordResetUpdatePassword'); - }); - - it('should render the page elements', () => { - cy.visit('/settings/profile'); - - cy.get('h1').contains('User profile'); - cy.get('h2').contains('Profile security'); - cy.get('button').contains('Update password').should('exist'); - }); - - it('should be able to open the Update password sidebar and fill-out the form', () => { - cy.visit('/settings/profile'); - - cy.get('button').contains('Update password').click(); - - // ensure sidebar state is persisted in the URL - cy.url().should('include', 'view=password'); - - // sidebar title - cy.contains('h2', 'Update password').should('exist'); - // ensure at least one field is marked as error - cy.get('form div > input[type="password"]').should('have.length', 3); - - cy.get('form#reset-password-form button[type="submit"]').should('be.disabled'); - - const currentPassword = 'MyFriends,hell0!'; - const password = 'hell0MyFriends!'; - - cy.getByTestId('password-current').should('exist').type(currentPassword); - // blur is required due to the Password Strength popover - cy.getByTestId('password-new').should('exist').type(password).blur(); - cy.getByTestId('password-confirm') - .should('exist') - .type(password + 'blah'); - - cy.get('form#reset-password-form button[type="submit"]').should('not.be.disabled').click(); - - // ensure at least one field is marked as error - cy.get('div [aria-invalid="true"] input[type="password"]').should('have.length.at.least', 1); - - cy.getByTestId('password-confirm').clear().type(password); - - cy.get('form#reset-password-form button[type="submit"]').should('not.be.disabled'); - }); - }); -}); diff --git a/apps/web/cypress/tests/start-from-scratch-tour.spec.ts b/apps/web/cypress/tests/start-from-scratch-tour.spec.ts deleted file mode 100644 index ace6acd53fd8..000000000000 --- a/apps/web/cypress/tests/start-from-scratch-tour.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * The tests from this file were moved to the corresponding Playwright file apps/web/tests/start-from-scratch-tour.spec.ts. - * @deprecated - */ -describe.skip('Start from scratch tour hints', function () { - beforeEach(function () { - cy.initializeSession({ showOnBoardingTour: true }).as('session'); - }); - - it('should show the start from scratch intro step', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - cy.getByTestId('scratch-workflow-tooltip').should('be.visible'); - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Discover a quick guide'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Four simple tips to become a workflow expert.' - ); - cy.getByTestId('scratch-workflow-tooltip-skip-button').should('have.text', 'Watch later'); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Show me'); - }); - - it('should hide the start from scratch intro step after clicking on watch later', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - cy.getByTestId('scratch-workflow-tooltip').should('be.visible'); - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Discover a quick guide'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Four simple tips to become a workflow expert.' - ); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Show me'); - cy.getByTestId('scratch-workflow-tooltip-skip-button').should('have.text', 'Watch later').click(); - cy.getByTestId('scratch-workflow-tooltip').should('not.exist'); - }); - - it('should show the start from scratch tour hints', function () { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - cy.getByTestId('scratch-workflow-tooltip').should('be.visible'); - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Discover a quick guide'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Four simple tips to become a workflow expert.' - ); - - cy.getByTestId('scratch-workflow-tooltip-skip-button').should('have.text', 'Watch later'); - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Show me').click(); - - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Click to edit workflow name'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Specify a name for your workflow here or in the workflow settings.' - ); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Next').click(); - - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Verify workflow settings'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Manage name, identifier, group and description. Set up channels, active by default.' - ); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Next').click(); - - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Build a notification workflow'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Add channels you would like to send notifications to. The channels will be inserted to the trigger snippet.' - ); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Next').click(); - - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Run a test or Get Snippet'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Test a trigger as if it was sent from your API. Deploy it to your App by copy/paste trigger snippet.' - ); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Got it').click(); - - cy.getByTestId('scratch-workflow-tooltip').should('not.exist'); - }); - - it('should show the dots navigation after the intro step', () => { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - cy.getByTestId('scratch-workflow-tooltip').should('be.visible'); - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Discover a quick guide'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Four simple tips to become a workflow expert.' - ); - cy.getByTestId('scratch-workflow-tooltip-skip-button').should('have.text', 'Watch later'); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Show me').click(); - cy.getByTestId('scratch-workflow-tooltip-dots-navigation').should('be.visible'); - }); - - it('should show not show the start from scratch tour hints after it is shown twice', () => { - cy.waitLoadTemplatePage(() => { - cy.visit('/workflows/create'); - }); - - cy.getByTestId('scratch-workflow-tooltip').should('be.visible'); - cy.getByTestId('scratch-workflow-tooltip-title').should('have.text', 'Discover a quick guide'); - cy.getByTestId('scratch-workflow-tooltip-description').should( - 'have.text', - 'Four simple tips to become a workflow expert.' - ); - - cy.getByTestId('scratch-workflow-tooltip-primary-button').should('have.text', 'Show me'); - cy.getByTestId('scratch-workflow-tooltip-skip-button').should('have.text', 'Watch later').click(); - cy.getByTestId('scratch-workflow-tooltip').should('not.exist'); - - cy.reload(); - - cy.getByTestId('scratch-workflow-tooltip').should('be.visible'); - cy.getByTestId('scratch-workflow-tooltip-skip-button').should('have.text', 'Watch later').click(); - cy.getByTestId('scratch-workflow-tooltip').should('not.exist'); - - cy.reload(); - cy.getByTestId('scratch-workflow-tooltip').should('not.exist'); - }); -}); diff --git a/apps/web/cypress/tests/subscribers.spec.ts b/apps/web/cypress/tests/subscribers.spec.ts deleted file mode 100644 index f92ada084cf8..000000000000 --- a/apps/web/cypress/tests/subscribers.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -describe('Subscribers Page', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should display subscribers list', function () { - cy.visit('/subscribers'); - - cy.getByTestId('subscribers-table') - .find('th:nth-child(1)') - .each((el) => { - expect(el.text()).equal('Subscriber identifier'); - }); - - cy.getByTestId('subscribers-table') - .find('th:nth-child(3)') - .each((el) => { - expect(el.text()).equal('Last Name'); - }); - }); -}); diff --git a/apps/web/cypress/tests/templates-store.spec.ts b/apps/web/cypress/tests/templates-store.spec.ts deleted file mode 100644 index 6cf7397eb6f1..000000000000 --- a/apps/web/cypress/tests/templates-store.spec.ts +++ /dev/null @@ -1,243 +0,0 @@ -describe('Templates Store', function () { - const getTemplateDetails = (templateName: string): { name: string; iconName: string } => { - const regexResult = /^:.{1,}:/.exec(templateName); - let name = ''; - let iconName = 'fa-solid fa-question'; - if (regexResult !== null) { - name = templateName.replace(regexResult[0], '').trim(); - iconName = regexResult[0].replace(/:/g, '').trim(); - } - - return { name, iconName: iconName }; - }; - - beforeEach(function () { - cy.mockFeatureFlags({ IS_TEMPLATE_STORE_ENABLED: true }); - cy.initializeSession({ noTemplates: true }).as('session'); - indexedDB.deleteDatabase('localforage'); - }); - - it('the all templates tile should be disabled when there are no blueprints ', function () { - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - - cy.getByTestId('no-workflow-templates-placeholder').should('be.visible'); - cy.getByTestId('create-workflow-tile').should('exist'); - cy.getByTestId('all-workflow-tile').should('exist').should('be.disabled'); - }); - - it('the all templates tile should be enabled and popular should be shown when blueprints are fetched', function () { - cy.makeBlueprints(); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - - cy.getByTestId('no-workflow-templates-placeholder').should('be.visible'); - cy.getByTestId('create-workflow-tile').should('exist'); - cy.getByTestId('all-workflow-tile').should('exist').should('be.disabled'); - - cy.wait('@getBlueprints'); - cy.getByTestId('all-workflow-tile').should('exist').should('not.be.disabled'); - cy.getByTestId('popular-workflow-tile').should('have.length', 2); - cy.getByTestId('popular-workflow-tile').should('be.visible', 2); - }); - - it('should show the popover when hovering over the popular tile', function () { - cy.makeBlueprints(); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/v1/blueprints/group-by-category`, - }).then(({ body }) => { - const { blueprints: popularBlueprints } = body.data.popular; - const firstPopular = popularBlueprints[0]; - const { name } = getTemplateDetails(firstPopular.name); - - cy.getByTestId('no-workflow-templates-placeholder').should('be.visible'); - cy.getByTestId('popular-workflow-tile').should('have.length', 2); - cy.getByTestId('popular-workflow-tile').should('be.visible', 2); - cy.getByTestId('popular-workflow-tile').contains(name).trigger('mouseover'); - cy.get('.mantine-Popover-dropdown').should('be.visible').contains(firstPopular.description); - }); - }); - - it('should show the popular dropdown items', function () { - cy.makeBlueprints(); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.getByTestId('create-workflow-dropdown').should('be.visible').click(); - cy.getByTestId('create-workflow-btn').should('be.visible'); - cy.getByTestId('create-workflow-all-templates').should('be.visible'); - - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/v1/blueprints/group-by-category`, - }).then(({ body }) => { - const { blueprints: popularBlueprints } = body.data.popular; - const firstPopular = popularBlueprints[0]; - const secondPopular = popularBlueprints[1]; - const { name: firstName } = getTemplateDetails(firstPopular.name); - const { name: secondName } = getTemplateDetails(secondPopular.name); - - cy.getByTestId('create-template-dropdown-item').should('have.length', 2); - cy.getByTestId('create-template-dropdown-item').contains(firstName); - cy.getByTestId('create-template-dropdown-item').contains(secondName); - }); - }); - - it('should create template from the popular dropdown items', function () { - cy.makeBlueprints(); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.intercept('POST', '**/notification-templates**').as('createTemplate'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.getByTestId('create-workflow-dropdown').should('be.visible').click(); - cy.getByTestId('create-workflow-btn').should('be.visible'); - cy.getByTestId('create-workflow-all-templates').should('be.visible'); - - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/v1/blueprints/group-by-category`, - }).then(({ body }) => { - const { blueprints: popularBlueprints } = body.data.popular; - const firstPopular = popularBlueprints[0]; - const { name: firstName } = getTemplateDetails(firstPopular.name); - - cy.getByTestId('create-template-dropdown-item').contains(firstName).click(); - - cy.wait('@createTemplate'); - - cy.location('pathname').should('contain', `/workflows/edit`); - }); - }); - - it('should show the popular description in the popover when hovering over the dropdown item', function () { - cy.makeBlueprints(); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.getByTestId('create-workflow-dropdown').should('be.visible').click(); - cy.getByTestId('create-workflow-btn').should('be.visible'); - cy.getByTestId('create-workflow-all-templates').should('be.visible'); - - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/v1/blueprints/group-by-category`, - }).then(({ body }) => { - const { blueprints: popularBlueprints } = body.data.popular; - const firstPopular = popularBlueprints[0]; - const { name: firstName } = getTemplateDetails(firstPopular.name); - - cy.getByTestId('create-template-dropdown-item').should('have.length', 2); - cy.getByTestId('create-template-dropdown-item').contains(firstName).trigger('mouseover'); - cy.get('.mantine-Popover-dropdown').should('be.visible').contains(firstPopular.description); - }); - }); - - it('should create template from the popular tile items', function () { - cy.makeBlueprints().as('blueprints'); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.intercept('POST', '**/notification-templates**').as('createTemplate'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/v1/blueprints/group-by-category`, - }).then(({ body }) => { - const { blueprints: popularBlueprints } = body.data.popular; - const firstPopular = popularBlueprints[0]; - const { name: firstName } = getTemplateDetails(firstPopular.name); - - cy.getByTestId('no-workflow-templates-placeholder').contains(firstName).click(); - - cy.wait('@createTemplate'); - - cy.location('pathname').should('contain', `/workflows/edit`); - }); - }); - - it('should show the templates store modal when clicking on the all templates tile', function () { - cy.makeBlueprints().as('blueprints'); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.getByTestId('all-workflow-tile').should('exist').should('not.be.disabled').click(); - cy.getByTestId('templates-store-modal').should('be.visible'); - cy.get('.react-flow').should('be.visible'); - cy.get('.react-flow__container').should('be.visible'); - cy.get('.react-flow__controls').should('be.visible'); - cy.getByTestId('templates-store-modal-use-template').should('be.enabled').contains('Use template'); - - cy.get('@blueprints').then((blueprints) => { - blueprints.forEach((blueprint) => { - const { name } = getTemplateDetails(blueprint.name); - cy.getByTestId('templates-store-modal-sidebar').contains(name); - }); - }); - }); - - it('should show the templates store modal and allow selecting blueprints', function () { - cy.makeBlueprints().as('blueprints'); - cy.intercept('**/notification-templates**').as('getTemplates'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.getByTestId('all-workflow-tile').should('exist').should('not.be.disabled').click(); - cy.getByTestId('templates-store-modal').should('be.visible'); - - cy.get('@blueprints').then((blueprints) => { - const { name: firstBlueprintName } = getTemplateDetails(blueprints[0].name); - cy.getByTestId('templates-store-modal-blueprint-name').contains(firstBlueprintName); - cy.getByTestId('templates-store-modal-blueprint-description').contains(blueprints[0].description); - - const { name: secondBlueprintName } = getTemplateDetails(blueprints[1].name); - cy.getByTestId('templates-store-modal-blueprint-item').contains(secondBlueprintName).click(); - cy.getByTestId('templates-store-modal-blueprint-name').contains(secondBlueprintName); - cy.getByTestId('templates-store-modal-blueprint-description').contains(blueprints[1].description); - }); - }); - - it('should allow creating workflow from the blueprint and redirect to the editor', function () { - cy.makeBlueprints().as('blueprints'); - cy.intercept('GET', '**/notification-templates**').as('getTemplates'); - cy.intercept('POST', '**/notification-templates**').as('createTemplate'); - cy.intercept('**/blueprints/group-by-category').as('getBlueprints'); - cy.visit('/workflows'); - cy.wait('@getTemplates'); - cy.wait('@getBlueprints'); - - cy.getByTestId('all-workflow-tile').should('exist').should('not.be.disabled').click(); - cy.getByTestId('templates-store-modal').should('be.visible'); - cy.getByTestId('templates-store-modal-use-template').should('be.enabled').click(); - - cy.wait('@createTemplate'); - - cy.location('pathname').should('contain', `/workflows/edit`); - }); -}); diff --git a/apps/web/cypress/tests/tenants-page.spec.ts b/apps/web/cypress/tests/tenants-page.spec.ts deleted file mode 100644 index 2df27625a439..000000000000 --- a/apps/web/cypress/tests/tenants-page.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -describe('Tenants Page', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should display empty tenants state', function () { - cy.visit('/tenants'); - - cy.getByTestId('no-tenant-placeholder').contains('Add the first tenant for the'); - }); - - it('should add new tenant', function () { - createTenant(); - - cy.visit('/tenants'); - - cy.getByTestId('tenants-list-table').find('td:nth-child(1)').contains('Test Tenant'); - cy.getByTestId('tenants-list-table').find('td:nth-child(2)').contains('test-tenant'); - }); - - it('should update tenant', function () { - createTenant(); - - //update tenant name - cy.getByTestId('tenants-list-table') - .find('tr') - .eq(1) - .click({ force: true }) - .then(() => { - cy.getByTestId('tenant-name').clear().type('New Name'); - cy.getByTestId('update-tenant-sidebar-submit').click(); - }); - - cy.waitForNetworkIdle(500); - - cy.getByTestId('tenants-list-table').find('td:nth-child(1)').contains('New Name'); - cy.getByTestId('tenants-list-table').find('td:nth-child(2)').contains('test-tenant'); - - //update tenant identifier - cy.getByTestId('tenants-list-table') - .find('tr') - .eq(1) - .click({ force: true }) - .then(() => { - cy.getByTestId('tenant-identifier').clear().type('new-identifier'); - cy.getByTestId('update-tenant-sidebar-submit').click(); - }); - - cy.getByTestId('tenants-list-table').find('td:nth-child(1)').contains('New Name'); - cy.getByTestId('tenants-list-table').find('td:nth-child(2)').contains('new-identifier'); - }); - - function createTenant() { - cy.visit('/tenants'); - - cy.getByTestId('add-tenant').click(); - cy.getByTestId('tenant-name').type('Test Tenant'); - cy.getByTestId('tenant-custom-properties').type('{"Org Name" : "Nike"}', { parseSpecialCharSequences: false }); - cy.getByTestId('create-tenant-sidebar-submit').click(); - } -}); diff --git a/apps/web/cypress/tests/translations/translation-group.spec-ee.ts b/apps/web/cypress/tests/translations/translation-group.spec-ee.ts deleted file mode 100644 index 497914472e20..000000000000 --- a/apps/web/cypress/tests/translations/translation-group.spec-ee.ts +++ /dev/null @@ -1,57 +0,0 @@ -describe('Translations Group Page', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should display translations group page', function () { - cy.visit('/translations'); - - cy.getByTestId('translation-title').should('have.text', 'Translations'); - }); - - it('should add a new translations group', function () { - createTranslationGroup(); - cy.getByTestId('test-group').click({ force: true }); - cy.getByTestId('translation-filename').should('have.text', 'Empty...'); - cy.getByTestId('translation-keys-value').should('have.text', ''); - }); - - it('should delete a translations group', function () { - createTranslationGroup(); - cy.getByTestId('delete-group-btn').click(); - cy.getByTestId('delete-group-submit-btn').should('have.text', 'Delete group').click(); - cy.getByTestId('add-group-btn').should('exist'); - }); - - it('should upload translation file', function () { - createTranslationGroup(); - - cy.getByTestId('upload-files-container').find('input').attachFile('translation.json'); - cy.getByTestId('upload-submit-btn').click(); - cy.visit('/translations'); - cy.getByTestId('test-group').click(); - cy.getByTestId('translation-filename').should('have.text', 'translation.json'); - cy.getByTestId('translation-keys-value').should('have.text', 15); - }); -}); - -function createTranslationGroup() { - const identifier = 'test-group'; - - cy.visit('/translations'); - cy.waitForNetworkIdle(500); - cy.getByTestId('translation-title').should('have.text', 'Translations'); - cy.getByTestId('add-group-btn').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('default-language-select').click().type('Hindi'); - - cy.get('.mantine-Select-item').first().click(); - cy.getByTestId('default-language-submit-btn').click(); - cy.waitForNetworkIdle(500); - - cy.getByTestId('group-name-input').type('Test Group'); - cy.getByTestId('group-identifier-input').should('have.value', identifier); - cy.getByTestId('add-group-submit-btn').click({ force: true }); - cy.waitForNetworkIdle(1000); -} diff --git a/apps/web/cypress/tests/workflows.spec.ts b/apps/web/cypress/tests/workflows.spec.ts deleted file mode 100644 index 64a8cc21c69e..000000000000 --- a/apps/web/cypress/tests/workflows.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { TriggerTypeEnum } from '@novu/shared'; - -describe('Workflows Page', function () { - beforeEach(function () { - cy.initializeSession().as('session'); - }); - - it('should allow searching by name or identifier', function () { - cy.createWorkflows({ - userId: this.session.user._id, - organizationId: this.session.organization._id, - environmentId: this.session.environment._id, - workflows: [ - { name: 'SMS Workflow' }, - { triggers: [{ identifier: 'sms-test', variables: [], type: TriggerTypeEnum.EVENT }] }, - ], - }); - - cy.intercept('GET', '**/v1/notification-groups').as('notification-groups'); - cy.intercept('GET', '**/v1/notification-templates*').as('notification-templates'); - - cy.visit('/workflows'); - cy.wait(['@notification-groups', '@notification-templates']); - - cy.getByTestId('workflows-search-input').type('SMS'); - cy.wait('@notification-templates'); - - cy.getByTestId('workflow-row-name').should('have.length', 2); - cy.getByTestId('workflow-row-name').contains('SMS Workflow'); - cy.getByTestId('workflow-row-trigger-identifier').contains('sms-test'); - }); - - it('should allow clearing the search', function () { - cy.intercept('GET', '**/v1/notification-groups').as('notification-groups'); - cy.intercept('GET', '**/v1/notification-templates*').as('notification-templates'); - - cy.visit('/workflows'); - cy.wait(['@notification-groups', '@notification-templates']); - - cy.getByTestId('workflows-search-input').type('This template does not exist'); - cy.wait('@notification-templates'); - cy.getByTestId('workflows-no-matches').should('exist'); - - cy.getByTestId('search-input-clear').click(); - cy.getByTestId('workflows-no-matches').should('not.exist'); - }); - - it('should show no results view', function () { - cy.intercept('GET', '**/v1/notification-groups').as('notification-groups'); - cy.intercept('GET', '**/v1/notification-templates*').as('notification-templates'); - - cy.visit('/workflows'); - cy.wait(['@notification-groups', '@notification-templates']); - - cy.getByTestId('workflows-search-input').type('This template does not exist'); - cy.wait('@notification-templates'); - - cy.getByTestId('workflow-row-name').should('have.length', 0); - cy.getByTestId('workflows-no-matches').should('exist'); - }); -}); diff --git a/apps/web/cypress/tsconfig.json b/apps/web/cypress/tsconfig.json deleted file mode 100644 index 6bbedd9daa39..000000000000 --- a/apps/web/cypress/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": ["./**/*.ts", "global.d.ts", "../src/**/*.cy.{js,jsx,ts,tsx}"], - "exclude": [], - "compilerOptions": { - "types": ["cypress"], - "lib": ["es2015", "dom"], - "isolatedModules": false, - "allowJs": true, - "noEmit": true - } -} diff --git a/apps/web/package.json b/apps/web/package.json index 77d69a6c8341..70f66aeec4e7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -6,6 +6,7 @@ "start": "pnpm panda --watch & cross-env NODE_OPTIONS=--max_old_space_size=8192 PORT=4200 react-app-rewired start", "prebuild": "rimraf build", "build": "pnpm panda && cross-env NODE_OPTIONS=--max_old_space_size=4096 GENERATE_SOURCEMAP=false react-app-rewired --max_old_space_size=4096 build", + "build:test": "pnpm panda && cross-env REACT_APP_HUBSPOT_EMBED=44416662 REACT_APP_API_URL=http://127.0.0.1:1336 NODE_OPTIONS=--max_old_space_size=4096 GENERATE_SOURCEMAP=false react-app-rewired --max_old_space_size=4096 build", "precommit": "lint-staged", "docker:build": "docker buildx build --load -f ./Dockerfile -t novu-web ./../.. $DOCKER_BUILD_ARGUMENTS", "docker:build:depot": "depot build -f ./Dockerfile -t novu-web ./../.. --load", @@ -14,17 +15,13 @@ "start:static:build": "pnpm envsetup:docker && http-server build -p 4200 --proxy http://127.0.0.1:4200?", "start:docker": "pnpm build && pnpm start:static:build", "start:dev": "pnpm start", - "cypress:run": "cross-env NODE_ENV=test cypress run", - "cypress:run:ee": "cross-env NODE_ENV=test cypress run --spec cypress/tests/**/*-ee.{js,jsx,ts,tsx}", - "cypress:install": "cypress install", - "cypress:open": "cross-env NODE_ENV=test cypress open", - "cypress:run:components": "cross-env NODE_OPTIONS=--max_old_space_size=4096 NODE_ENV=test cypress run --component", - "playwright:install": "playwright install --with-deps", - "playwright:test": "playwright test", - "playwright:test-ui": "playwright test --ui", - "playwright:codegen": "playwright codegen", - "playwright:show-report": "npx playwright show-report", - "playwright:merge-report": "playwright merge-reports --reporter html", + "start:test": "http-server build -p 4200 --proxy http://127.0.0.1:4200?", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:install": "playwright install --with-deps", + "test:e2e:codegen": "playwright codegen", + "test:e2e:show-report": "npx playwright show-report", + "test:e2e:merge-report": "playwright merge-reports --reporter html", "start:api": "cd ../../ && pnpm start:api:test", "storybook": "storybook dev -p 6006 -s public", "build-storybook": "storybook build -s public", @@ -33,15 +30,13 @@ "link:submodules": "pnpm link ../../enterprise/packages/translation-web && pnpm link ../../enterprise/packages/billing-web" }, "dependencies": { - "@ant-design/icons": "4.6.2", "@babel/plugin-proposal-optional-chaining": "^7.20.7", "@babel/plugin-transform-react-display-name": "^7.18.6", "@babel/plugin-transform-runtime": "^7.23.2", - "@cypress/react": "^7.0.3", - "@cypress/webpack-dev-server": "^3.6.1", "@editorjs/editorjs": "^2.19.3", "@editorjs/paragraph": "^2.8.0", "@emotion/babel-plugin": "^11.7.2", + "@emotion/css": "^11.10.5", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@fortawesome/fontawesome-svg-core": "^6.4.0", @@ -59,25 +54,26 @@ "@mantine/prism": "^5.7.1", "@mantine/spotlight": "^5.7.1", "@monaco-editor/react": "^4.6.0", - "@novu/design-system": "^0.24.2", - "@novu/notification-center": "^0.24.2", - "@novu/novui": "^0.0.1", - "@novu/shared": "^0.24.1", - "@novu/shared-web": "^0.24.1", + "@novu/design-system": "workspace:*", + "@novu/notification-center": "workspace:*", + "@novu/novui": "workspace:*", + "@novu/shared": "workspace:*", "@rive-app/react-canvas": "^4.8.1", + "@rjsf/core": "^5.17.1", + "@rjsf/validator-ajv8": "^5.17.1", "@segment/analytics-next": "^1.48.0", "@sentry/react": "^7.40.0", "@sentry/tracing": "^7.40.0", "@storybook/addon-docs": "^7.4.2", "@storybook/theming": "^7.4.2", + "@stripe/react-stripe-js": "^2.5.0", + "@stripe/stripe-js": "^2.4.0", "@tanstack/react-query": "^4.20.4", "@tanstack/react-query-devtools": "^4.20.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^29.5.0", "@types/node": "^12.0.0", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", "@types/react-table": "^7.7.12", "ace-builds": "^1.4.12", "antd": "^4.10.0", @@ -87,11 +83,13 @@ "chart.js": "^3.7.1", "customize-cra": "^1.0.0", "date-fns": "^2.29.2", + "dotenv": "^16.4.5", "eslint-plugin-react-hooks": "^4.4.0", "handlebars": "^4.7.7", + "highlight.js": "11.9.0", "html-webpack-plugin": "5.5.3", "jwt-decode": "^3.1.2", - "launchdarkly-react-client-sdk": "^3.0.6", + "launchdarkly-react-client-sdk": "^3.3.2", "less": "^4.1.0", "localforage": "^1.10.0", "lodash.capitalize": "^4.2.1", @@ -100,15 +98,16 @@ "lodash.get": "^4.3.2", "lodash.isequal": "^4.5.0", "lodash.set": "^4.3.2", + "mdx-bundler": "10.0.2", "monaco-editor": "^0.45.0", "polished": "^4.1.3", - "react": "^17.0.1", + "react": "^18.3.1", "react-ace": "^9.4.3", "react-chartjs-2": "^4.0.1", "react-color": "^2.19.3", "react-css-theme-switcher": "^0.3.0", "react-custom-scrollbars": "^4.2.1", - "react-dom": "^17.0.1", + "react-dom": "^18.3.1", "react-editor-js": "^1.9.0", "react-error-boundary": "^3.1.4", "react-flow-renderer": "^10.2.2", @@ -131,22 +130,18 @@ "web-vitals": "^1.0.1", "zod": "^3.22.4" }, - "optionalDependencies": { - "@novu/ee-billing-web": "^0.24.2", - "@novu/ee-echo-web": "^0.24.2", - "@novu/ee-translation-web": "^0.24.2" - }, "devDependencies": { "@babel/polyfill": "^7.12.1", "@babel/preset-env": "^7.23.2", "@babel/preset-react": "^7.13.13", "@babel/preset-typescript": "^7.13.0", "@babel/runtime": "^7.20.13", - "@novu/dal": "^0.24.2", - "@novu/testing": "^0.24.2", - "@pandacss/dev": "^0.38.0", - "@pandacss/studio": "^0.38.0", - "@playwright/test": "^1.42.1", + "@faker-js/faker": "^6.0.0", + "@novu/dal": "workspace:*", + "@novu/testing": "workspace:*", + "@pandacss/dev": "^0.40.1", + "@pandacss/studio": "^0.40.1", + "@playwright/test": "^1.44.0", "@storybook/addon-actions": "^7.4.2", "@storybook/addon-essentials": "^7.4.2", "@storybook/addon-links": "^7.4.2", @@ -156,14 +151,9 @@ "@storybook/react": "^7.4.2", "@storybook/react-webpack5": "^7.4.2", "@testing-library/jest-dom": "^4.2.4", - "@types/react": "^17.0.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@types/testing-library__jest-dom": "^5.14.5", - "cypress": "^13.3.1", - "cypress-file-upload": "^5.0.8", - "cypress-localstorage-commands": "^2.2.4", - "cypress-network-idle": "^1.14.2", - "cypress-wait-until": "^2.0.1", - "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-storybook": "^0.6.13", "http-server": "^0.13.0", "less-loader": "4.1.0", diff --git a/apps/web/panda.config.ts b/apps/web/panda.config.ts index a7a8d53fde9c..753a42040765 100644 --- a/apps/web/panda.config.ts +++ b/apps/web/panda.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ ], // Files to exclude - exclude: ['**/*.cy.{js,jsx,ts,tsx}', '**/*/styled-system'], + exclude: ['**/*.spec.{js,jsx,ts,tsx}', '**/*/styled-system'], presets: [novuPandaPreset], diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index c6872da1b355..9965ed0ba1f0 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -2,13 +2,7 @@ import { defineConfig, devices } from '@playwright/test'; import dotenv from 'dotenv'; import path from 'path'; -dotenv.config({ path: path.resolve(__dirname, '.env.playwirght.test') }); - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); +dotenv.config({ path: path.resolve(__dirname, '.env.test') }); /** * See https://playwright.dev/docs/test-configuration. @@ -20,9 +14,9 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 4 : undefined, + retries: process.env.CI ? 5 : 3, + /* Use 4 workers in CI, 50% of CPU count in local */ + workers: process.env.CI ? 4 : '25%', /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: process.env.CI ? 'blob' : 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -32,8 +26,12 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + permissions: ['clipboard-read'], + }, + timeout: 30_000, + expect: { + timeout: 15000, }, - timeout: 60_000, /* Configure projects for major browsers */ projects: [ { diff --git a/apps/web/public/env-config.js b/apps/web/public/env-config.js index 761a04116215..18bf0d9fe77b 100644 --- a/apps/web/public/env-config.js +++ b/apps/web/public/env-config.js @@ -1,5 +1,11 @@ -window._env_ = { - SKIP_PREFLIGHT_CHECK: 'true', - REACT_APP_ENVIRONMENT: 'dev', - REACT_APP_VERSION: '$npm_package_version', -}; +window._env_ = Object.assign( + { + SKIP_PREFLIGHT_CHECK: 'true', + REACT_APP_ENVIRONMENT: 'dev', + REACT_APP_VERSION: '$npm_package_version', + IS_IMPROVED_ONBOARDING_ENABLED: 'false', + IS_INFORMATION_ARCHITECTURE_ENABLED: 'true', + }, + // Allow overrides of the above defaults + window._env_ +); diff --git a/apps/web/public/index.html b/apps/web/public/index.html index 5e4eb21eb226..ebc52278de4a 100644 --- a/apps/web/public/index.html +++ b/apps/web/public/index.html @@ -42,6 +42,38 @@ --> Novu Manage Platform + @@ -57,6 +89,31 @@ <% } %> + +
+ + + + + + + + + + + + + + + + + + + + + + +
<% if ( process.env.REACT_APP_DOCKER_HOSTED_ENV === 'false' ) { %>