diff --git a/.github/workflows/cloud-frontend-gcp.yml b/.github/workflows/cloud-frontend-gcp.yml index a2133a762f..f3119c4ac1 100644 --- a/.github/workflows/cloud-frontend-gcp.yml +++ b/.github/workflows/cloud-frontend-gcp.yml @@ -119,7 +119,7 @@ jobs: TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_TJDB_SQL_MODE_DISABLE }} TOOLJET_SERVER_URL: ${{ secrets.CLOUD_TOOLJET_SERVER_URL }} TOOLJET_EDITION: cloud - WEBSITE_SIGNUP_URL: https://website-stage.tooljet.ai/ai-create-account + WEBSITE_SIGNUP_URL: https://website-stage.tooljet.ai/signup - name: ๐ Deploy to Netlify run: | @@ -138,5 +138,52 @@ jobs: SERVER_IP: ${{ secrets.CLOUD_SERVER_IP }} TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_TJDB_SQL_MODE_DISABLE }} TOOLJET_SERVER_URL: ${{ secrets.CLOUD_TOOLJET_SERVER_URL }} - WEBSITE_SIGNUP_URL: https://website-stage.tooljet.ai/ai-create-account + WEBSITE_SIGNUP_URL: https://website-stage.tooljet.ai/signup TOOLJET_EDITION: cloud + + Purge_Cloudflare_Cache: + needs: deploy + runs-on: ubuntu-latest + + steps: + - name: โ Check user authorization + run: | + allowed_user1=${{ secrets.ALLOWED_USER1_USERNAME }} + allowed_user2=${{ secrets.ALLOWED_USER2_USERNAME }} + allowed_user3=${{ secrets.ALLOWED_USER3_USERNAME }} + + if [[ "${{ github.actor }}" != "$allowed_user1" && \ + "${{ github.actor }}" != "$allowed_user2" && \ + "${{ github.actor }}" != "$allowed_user3" ]]; then + echo "โ User '${{ github.actor }}' is not authorized to trigger this workflow." + exit 1 + else + echo "โ User '${{ github.actor }}' is authorized." + fi + + - name: ๐งน Purge Cloudflare Cache + continue-on-error: true + run: | + echo "๐ Purging Cloudflare cache for specific URLs..." + response=$(curl -s -w "\n%{http_code}" -X POST \ + "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID_PROD }}/purge_cache" \ + -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN_PROD }}" \ + -H "Content-Type: application/json" \ + --data '{ + "files": [ + "${{ secrets.CLOUDFLARE_CONFIG_URL_STAGE }}", + "${{ secrets.CLOUDFLARE_METADATA_URL_STAGE }}" + ] + }') + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" = "200" ]; then + echo "โ Cloudflare cache purged successfully for specified URLs" + echo "$body" + else + echo "โ ๏ธ Cloudflare cache purge failed with status code: $http_code" + echo "$body" + exit 1 + fi diff --git a/.github/workflows/cloud-frontend.yml b/.github/workflows/cloud-frontend.yml index 067761700f..b09bf07733 100644 --- a/.github/workflows/cloud-frontend.yml +++ b/.github/workflows/cloud-frontend.yml @@ -123,7 +123,7 @@ jobs: - name: ๐ Deploy to Netlify run: | - npm install -g netlify-cli@17.10.1 + npm install -g netlify-cli netlify deploy --prod --dir=frontend/build --auth=$NETLIFY_AUTH_TOKEN --site=${{ secrets.CLOUD_PROD_NETLIFY_SITE_ID }} working-directory: repo env: @@ -138,5 +138,52 @@ jobs: SERVER_IP: ${{ secrets.CLOUD_PROD_CLOUD_SERVER_IP }} TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_PROD_TJDB_SQL_MODE_DISABLE }} TOOLJET_SERVER_URL: ${{ secrets.CLOUD_PROD_TOOLJET_SERVER_URL }} - WEBSITE_SIGNUP_URL: https://tooljet.ai/ai-create-account + WEBSITE_SIGNUP_URL: https://www.tooljet.ai/create-account TOOLJET_EDITION: cloud + + Purge_Cloudflare_Cache: + needs: deploy + runs-on: ubuntu-latest + + steps: + - name: โ Check user authorization + run: | + allowed_user1=${{ secrets.ALLOWED_USER1_USERNAME }} + allowed_user2=${{ secrets.ALLOWED_USER2_USERNAME }} + allowed_user3=${{ secrets.ALLOWED_USER3_USERNAME }} + + if [[ "${{ github.actor }}" != "$allowed_user1" && \ + "${{ github.actor }}" != "$allowed_user2" && \ + "${{ github.actor }}" != "$allowed_user3" ]]; then + echo "โ User '${{ github.actor }}' is not authorized to trigger this workflow." + exit 1 + else + echo "โ User '${{ github.actor }}' is authorized." + fi + + - name: ๐งน Purge Cloudflare Cache + continue-on-error: true + run: | + echo "๐ Purging Cloudflare cache for specific URLs..." + response=$(curl -s -w "\n%{http_code}" -X POST \ + "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID_PROD }}/purge_cache" \ + -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN_PROD }}" \ + -H "Content-Type: application/json" \ + --data '{ + "files": [ + "${{ secrets.CLOUDFLARE_CONFIG_URL_PROD }}", + "${{ secrets.CLOUDFLARE_METADATA_URL_PROD }}" + ] + }') + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" = "200" ]; then + echo "โ Cloudflare cache purged successfully for specified URLs" + echo "$body" + else + echo "โ ๏ธ Cloudflare cache purge failed with status code: $http_code" + echo "$body" + exit 1 + fi diff --git a/.github/workflows/cypress-platform.yml b/.github/workflows/cypress-platform.yml index 9958840934..680fe00884 100644 --- a/.github/workflows/cypress-platform.yml +++ b/.github/workflows/cypress-platform.yml @@ -37,6 +37,29 @@ jobs: echo "Labels: ${{ toJSON(github.event.pull_request.labels.*.name) }}" echo "Matrix edition: ${{ matrix.edition }}" + - name: Free up disk space + run: | + echo "Available disk space before cleanup:" + df -h + + # Remove unnecessary packages + sudo apt-get remove -y '^aspnetcore-.*' '^dotnet-.*' '^llvm-.*' '^php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel || true + sudo apt-get autoremove -y + sudo apt-get clean + + # Remove large directories + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/share/boost + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + # Clean Docker + docker system prune -af --volumes + + echo "Available disk space after cleanup:" + df -h + - name: Checkout uses: actions/checkout@v3 with: @@ -56,32 +79,47 @@ jobs: - name: Build CE Docker image if: matrix.edition == 'ce' - uses: docker/build-push-action@v4 - with: - context: . - file: docker/ce-production.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building CE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f docker/ce-production.Dockerfile \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce \ + --no-cache \ + --load \ + . + + echo "Pushing CE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Build EE Docker image if: matrix.edition == 'ee' - uses: docker/build-push-action@v4 - with: - context: . - build-args: | - CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} - BRANCH_NAME=${{ github.event.pull_request.head.ref }} - file: cypress-tests/cypress-lts.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building EE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f cypress-tests/cypress-lts.Dockerfile \ + --build-arg CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} \ + --build-arg BRANCH_NAME=${{ github.event.pull_request.head.ref }} \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee \ + --no-cache \ + --load \ + . + + echo "Pushing EE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Set up environment variables run: | @@ -119,7 +157,10 @@ jobs: echo "LICENSE_KEY=${{ secrets.RENDER_LICENSE_KEY }}" >> .env - name: clean up old docker containers - run: docker system prune -af + run: | + docker system prune -af --volumes + echo "Disk space after Docker cleanup:" + df -h - name: Pulling the docker-compose file run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data @@ -182,6 +223,112 @@ jobs: echo "โ Server is ready!" + - name: Test database connection + run: | + # Wait for database to be ready + echo "Testing database connection..." + docker exec Tooljet-postgres psql -U postgres -d tooljet_development -c "SELECT current_database();" + + - name: Create delete_user procedure + run: | + echo "Creating delete_user stored procedure..." + docker exec Tooljet-postgres psql -U postgres -d tooljet_development -c " + CREATE OR REPLACE PROCEDURE delete_user(p_email TEXT) + LANGUAGE plpgsql + AS \$\$ + DECLARE + v_user_id UUID; + v_organization_ids UUID[]; + v_organizations_to_delete UUID[]; + v_log_message TEXT; + BEGIN + -- Log start of procedure + RAISE NOTICE 'Starting delete_user procedure for email: %', p_email; + + -- Get user_id from email + SELECT id INTO v_user_id + FROM users + WHERE email = p_email; + + IF v_user_id IS NULL THEN + RAISE EXCEPTION 'User with email % not found', p_email; + END IF; + + RAISE NOTICE 'User found with id: %', v_user_id; + + -- Get organization_ids for the user + SELECT ARRAY_AGG(organization_id) INTO v_organization_ids + FROM organization_users + WHERE user_id = v_user_id; + + RAISE NOTICE 'Found % organizations for user', COALESCE(ARRAY_LENGTH(v_organization_ids, 1), 0); + + -- Get organizations with exactly one user (to be deleted) + SELECT ARRAY_AGG(organization_id) INTO v_organizations_to_delete + FROM ( + SELECT organization_id + FROM organization_users + WHERE organization_id = ANY(v_organization_ids) + GROUP BY organization_id + HAVING COUNT(*) = 1 + ) subquery; + + -- Add organizations where the user is the owner + v_organizations_to_delete := v_organizations_to_delete; + + RAISE NOTICE 'Found % organizations to delete', COALESCE(ARRAY_LENGTH(v_organizations_to_delete, 1), 0); + + -- Delete apps in organizations to be deleted + WITH deleted_apps AS ( + DELETE FROM apps + WHERE organization_id = ANY(v_organizations_to_delete) + RETURNING id + ) + SELECT 'Deleted ' || COUNT(*) || ' apps' INTO v_log_message FROM deleted_apps; + + RAISE NOTICE '%', v_log_message; + + -- Delete data_sources in organizations to be deleted + WITH deleted_data_sources AS ( + DELETE FROM data_sources + WHERE organization_id = ANY(v_organizations_to_delete) + RETURNING id + ) + SELECT 'Deleted ' || COUNT(*) || ' data sources' INTO v_log_message FROM deleted_data_sources; + + RAISE NOTICE '%', v_log_message; + + -- Delete audit_logs for organizations to be deleted and for the user + WITH deleted_audit_logs AS ( + DELETE FROM audit_logs + WHERE organization_id = ANY(v_organization_ids) + OR user_id = v_user_id + RETURNING id + ) + SELECT 'Deleted ' || COUNT(*) || ' audit logs' INTO v_log_message FROM deleted_audit_logs; + + RAISE NOTICE '%', v_log_message; + + -- Delete organizations + WITH deleted_organizations AS ( + DELETE FROM organizations + WHERE id = ANY(v_organizations_to_delete) + RETURNING id + ) + SELECT 'Deleted ' || COUNT(*) || ' organizations' INTO v_log_message FROM deleted_organizations; + + RAISE NOTICE '%', v_log_message; + + -- Finally, delete the user + DELETE FROM users + WHERE id = v_user_id; + + RAISE NOTICE 'Deleted user with id: %', v_user_id; + RAISE NOTICE 'User deletion procedure completed successfully for email: %', p_email; + END; + \$\$;" + echo "โ delete_user procedure created successfully" + - name: Create Cypress environment file id: create-json-tj uses: jsdaniell/create-json@1.1.2 @@ -250,32 +397,47 @@ jobs: - name: Build CE Docker image if: matrix.edition == 'ce' - uses: docker/build-push-action@v4 - with: - context: . - file: docker/ce-production.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building CE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f docker/ce-production.Dockerfile \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce \ + --no-cache \ + --load \ + . + + echo "Pushing CE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Build EE Docker image if: matrix.edition == 'ee' - uses: docker/build-push-action@v4 - with: - context: . - build-args: | - CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} - BRANCH_NAME=${{ github.event.pull_request.head.ref }} - file: cypress-tests/cypress-lts.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building EE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f cypress-tests/cypress-lts.Dockerfile \ + --build-arg CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} \ + --build-arg BRANCH_NAME=${{ github.event.pull_request.head.ref }} \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee \ + --no-cache \ + --load \ + . + + echo "Pushing EE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Set up environment variables run: | @@ -315,7 +477,10 @@ jobs: echo "LICENSE_KEY=${{ secrets.RENDER_LICENSE_KEY }}" >> .env - name: clean up old docker containers - run: docker system prune -af + run: | + docker system prune -af --volumes + echo "Disk space after Docker cleanup:" + df -h - name: Pulling the docker-compose file run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data @@ -436,32 +601,47 @@ jobs: - name: Build CE Docker image if: matrix.edition == 'ce' - uses: docker/build-push-action@v4 - with: - context: . - file: docker/ce-production.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building CE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f docker/ce-production.Dockerfile \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce \ + --no-cache \ + --load \ + . + + echo "Pushing CE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Build EE Docker image if: matrix.edition == 'ee' - uses: docker/build-push-action@v4 - with: - context: . - build-args: | - CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} - BRANCH_NAME=${{ github.event.pull_request.head.ref }} - file: cypress-tests/cypress-lts.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building EE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f cypress-tests/cypress-lts.Dockerfile \ + --build-arg CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} \ + --build-arg BRANCH_NAME=${{ github.event.pull_request.head.ref }} \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee \ + --no-cache \ + --load \ + . + + echo "Pushing EE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Set up environment variables run: | @@ -501,7 +681,10 @@ jobs: echo "LICENSE_KEY=${{ secrets.RENDER_LICENSE_KEY }}" >> .env - name: clean up old docker containers - run: docker system prune -af + run: | + docker system prune -af --volumes + echo "Disk space after Docker cleanup:" + df -h - name: Pulling the docker-compose file run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data @@ -635,32 +818,47 @@ jobs: - name: Build CE Docker image if: matrix.edition == 'ce' - uses: docker/build-push-action@v4 - with: - context: . - file: docker/ce-production.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building CE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f docker/ce-production.Dockerfile \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce \ + --no-cache \ + --load \ + . + + echo "Pushing CE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Build EE Docker image if: matrix.edition == 'ee' - uses: docker/build-push-action@v4 - with: - context: . - build-args: | - CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} - BRANCH_NAME=${{ github.event.pull_request.head.ref }} - file: cypress-tests/cypress-lts.Dockerfile - push: true - tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee - platforms: linux/amd64 - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "Building EE Docker image..." + docker buildx build \ + --platform=linux/amd64 \ + -f cypress-tests/cypress-lts.Dockerfile \ + --build-arg CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} \ + --build-arg BRANCH_NAME=${{ github.event.pull_request.head.ref }} \ + -t tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee \ + --no-cache \ + --load \ + . + + echo "Pushing EE Docker image..." + docker push tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee + + echo "Cleaning up build cache..." + docker builder prune -af + + echo "Disk space after build:" + df -h - name: Set up environment variables run: | @@ -699,7 +897,10 @@ jobs: echo "LICENSE_KEY=${{ secrets.RENDER_LICENSE_KEY }}" >> .env - name: clean up old docker containers - run: docker system prune -af + run: | + docker system prune -af --volumes + echo "Disk space after Docker cleanup:" + df -h - name: Pulling the docker-compose file run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data diff --git a/.github/workflows/deploy-to-stage.yml b/.github/workflows/deploy-to-stage.yml index 99b9f930a7..c0354e5531 100644 --- a/.github/workflows/deploy-to-stage.yml +++ b/.github/workflows/deploy-to-stage.yml @@ -4,18 +4,18 @@ on: workflow_dispatch: inputs: branch_name: - description: 'Git branch to build from' + description: "Git branch to build from" required: true - default: 'main' + default: "lts-3.16" dockerfile_path: - description: 'Path to Dockerfile' + description: "Path to Dockerfile" required: true - default: './docker/LTS/cloud/cloud-server.Dockerfile' + default: "./docker/LTS/cloud/cloud-server.Dockerfile" type: choice options: - ./docker/LTS/cloud/cloud-server.Dockerfile docker_tag: - description: 'Docker tag suffix (e.g., cloud-staging-v14)' + description: "Docker tag suffix (e.g., cloud-staging-v14)" required: true jobs: @@ -82,7 +82,7 @@ jobs: BRANCH_NAME=${{ github.event.inputs.branch_name }} - name: Show the full Docker tag - run: | + run: | echo "โ Docker image tagged as: $IMAGE_TAG" # Deploy to AKS @@ -144,6 +144,7 @@ jobs: - name: ๐ฅ Manual Git checkout with submodules run: | set -e + BRANCH="${{ github.event.inputs.branch_name }}" REPO="https://x-access-token:${{ secrets.CUSTOM_GITHUB_TOKEN }}@github.com/${{ github.repository }}" @@ -155,6 +156,11 @@ jobs: git clone --recurse-submodules --depth=1 --branch "$BRANCH" "$REPO" repo cd repo + echo "๐ Main repo: verifying checkout" + MAIN_CURRENT=$(git rev-parse --abbrev-ref HEAD) + echo "โ Main repo: successfully checked out branch $MAIN_CURRENT" + echo "๐ Main repo: current commit $(git rev-parse --short HEAD): $(git log -1 --pretty=%s)" + echo "๐ Updating submodules" git submodule update --init --recursive @@ -162,13 +168,44 @@ jobs: BRANCH="$BRANCH" git submodule foreach --recursive bash -c ' name="$sm_path" - echo "โช $name: checking out branch $BRANCH" + echo "" + echo "Entering '\''$name'\''" + echo "โช $name: trying to checkout branch '\''$BRANCH'\''" + if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null; then - git fetch origin "$BRANCH:$BRANCH" - git checkout "$BRANCH" + git fetch origin "$BRANCH:$BRANCH" || { + echo "โ $name: fetch failed for $BRANCH" + exit 1 + } + + PREV=$(git rev-parse --short HEAD || echo "unknown") + git checkout "$BRANCH" || { + echo "โ $name: checkout failed for $BRANCH" + exit 1 + } + + echo "Previous HEAD position was $PREV: $(git log -1 --pretty=%s || echo 'unknown')" + echo "โ $name: checked out branch $BRANCH" else - echo "โ ๏ธ Branch not found, falling back to main" - git checkout main && git pull origin main + echo "โ ๏ธ $name: branch '$BRANCH' not found on origin. Falling back to 'lts-3.16'" + PREV=$(git rev-parse --short HEAD || echo "unknown") + git fetch origin lts-3.16:lts-3.16 || { + echo "โ $name: fetch failed for lts-3.16" + exit 1 + } + git checkout lts-3.16 || { + echo "โ $name: fallback to lts-3.16 failed" + exit 1 + } + echo "Previous HEAD position was $PREV: $(git log -1 --pretty=%s || echo 'unknown')" + echo "โ $name: now on branch lts-3.16" + fi + + CURRENT=$(git rev-parse --abbrev-ref HEAD) + echo "๐ $name: current branch = $CURRENT" + if [ "$CURRENT" != "$BRANCH" ] && [ "$CURRENT" != "lts-3.16" ]; then + echo "โ $name: unexpected branch state โ wanted '$BRANCH' or fallback 'lts-3.16', got '$CURRENT'" + exit 1 fi ' @@ -181,7 +218,7 @@ jobs: run: npm install working-directory: repo - - name: ๐ ๏ธ Build project + - name: ๐ ๏ธ Build the project run: npm run build:plugins:prod && npm run build:frontend working-directory: repo env: diff --git a/.github/workflows/manual-docker-build.yml b/.github/workflows/manual-docker-build.yml index c2d8251318..960f970dfe 100644 --- a/.github/workflows/manual-docker-build.yml +++ b/.github/workflows/manual-docker-build.yml @@ -4,18 +4,19 @@ on: workflow_dispatch: inputs: branch_name: - description: 'Git branch to build from' + description: "Git branch to build from" required: true - default: 'main' + default: "main" dockerfile_path: - description: 'Path to Dockerfile' + description: "Path to Dockerfile" required: true type: choice options: - ./docker/LTS/ee/ee-production.Dockerfile - ./docker/pre-release/ee/ee-production.Dockerfile + - ./docker/LTS/cloud/cloud-server.Dockerfile docker_tag: - description: 'Docker tag suffix (e.g., pre-release-14)' + description: "Docker tag suffix (e.g., pre-release-14)" required: true jobs: diff --git a/.github/workflows/update-test-system.yml b/.github/workflows/update-test-system.yml index 81b8305f3c..cb95b20403 100644 --- a/.github/workflows/update-test-system.yml +++ b/.github/workflows/update-test-system.yml @@ -4,21 +4,21 @@ on: workflow_dispatch: inputs: branch_name: - description: 'Git branch to build from' + description: "Git branch to build from" required: true - default: 'main' + default: "main" dockerfile_path: - description: 'Select Dockerfile' + description: "Select Dockerfile" required: true type: choice options: - ./docker/LTS/ee/ee-production.Dockerfile - ./docker/pre-release/ee/ee-production.Dockerfile docker_tag: - description: 'Docker tag suffix (e.g., pre-release-14, 3.16-lts, etc.)' + description: "Docker tag suffix (e.g., pre-release-14, 3.16-lts, etc.)" required: true test_system: - description: 'Select test system' + description: "Select test system" required: true type: choice options: @@ -52,24 +52,27 @@ jobs: "${{ secrets.ALLOWED_USER10_TEST_SYSTEM }}" "${{ secrets.ALLOWED_USER11_TEST_SYSTEM }}" "${{ secrets.ALLOWED_USER12_TEST_SYSTEM }}" + "${{ secrets.ALLOWED_USER13_TEST_SYSTEM }}" + "${{ secrets.ALLOWED_USER14_TEST_SYSTEM }}" ) - + current_user="${{ github.actor }}" authorized=false - + for user in "${allowed_users[@]}"; do if [[ "$current_user" == "$user" ]]; then authorized=true break fi done - + if [[ "$authorized" == "false" ]]; then echo "โ User '$current_user' is not authorized to trigger this workflow." exit 1 else echo "โ User '$current_user' is authorized." fi + - name: Checkout code uses: actions/checkout@v4 with: @@ -94,6 +97,7 @@ jobs: else echo "tag=tooljet/tj-osv:$input_tag" >> $GITHUB_OUTPUT fi + - name: Build and Push Docker image uses: docker/build-push-action@v4 with: @@ -105,6 +109,7 @@ jobs: build-args: | CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} BRANCH_NAME=${{ github.event.inputs.branch_name }} + - name: Show the full Docker tag run: echo "โ Docker image built and pushed:${{ steps.taggen.outputs.tag }}" @@ -116,38 +121,39 @@ jobs: run: | test_system="${{ github.event.inputs.test_system }}" vm_host=$(echo '${{ secrets.VM_HOST_MAP_JSON }}' | jq -r --arg sys "$test_system" '.[$sys]') - + if [[ -z "$vm_host" || "$vm_host" == "null" ]]; then echo "VM mapping not found for $test_system" exit 1 fi - + echo "host=$vm_host" >> $GITHUB_OUTPUT + - name: Deploy to target environment run: | echo "$SSH_KEY" > key.pem chmod 600 key.pem - + IMAGE_TAG="${{ steps.taggen.outputs.tag }}" TARGET_SYSTEM="${{ github.event.inputs.test_system }}" - + # Debug: Show what we're deploying echo "DEBUG: IMAGE_TAG=$IMAGE_TAG" echo "DEBUG: TARGET_SYSTEM=$TARGET_SYSTEM" - + ssh -o StrictHostKeyChecking=no -i key.pem $SSH_USER@${{ steps.vmhost.outputs.host }} << EOF set -e - + IMAGE_TAG="$IMAGE_TAG" TARGET_SYSTEM="$TARGET_SYSTEM" - + cd ~ echo "๐ Finding correct deployment directory" - + # Debug: Show variables on remote host echo "Debug on remote: IMAGE_TAG=\$IMAGE_TAG" echo "Debug on remote: TARGET_SYSTEM=\$TARGET_SYSTEM" - + if [[ "\$TARGET_SYSTEM" == *-3.16-lts ]]; then echo "Detected LTS system: \$TARGET_SYSTEM" echo "๐ Searching for LTS directories..." @@ -178,39 +184,39 @@ jobs: cd ~ echo "โ Now in directory: \$(pwd)" fi - + echo "๐ Docker login" echo "${{ secrets.DOCKER_PASSWORD }}" | sudo docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin - + echo "current image" cat .env | grep TOOLJET_IMAGE - + echo "๐ฆ Reading current TOOLJET_IMAGE from .env" CURRENT_IMAGE=\$(grep '^TOOLJET_IMAGE=' .env | cut -d '=' -f2- | tr -d '"' | tr -d "'") echo "Found CURRENT_IMAGE: \$CURRENT_IMAGE" - + echo "๐ Stopping containers" sudo docker-compose down - + echo "๐ Updating .env with new image" sudo sed -i "s|^TOOLJET_IMAGE=.*|TOOLJET_IMAGE=\$IMAGE_TAG|" .env - + echo "๐ฅ Pulling new image: \$IMAGE_TAG" if [ -z "\$IMAGE_TAG" ]; then echo "โ IMAGE_TAG is empty!" exit 1 fi sudo docker pull "\$IMAGE_TAG" - + echo "๐ Starting container in background" sudo docker-compose up -d - + # Wait for ToolJet to start and show success message echo "โณ Waiting for ToolJet to start (timeout: 300 seconds)..." SUCCESS_FOUND=false TIMEOUT=300 ELAPSED=0 - + while [ \$ELAPSED -lt \$TIMEOUT ]; do # Check for success message in logs if sudo docker-compose logs 2>/dev/null | grep -qE "๐ TOOLJET APPLICATION STARTED SUCCESSFULLY|Ready to use at http://localhost:82 ๐|Ready to use at http://localhost:80"; then @@ -223,7 +229,7 @@ jobs: sleep 10 ELAPSED=\$((ELAPSED + 10)) done - + if [ "\$SUCCESS_FOUND" = false ]; then echo "โ Timeout reached without finding success logs" echo "๐ Showing current logs for troubleshooting..." @@ -244,20 +250,19 @@ jobs: echo "โ Rollback completed!" exit 1 fi - + echo "โ Deployment successful!" - + echo "๐ Storing successful deployment info in .env" sudo sed -i "/^OLD_IMAGE=/d" .env echo "OLD_IMAGE=\$CURRENT_IMAGE" | sudo tee -a .env echo "๐ Final application logs:" sudo docker-compose logs --tail=50 - + echo "๐งน Pruning old Docker images" sudo docker image prune -a -f - + EOF env: SSH_USER: ${{ secrets.AZURE_VM_USER }} SSH_KEY: ${{ secrets.AZURE_VM_KEY }} - diff --git a/.version b/.version index f4e635c035..8f1ee24bff 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.20.21-lts +3.20.30-lts diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 4c2e971648..c71d81d4b1 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -19,9 +19,9 @@ module.exports = defineConfig({ trashAssetsBeforeRuns: true, e2e: { - setupNodeEvents(on, config) { + setupNodeEvents (on, config) { on("task", { - readPdf(pathToPdf) { + readPdf (pathToPdf) { return new Promise((resolve) => { const pdfPath = path.resolve(pathToPdf); let dataBuffer = fs.readFileSync(pdfPath); @@ -33,7 +33,7 @@ module.exports = defineConfig({ }); on("task", { - readXlsx(filePath) { + readXlsx (filePath) { return new Promise((resolve, reject) => { try { let dataBuffer = fs.readFileSync(filePath); @@ -48,7 +48,7 @@ module.exports = defineConfig({ }); on("task", { - deleteFile(filePath) { + deleteFile (filePath) { return new Promise((resolve, reject) => { const fullPath = path.resolve(filePath); if (fs.existsSync(fullPath)) { @@ -69,7 +69,7 @@ module.exports = defineConfig({ }); on("task", { - deleteFolder(folderName) { + deleteFolder (folderName) { return new Promise((resolve, reject) => { if (fs.existsSync(folderName)) { rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { @@ -87,7 +87,7 @@ module.exports = defineConfig({ }); on("task", { - dbConnection({ dbconfig, sql }) { + dbConnection ({ dbconfig, sql }) { const client = new pg.Pool(dbconfig); return client.query(sql); }, @@ -110,18 +110,20 @@ module.exports = defineConfig({ return config; }, experimentalRunAllSpecs: true, - experimentalModfyObstructiveThirdPartyCode: true, baseUrl: "http://localhost:8082", specPattern: "cypress/e2e/happyPath/**/*.cy.js", downloadsFolder: "cypress/downloads", numTestsKeptInMemory: 0, - redirectionLimit: 5, + redirectionLimit: 3, trashAssetsBeforeRuns: true, experimentalMemoryManagement: true, - coverage: true, - codeCoverageTasksRegistered: true, + coverage: false, + codeCoverageTasksRegistered: false, video: false, videoUploadOnPasses: false, - experimentalStudio: true, + // experimentalStudio: true, + // experimentalPromptCommand: true, + + // projectId: "ca6324a0-4210-4f7e-846a-71ca2766ca4", }, }); diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 873e6e1d13..9f8303d560 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -1,35 +1,5 @@ const envVar = Cypress.env("environment"); -Cypress.Commands.add( - "apiLogin", - ( - userEmail = "dev@tooljet.io", - userPassword = "password", - workspaceId = "", - redirection = "/" - ) => { - cy.request({ - url: `${Cypress.env("server_host")}/api/authenticate/${workspaceId}`, - method: "POST", - body: { - email: userEmail, - password: userPassword, - redirectTo: redirection, - }, - }) - .its("body") - .then((res) => { - Cypress.env("workspaceId", res.current_organization_id); - - Cypress.log({ - name: "Api login", - displayName: "LOGIN: ", - message: `: Success`, - }); - }); - } -); - Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => { cy.getCookie("tj_auth_token").then((cookie) => { cy.request( @@ -64,7 +34,7 @@ Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => { }); }); -Cypress.Commands.add("apiFetchDataSourcesId", () => { +Cypress.Commands.add("apiFetchDataSourcesIdFromApp", () => { cy.getAuthHeaders().then((headers) => { cy.request({ method: "GET", @@ -183,23 +153,6 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("apiLogout", () => { - cy.getCookie("tj_auth_token").then((cookie) => { - cy.request( - { - method: "GET", - url: `${Cypress.env("server_host")}/api/session/logout`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: `tj_auth_token=${cookie.value}`, - }, - }, - { log: false } - ).then((response) => { - expect(response.status).to.equal(200); - }); - }); -}); Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => { cy.getCookie("tj_auth_token").then((cookie) => { @@ -233,7 +186,7 @@ Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => { Cypress.Commands.add( "apiAddQueryToApp", - ({ queryName, options, dsName, dsKind }) => { + ({ queryName, options, dataSourceName, dsKind }) => { cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { const authToken = cookie?.value; const workspaceId = Cypress.env("workspaceId"); @@ -259,10 +212,10 @@ Cypress.Commands.add( headers: commonHeaders, }).then((dsResponse) => { const dataSource = dsResponse.body.data_sources.find( - (ds) => ds.name === dsName + (ds) => ds.name === dataSourceName ); const dataSourceID = dataSource.id; - Cypress.env(`${dsName}`, dataSourceID); + Cypress.env(`${dataSourceName}`, dataSourceID); cy.request({ method: "POST", @@ -378,19 +331,6 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("apiGetEnvironments", () => { - cy.getAuthHeaders().then((headers) => { - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/app-environments`, - headers: headers, - }).then((response) => { - expect(response.status).to.equal(200); - return response.body.environments; - }); - }); -}); - Cypress.Commands.add("apiMakeAppPublic", (appId = Cypress.env("appId")) => { cy.getAuthHeaders().then((headers) => { cy.request({ @@ -495,30 +435,17 @@ Cypress.Commands.add("apiGetDataSourceIdByName", (dataSourceName) => { headers: headers, }).then((response) => { expect(response.status).to.equal(200); - const dataSource = response.body.data_sources.find( - (ds) => ds.name === dataSourceName - ); - return dataSource.id; + const id = response.body.data_sources.find(ds => ds.name === dataSourceName)?.id; + Cypress.log({ + name: "apiGetDataSourceIdByName", + displayName: "Data Source ID", + message: `Data source ID for '${dataSourceName}': ${id}`, + }); + return id; }); }); }); -Cypress.Commands.add("getAuthHeaders", (returnCached = false) => { - let headers = {}; - if (returnCached) { - return returnCached; - } else { - cy.getCookie("tj_auth_token").then((cookie) => { - headers = { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: `tj_auth_token=${cookie.value}`, - }; - Cypress.env("authHeaders", headers); - return headers; - }); - } -}); - Cypress.Commands.add( "apiUpdateDataSource", (dataSourceName, envName, updateData) => { diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index ceeb223f50..3548ba668c 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -17,7 +17,7 @@ const API_ENDPOINT = Cypress.Commands.add( "appUILogin", - (email = "dev@tooljet.io", password = "password") => { + (email = "dev@tooljet.io", password = "password", status = 'success', toast = '') => { cy.clearAndType(onboardingSelectors.loginEmailInput, email); cy.clearAndType(onboardingSelectors.loginPasswordInput, password); cy.get(onboardingSelectors.signInButton).click(); @@ -27,7 +27,7 @@ Cypress.Commands.add( ); Cypress.Commands.add("clearAndType", (selector, text) => { - cy.get(selector).type(`{selectall}{backspace}${text}`); + cy.get(selector).should("be.visible", { timeout: 10000 }).click({ force: true }).type(`{selectall}{backspace}${text}`); }); Cypress.Commands.add("forceClickOnCanvas", () => { @@ -53,9 +53,10 @@ Cypress.Commands.add("waitForAutoSave", () => { cy.wait(200); cy.get(commonSelectors.autoSave, { timeout: 20000 }).should( "have.text", - commonText.autoSave, + '', { timeout: 20000 } - ); + ).find('svg') + .should('be.visible', { timeout: 20000 }); }); Cypress.Commands.add("createApp", (appName) => { @@ -65,7 +66,7 @@ Cypress.Commands.add("createApp", (appName) => { : commonSelectors.appCreateButton; cy.get("body").then(($title) => { - cy.get(getAppButtonSelector($title)).click(); + cy.get(getAppButtonSelector($title)).scrollIntoView().click({ force: true });//workaround for cypress dashboard click issue cy.clearAndType('[data-cy="app-name-input"]', appName); cy.get('[data-cy="create-app"]').click(); }); @@ -77,37 +78,47 @@ Cypress.Commands.add( "dragAndDropWidget", ( widgetName, - positionX = 80, - positionY = 80, + positionX = 100, + positionY = 100, widgetName2 = widgetName, canvas = commonSelectors.canvas ) => { const dataTransfer = new DataTransfer(); - cy.forceClickOnCanvas(); - cy.get("body") - .then(($body) => { - const isSearchVisible = $body - .find(commonSelectors.searchField) - .is(":visible"); + cy.get('[data-cy="right-sidebar-plus-button"]').click(); + cy.get(commonSelectors.searchField).should('be.visible'); - if (!isSearchVisible) { - cy.get('[data-cy="right-sidebar-plus-button"]').click(); - } + cy.get(commonSelectors.searchField).first().clear().type(widgetName); + cy.get(commonWidgetSelector.widgetBox(widgetName2)).should('be.visible'); + + cy.get(commonWidgetSelector.widgetBox(widgetName2)) + .trigger('mousedown', { which: 1, button: 0, force: true }) + .trigger('dragstart', { dataTransfer, force: true }); + + cy.get(canvas) + .trigger('dragenter', { + dataTransfer, + clientX: positionX, + clientY: positionY, + force: true }) - .then(() => { - cy.clearAndType(commonSelectors.searchField, widgetName); + .trigger('dragover', { + dataTransfer, + clientX: positionX, + clientY: positionY, + force: true }); - cy.get(commonWidgetSelector.widgetBox(widgetName2)).trigger( - "dragstart", - { dataTransfer }, - { force: true } - ); - cy.get(canvas).trigger("drop", positionX, positionY, { - dataTransfer, - force: true, - }); + + cy.get(canvas) + .trigger('drop', { + dataTransfer, + clientX: positionX, + clientY: positionY, + force: true + }) + .trigger('mouseup', { force: true }); + cy.waitForAutoSave(); } ); @@ -224,8 +235,11 @@ Cypress.Commands.add("renameApp", (appName) => { `{selectAll}{backspace}${appName}`, { force: true } ); - cy.forceClickOnCanvas(); - cy.waitForAutoSave(); + cy.get(commonSelectors.renameAppButton).should("be.enabled").click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + commonText.appRenamedToast + ); }); Cypress.Commands.add( @@ -410,7 +424,7 @@ Cypress.Commands.add("getPosition", (componentName) => { ); }); -Cypress.Commands.add("defaultWorkspaceLogin", () => { +Cypress.Commands.add("defaultWorkspaceLogin", (workspaceSlug = 'my-workspace',) => { // cy.task("dbConnection", { // dbconfig: Cypress.env("app_db"), // sql: ` @@ -421,10 +435,9 @@ Cypress.Commands.add("defaultWorkspaceLogin", () => { cy.apiLogin( "dev@tooljet.io", "password", - // workspaceId, // "/my-workspace" ).then(() => { - cy.visit("/"); + cy.visit(`/${workspaceSlug}`); cy.wait(2000); cy.get(commonSelectors.homePageLogo, { timeout: 10000 }); }); @@ -557,7 +570,7 @@ Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { } }); - function installPlugin (pluginName) { + function installPlugin(pluginName) { cy.get('[data-cy="-list-item"]').eq(1).click(); cy.wait(1000); @@ -659,4 +672,27 @@ Cypress.Commands.add("runSqlQuery", (query, db = Cypress.env("app_db")) => { dbconfig: db, sql: query, }); -}); \ No newline at end of file +}); + +Cypress.Commands.add( + "openWorkflow", + ( + slug = "", + workspaceId = Cypress.env("workspaceId"), + workflowId = Cypress.env("workflowId"), + ) => { + cy.intercept("GET", "/api/apps/*").as("getWorkflowData"); + cy.window({ log: false }).then((win) => { + win.localStorage.setItem("walkthroughCompleted", "true"); + }); + cy.visit(`/${workspaceId}/apps/${workflowId}/${slug}`); + + cy.wait("@getWorkflowData").then((interception) => { + const responseData = interception.response.body; + + Cypress.env("editingVersionId", responseData.editing_version.id); + Cypress.env("environmentId", responseData.editorEnvironment.id); + Cypress.env("workflowId", responseData.id); + }); + } +); diff --git a/cypress-tests/cypress/commands/platform/platformApiCommands.js b/cypress-tests/cypress/commands/platform/platformApiCommands.js index f17bc6ade0..24ab49a0b4 100644 --- a/cypress-tests/cypress/commands/platform/platformApiCommands.js +++ b/cypress-tests/cypress/commands/platform/platformApiCommands.js @@ -1,5 +1,66 @@ const envVar = Cypress.env("environment"); +Cypress.Commands.add( + "apiLogin", + ( + userEmail = "dev@tooljet.io", + userPassword = "password", + workspaceId = "", + redirection = "/" + ) => { + cy.request({ + url: `${Cypress.env("server_host")}/api/authenticate/${workspaceId}`, + method: "POST", + body: { + email: userEmail, + password: userPassword, + redirectTo: redirection, + }, + }) + .its("body") + .then((res) => { + Cypress.env("workspaceId", res.current_organization_id); + + Cypress.log({ + name: "Api login", + displayName: "LOGIN: ", + message: `: Success`, + }); + }); + } +); + +Cypress.Commands.add("apiLogout", () => { + cy.getCookie("tj_auth_token").then((cookie) => { + cy.request( + { + method: "GET", + url: `${Cypress.env("server_host")}/api/session/logout`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }, + }, + { log: false } + ).then((response) => { + expect(response.status).to.equal(200); + }); + }); +}); + +Cypress.Commands.add("apiGetEnvironments", () => { + cy.getAuthHeaders().then((headers) => { + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/app-environments`, + headers: headers, + }).then((response) => { + expect(response.status).to.equal(200); + return response.body.environments; + }); + }); +}); + Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => { cy.getCookie("tj_auth_token").then((cookie) => { cy.request( @@ -100,12 +161,61 @@ Cypress.Commands.add("apiUpdateWsConstant", (id, updateValue, envName) => { url: `${Cypress.env("server_host")}/api/organization-constants/${id}`, headers: headers, body: { - value: String(updateValue), // ensure it's a string + value: String(updateValue), environment_id: envId, }, }).then((response) => { expect(response.status).to.equal(200); - response.body; // returns the PATCH response body + response.body; + }); + }); + }); +}); + +Cypress.Commands.add("apiGetGroupId", (groupName) => { + return cy.getAuthHeaders().then((headers) => { + return cy + .request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/v2/group-permissions`, + headers: headers, + log: false, + }) + .then((response) => { + expect(response.status).to.equal(200); + const group = response.body.groupPermissions.find( + (g) => g.name === groupName + ); + if (!group) throw new Error(`Group with name ${groupName} not found`); + return group.id; + }); + }); +}); +Cypress.Commands.add("apiUpdateUserRole", (email, role) => { + return cy.task("dbConnection", { + dbconfig: Cypress.env("app_db"), + sql: ` + SELECT id + FROM users + WHERE email='${email}' + LIMIT 1; + `, + }).then((resp) => { + const userId = resp.rows[0]?.id; + if (!userId) throw new Error(`User with email ${email} not found`); + return userId; + }).then((userId) => { + return cy.getAuthHeaders().then((headers) => { + return cy.request({ + method: "PUT", + url: `${Cypress.env("server_host")}/api/v2/group-permissions/role/user`, + headers: headers, + body: { + newRole: role, + userId: userId, + }, + }).then((response) => { + expect(response.status).to.equal(200); }); }); }); @@ -116,47 +226,71 @@ Cypress.Commands.add( ( groupName, name, - canEdit = false, - canView = true, - hideFromDashboard = false, + resourceType = "app", + permissions = {}, resourcesToAdd = [] ) => { cy.getAuthHeaders().then((headers) => { - // Fetch group permissions - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/v2/group-permissions`, - headers: headers, - log: false, - }).then((response) => { - expect(response.status).to.equal(200); - const group = response.body.groupPermissions.find( - (g) => g.name === groupName - ); - if (!group) throw new Error(`Group with name ${groupName} not found`); + cy.apiGetGroupId(groupName).then((groupId) => { + const environment = Cypress.env("environment"); + const isEnterprise = environment === "Enterprise"; - const groupId = group.id; + const resourceTypeMap = { + app: { type: "app", endpoint: "app" }, + workflow: { type: "workflow", endpoint: "data-source" }, + datasource: { type: "data_source", endpoint: "data-source" }, + }; - // Create granular permission - cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions`, - headers: headers, - body: { + const { type, endpoint } = + resourceTypeMap[resourceType] || resourceTypeMap.app; + + const url = isEnterprise + ? `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions/${endpoint}` + : `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions`; + let permissionObject; + + if (resourceType === "datasource") { + permissionObject = { + action: { + canUse: permissions.canUse ?? true, + canConfigure: permissions.canConfigure ?? false, + }, + resourcesToAdd, + }; + } else { + permissionObject = { + canEdit: permissions.canEdit ?? false, + canView: permissions.canView ?? true, + hideFromDashboard: permissions.hideFromDashboard ?? false, + resourcesToAdd, + }; + } + + const body = isEnterprise + ? { + name, + type, + groupId, + isAll: true, + createResourcePermissionObject: permissionObject, + } + : { name, type: "app", groupId, isAll: true, - createAppsPermissionsObject: { - canEdit, - canView, - hideFromDashboard, - resourcesToAdd, - }, - }, + createAppsPermissionsObject: permissionObject, + }; + + cy.request({ + method: "POST", + url: url, + headers: headers, + body: body, log: false, }).then((res) => { expect(res.status).to.equal(201); + cy.log(`Created ${resourceType} granular permission: ${name}`); }); }); }); @@ -167,22 +301,7 @@ Cypress.Commands.add( "apiDeleteGranularPermission", (groupName, typesToDelete = []) => { cy.getAuthHeaders().then((headers) => { - // Step 1: Get the group by name - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/v2/group-permissions`, - headers, - log: false, - }).then((response) => { - expect(response.status).to.equal(200); - const group = response.body.groupPermissions.find( - (g) => g.name === groupName - ); - if (!group) throw new Error(`Group with name ${groupName} not found`); - - const groupId = group.id; - - // Step 2: Get all granular permissions for the group + cy.apiGetGroupId(groupName).then((groupId) => { cy.request({ method: "GET", url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions`, @@ -192,23 +311,30 @@ Cypress.Commands.add( expect(granularResponse.status).to.equal(200); const granularPermissions = granularResponse.body; - // Step 3: Filter if typesToDelete is specified const permissionsToDelete = typesToDelete.length ? granularPermissions.filter((perm) => typesToDelete.includes(perm.type) ) : granularPermissions; - // Step 4: Delete each granular permission permissionsToDelete.forEach((permission) => { + const typeEndpointMap = { + app: "app", + workflow: "app", + data_source: "data-source", + }; + const endpoint = typeEndpointMap[permission.type] || "app"; + cy.request({ method: "DELETE", - url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions/app/${permission.id}`, + url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions/${endpoint}/${permission.id}`, headers, log: false, }).then((deleteResponse) => { expect(deleteResponse.status).to.equal(200); - cy.log(`Deleted granular permission: ${permission.name}`); + cy.log( + `Deleted ${permission.type} granular permission: ${permission.name}` + ); }); }); }); @@ -310,14 +436,13 @@ Cypress.Commands.add( redirectTo = "/", }) => { cy.intercept("POST", "/api/oauth/sign-in/*", (req) => { - // Inject missing params if not present if (!req.body.organizationId) { req.body.organizationId = organizationId; } if (!req.body.redirectTo) { req.body.redirectTo = redirectTo; } - req.continue(); // Send the modified request + req.continue(); }).as("oidcSignIn"); cy.getSsoConfigId("openid").then((ssoConfigId) => { const configIdToUse = ssoConfigId; @@ -375,7 +500,6 @@ Cypress.Commands.add( const redirectUrl = authResp.headers["location"]; const params = new URL(redirectUrl).searchParams; const code = params.get("code"); - // 6. Exchange code for tokens cy.request({ method: "POST", url: `https://${oktaDomain}/oauth2/v1/token`, @@ -567,3 +691,45 @@ Cypress.Commands.add( }); } ); + +Cypress.Commands.add( + "apiUpdateGroupPermission", + (groupName, permissionPayload) => { + return cy.apiGetGroupId(groupName).then((groupId) => { + return cy.getAuthHeaders().then((headers) => { + return cy + .request({ + method: "PUT", + url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}`, + headers: headers, + body: permissionPayload, + log: false, + }) + .then((response) => { + expect(response.status).to.equal(200); + return response.body; + }); + }); + }); + } +); + +Cypress.Commands.add("getAuthHeaders", (returnCached = false) => { + let headers = {}; + if (returnCached) { + return returnCached; + } else { + cy.getCookie("tj_auth_token").then((cookie) => { + headers = { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }; + Cypress.env("authHeaders", headers); + Cypress.log({ + name: "getAuthHeaders", + message: `Auth headers: ${JSON.stringify(headers)}`, + }); + return headers; + }); + } +}); diff --git a/cypress-tests/cypress/commands/workflowCommands.js b/cypress-tests/cypress/commands/workflowCommands.js index 592bba9365..9288b924f4 100644 --- a/cypress-tests/cypress/commands/workflowCommands.js +++ b/cypress-tests/cypress/commands/workflowCommands.js @@ -6,10 +6,10 @@ import { commonText } from "Texts/common"; import { selectAppCardOption } from "Support/utils/common"; import { navigateBackToWorkflowsDashboard } from "Support/utils/workFlows"; -Cypress.Commands.add("createWorkflowApp", (wfName) => { +Cypress.Commands.add("createWorkflowApp", (workflowName) => { cy.get(workflowSelector.globalWorkFlowsIcon).click(); cy.get(workflowSelector.workflowsCreateButton).click(); - cy.get(workflowSelector.workFlowNameInputField).type(wfName); + cy.get(workflowSelector.workFlowNameInputField).type(workflowName); cy.get(workflowSelector.createWorkFlowsButton).click(); }); @@ -115,10 +115,10 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("deleteWorkflow", (wfName) => { +Cypress.Commands.add("deleteWorkflow", (workflowName) => { cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); navigateBackToWorkflowsDashboard(); - cy.get(commonSelectors.appCard(wfName)) + cy.get(commonSelectors.appCard(workflowName)) .realHover() .find(commonSelectors.appCardOptionsButton) .realHover() @@ -128,9 +128,9 @@ Cypress.Commands.add("deleteWorkflow", (wfName) => { cy.wait("@appDeleted"); }); -Cypress.Commands.add("deleteWorkflowfromDashboard", (wfName) => { +Cypress.Commands.add("deleteWorkflowfromDashboard", (workflowName) => { cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); - cy.get(commonSelectors.appCard(wfName)) + cy.get(commonSelectors.appCard(workflowName)) .realHover() .find(commonSelectors.appCardOptionsButton) .realHover() @@ -142,13 +142,17 @@ Cypress.Commands.add("deleteWorkflowfromDashboard", (wfName) => { Cypress.Commands.add( "exportWorkflowApp", - (wfName, fixtureFile = "cypress/fixtures/exportedApp.json") => { + (workflowName, fixtureFile = "cypress/fixtures/exportedApp.json") => { navigateBackToWorkflowsDashboard(); - selectAppCardOption( - wfName, - commonSelectors.appCardOptions(workflowsText.exportWFOption) - ); + cy.get(`[data-cy="${workflowName}-card"]`) + .trigger('mouseover') + .find('[data-cy="app-card-menu-icon"]') + .click({ force: true }); + + cy.get(commonSelectors.appCardOptions(workflowsText.exportWFOption)) + .click(); + cy.wait(2000); cy.exec("ls -t ./cypress/downloads/ | head -1").then((result) => { @@ -158,20 +162,21 @@ Cypress.Commands.add( cy.writeFile(fixtureFile, json); }); }); - cy.deleteWorkflowfromDashboard(wfName); + + cy.deleteWorkflowfromDashboard(workflowName); } ); -Cypress.Commands.add("addWorkflowInApp", (wfName) => { +Cypress.Commands.add("addWorkflowInApp", (workflowName) => { cy.get(workflowSelector.showDSPopoverButton).click(); cy.get(workflowSelector.workflowSearchInput).type( workflowsText.workflowLabel ); cy.contains(`[id*="react-select-"]`, workflowsText.workflowLabel).click(); - cy.get(workflowSelector.queryRenameInput).clear().type(wfName); + cy.get(workflowSelector.queryRenameInput).clear().type(workflowName); cy.get(workflowSelector.workflowDropdown).parent() .find('.react-select__control') .click(); - cy.get(workflowSelector.workflowSelectInput).realType(wfName); - cy.get(workflowSelector.workflowSelectOption).contains(wfName).click(); + cy.get(workflowSelector.workflowSelectInput).realType(workflowName); + cy.get(workflowSelector.workflowSelectOption).contains(workflowName).click(); }); diff --git a/cypress-tests/cypress/commands/workflowsApiCommands.js b/cypress-tests/cypress/commands/workflowsApiCommands.js new file mode 100644 index 0000000000..5ad11ef171 --- /dev/null +++ b/cypress-tests/cypress/commands/workflowsApiCommands.js @@ -0,0 +1,102 @@ +const envVar = Cypress.env("environment"); + + Cypress.Commands.add("apiCreateWorkflow", (workflowName, reuseSession = false) => { + cy.getAuthHeaders(reuseSession).then((headers) => { + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/apps`, + headers: headers, + body: { + icon: "sentfast", + name: workflowName, + type: "workflow", + }, + }).then((response) => { + expect(response.status).to.equal(201); + + const workflowId = response.body?.id || response.allRequestResponses?.[0]?.["Response Body"]?.id; + const userId = response.body?.user_id || response.allRequestResponses?.[0]?.["Response Body"]?.user_id; + + Cypress.env("workflowId", workflowId); + Cypress.env("user_id", userId); + + Cypress.log({ + name: "Workflow create", + displayName: "WORKFLOW CREATED", + message: `: ${response.body.name}`, + }); + }); + }); + }); + + + Cypress.Commands.add( + "openWorkflowByName", + ( + workflowName, + workspaceId = Cypress.env("workspaceId"), + componentSelector = "[data-cy='workflow-canvas']", + reuseSession = false + ) => { + cy.getAuthHeaders(reuseSession).then((headers) => { + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: headers, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + Cypress.env("workflowId", workflow.id); + cy.openWorkflow(workflow.slug, workspaceId, workflow.id, componentSelector); + + Cypress.log({ + name: "Workflow Open", + displayName: "WORKFLOW OPENED", + message: `: ${workflowName}`, + }); + } else { + throw new Error(`Workflow "${workflowName}" not found`); + } + }); + }); + } + ); + + Cypress.Commands.add("apiDeleteWorkflow", (workflowName, reuseSession = false) => { + cy.getAuthHeaders(reuseSession).then((headers) => { + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: headers, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + cy.request({ + method: "DELETE", + url: `${Cypress.env("server_host")}/api/apps/${workflow.id}`, + headers: headers, + }, { log: false }).then((deleteResponse) => { + expect(deleteResponse.status).to.equal(200); + Cypress.log({ + name: "Workflow Delete", + displayName: "WORKFLOW DELETED", + message: `: ${workflowName}`, + }); + }); + } else { + Cypress.log({ + name: "Workflow Not Found", + displayName: "WORKFLOW NOT FOUND", + message: `: ${workflowName}`, + }); + } + }); + }); + }); + diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index 5bffc17390..e4d2481229 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -89,6 +89,7 @@ export const commonSelectors = { invitedUserEmail: '[data-cy="email-input-value"]', invitedUseremail: '[data-cy="email-input-input-value"]', acceptInviteButton: '[data-cy="accept-invite-button"]', + homePageIcon: '[data-cy="icon-home"]', databaseIcon: '[data-cy="icon-database"]', profileSettings: '[data-cy="profile-settings"]', workspaceSettings: '[data-cy="workspace-settings"]', @@ -176,7 +177,9 @@ export const commonSelectors = { resetPasswordButton: '[data-cy="reset-password-button"]', resetPasswordPageDescription: '[data-cy="reset-password-page-description"]', backToLoginButton: '[data-cy="back-to-login"]', - breadcrumbTitle: '[data-cy="app-header-label"]>>', + // breadcrumbTitle: '[data-cy="app-header-label"]>>', + breadcrumbHeaderTitle: '[data-cy="breadcrumb-header-applications"]>>', + breadcrumbTitle: '[data-cy="breadcrumb-header-workspace-settings"]>>', // breadcrumbPageTitle: '[data-cy="app-header-label"]', breadcrumbPageTitle: '[data-cy="breadcrumb-page-title"]', labelFullNameInput: '[data-cy="name-label"]', @@ -190,6 +193,7 @@ export const commonSelectors = { addNewDataSourceButton: '[data-cy="add-new-data-source-button"]', saveButton: '[data-cy="save-button"]', appEditButton: '[data-cy="edit-button"]', + editorAppNameInput: '[data-cy="editor-app-name-input"]', onboardingRadioButton: (radioButtonText) => { return `[data-cy="${cyParamName(radioButtonText)}-radio-button"]`; }, @@ -268,6 +272,7 @@ export const commonSelectors = { chooseFromTemplateButton: '[data-cy="choose-from-template-button"]', CreateAppFromTemplateButton: '[data-cy="create-new-app-from-template-title"]', settingsIcon: '[data-cy="settings-icon"]', + previewSettings: '[data-cy="preview-settings"]', marketplaceOption: '[data-cy="marketplace-option"]', backToAppOption: '[data-cy="back-to-app-option"]', databaseOption: '[data-cy="database-option"]', @@ -281,6 +286,8 @@ export const commonSelectors = { defaultModalTitle: '[data-cy="modal-title"]', workspaceConstantsIcon: '[data-cy="icon-workspace-constants"]', confirmationButton: '[data-cy="confirmation-button"]', + modalConfirmButton: '[data-cy="modal-confirm-button"]', + rightSidebarPlusButton: '[data-cy="right-sidebar-plus-button"]', textField: (fieldName) => { return `[data-cy="${cyParamName(fieldName)}-text-field"]`; diff --git a/cypress-tests/cypress/constants/selectors/dashboard.js b/cypress-tests/cypress/constants/selectors/dashboard.js index 50c0c87a0f..a7e8f1a817 100644 --- a/cypress-tests/cypress/constants/selectors/dashboard.js +++ b/cypress-tests/cypress/constants/selectors/dashboard.js @@ -55,4 +55,24 @@ export const dashboardSelector = { slugSuccessLabel: '[data-cy="slug-sucess-label"]', slugErrorLabel: '[data-cy="slug-error-label"]', editWorkspaceTitle: '[data-cy="edit-workspace-title"]', + + homePagePromptHeader: '[data-cy="home-page-prompt-header"]', + promptInput: '[data-cy="prompt-input"]', + homePageDividerText: '[data-cy="divider-text"]', + appCardWidget: '[data-cy="getstarted-app-widget"]', + templateCardWidget: '[data-cy="getstarted-templates-widget"]', + databaseCardWidget: '[data-cy="getstarted-datasource-widget"]', + workflowCardWidget: '[data-cy="getstarted-workflow-widget"]', + + widgetCardName: (cardType) => { + return `[data-cy="getstarted-${cardType}-widget"]`; + }, + widgetCardTitle: '[data-cy="widget-card-title"]', + widgetCardDescription: '[data-cy="widget-card-description"]', + homePagePromptTextArea: '[data-cy="prompt-textarea"]', + promptEnterButton: '[data-cy="prompt-enter-button"]', + aiIcon: '[data-cy="ai-icon"]', + homePageIcon: (iconName) => { + return `[data-cy="${iconName}s-icon"]`; + } }; diff --git a/cypress-tests/cypress/constants/selectors/exportImport.js b/cypress-tests/cypress/constants/selectors/exportImport.js index 5df2652253..869f75cefe 100644 --- a/cypress-tests/cypress/constants/selectors/exportImport.js +++ b/cypress-tests/cypress/constants/selectors/exportImport.js @@ -14,7 +14,7 @@ export const appVersionSelectors = { versionNameInputField: '[data-cy="version-name-input-field"]', createVersionFromLabel: '[data-cy="create-version-from-label"]', createVersionInputField: '[data-cy="create-version-from-input-field"]', - createNewVersionButton: '[data-cy="create-new-version-button"]', + createNewVersionButton: '[data-cy="create-new-version-button"]:last', appVersionContentList: ".react-select__menu-list", }; export const exportAppModalSelectors = { diff --git a/cypress-tests/cypress/constants/selectors/license.js b/cypress-tests/cypress/constants/selectors/license.js new file mode 100644 index 0000000000..861102a818 --- /dev/null +++ b/cypress-tests/cypress/constants/selectors/license.js @@ -0,0 +1,82 @@ +export const cyParamName = (paramName = "") => { + return String(paramName).toLowerCase().replace(/\s+/g, "-"); +}; + +export const cyParamNameCamelCase = (paramName = "") => { + return paramName + .replace(/[^a-zA-Z0-9\s]/g, "") + .toLowerCase() + .replace(/\s+(\w)/g, (_, c) => c.toUpperCase()); +}; + +export const licenseSelectors = { + comparePlansText: '[data-cy="compare-plans-button"]', + listOfItems: (itemName) => { + return `[data-cy="${cyParamName(itemName)}-list-item"]`; + }, + tabTitle: (tabName) => { + return `[data-cy="${cyParamNameCamelCase(tabName)}-tab-title"]`; + }, + subTab: (subTabName) => { + return `[data-cy="${cyParamName(subTabName)}-sub-tab"]`; + }, + numberOfTextLabel: (label) => { + return `[data-cy="number-of-${cyParamName(label)}-label"]`; + }, + inputField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-field"]`; + }, + label: (labelName) => { + return `[data-cy="${cyParamName(labelName)}-label"]`; + }, + + limitInfo: (type) => { + return `[data-cy="${cyParamName(type)}-limit-info"]`; + }, + limitHeading: (type) => { + return `[data-cy="${cyParamName(type)}-limit-heading"]`; + }, + + circularToggleDisabledIcon: '[data-cy="circular-toggle-disabled-icon"]', + licenseBannerHeading: '[data-cy="license-banner-heading"]', + licenseBannerInfo: '[data-cy="license-banner-info"]', + paidFeatureButton: '[data-cy="paid-feature-button"]', + warningIcon: '[data-cy="warning-icon"]', + noDomainLinkedLabel: '[data-cy="no-domain-header"]', + noDomainInfoText: '[data-cy="no-domain-info-text"]', + licenseOption: '[data-cy="license-list-item"]', + licenseKeyOption: '[data-cy="license-key-list-item"]', + limitOption: '[data-cy="limits-list-item"]', + accessOption: '[data-cy="access-list-item"]', + domainOption: '[data-cy="domain-list-item"]', + licenseKeyTitle: '[data-cy="licenseKey-tab-title"]', + licenseLabel: '[data-cy="license-label"]', + updateButton: '[data-cy="update-button"]', + limitsTabTitle: '[data-cy="limits-tab-title"]', + appsTab: '[data-cy="apps-sub-tab"]', + noOfAppsLabel: '[data-cy="number-of-apps-label"]', + noOfAppsfield: '[data-cy="apps-field"]', + workspaceTab: '[data-cy="workspaces-sub-tab"]', + noOfworkspaceLabel: '[data-cy="number-of-workspaces-label"]', + noOfWorkspacefield: '[data-cy="workspaces-field"]', + usersTab: '[data-cy="users-sub-tab"]', + noOfTotalUsersLabel: '[data-cy="number-of-total-users-label"]', + noOfTotalUsersfield: '[data-cy="total-users-field"]', + noOfBuildersLabel: '[data-cy="number-of-builders-label"]', + noOfBuildersfield: '[data-cy="builders-field"]', + noOfEndUsersLabel: '[data-cy="number-of-end-users-label"]', + noOfEndUsersfield: '[data-cy="end-users-field"]', + noOfSuperAdminLabel: '[data-cy="number-of-super-admins-label"]', + noOfSuperAdminfield: '[data-cy="super-admins-field"]', + tablesTab: '[data-cy="tables-sub-tab"]', + noOfTablesLabel: '[data-cy="number-of-tables-label"]', + noOfTablesfield: '[data-cy="tables-field"]', + expiryStatus: '[data-cy="license-expiry-status"]', + licenseTextArea: '[data-cy="license-text-area"]', + paidFeatureButton: '[data-cy="paid-feature-button"]', + accessTabTitle: '[data-cy="access-tab-title"]', + enterpriseGradientIcon: '[data-cy="enterprise-gradient-icon"]', + warningInfoText: '[data-cy="warning-info-text"]', + lockGradientIcon: '[data-cy="lock-gradient"]', + dsGradientIcon: '[data-cy="datasource-gradient"]', +}; diff --git a/cypress-tests/cypress/constants/selectors/manageGroups.js b/cypress-tests/cypress/constants/selectors/manageGroups.js index 9ed4273ba5..84a17b5742 100644 --- a/cypress-tests/cypress/constants/selectors/manageGroups.js +++ b/cypress-tests/cypress/constants/selectors/manageGroups.js @@ -4,6 +4,7 @@ export const cyParamName = (paramName = "") => { export const groupsSelector = { pageTitle: "[data-cy=user-groups-title]", createNewGroupButton: "[data-cy=create-new-group-button]", + createGroupButton: "[data-cy=create-group-button]", tableHeader: "[data-cy=table-header]", groupName: "[data-cy=group-name]", addNewGroupModalTitle: '[data-cy="add-new-group-title"]', @@ -41,7 +42,7 @@ export const groupsSelector = { allAppsRadio: '[data-cy="all-apps-radio"]', allAppsLabel: '[data-cy="all-apps-label"]', allAppsHelperText: '[data-cy="this-will-select-all-apps-in-the-workspace-including-any-new-apps-created-info-text"]', - customradio: '[data-cy="custom-radio"]', + customRadio: '[data-cy="custom-radio"]', customLabel: '[data-cy="custom-label"]', customHelperText: '[data-cy="custom-info-text"]', resourcesFolders: "[data-cy=resource-folders]", @@ -50,10 +51,29 @@ export const groupsSelector = { appsCreateLabel: "[data-cy=app-create-label]", appsDeleteCheck: "[data-cy=app-delete-checkbox]", appsDeleteLabel: "[data-cy=app-delete-label]", + appPromoteCheck: '[data-cy="app-promote-checkbox"]', + appPromoteLabel: '[data-cy="app-promote-label"]', + appPromoteHelperText: '[data-cy="app-promote-helper-text"]', + appReleaseCheck: '[data-cy="app-release-checkbox"]', + appReleaseLabel: '[data-cy="app-release-label"]', + appReleaseHelperText: '[data-cy="app-release-helper-text"]', + workflowsCreateCheck: "[data-cy=workflow-create-checkbox]", + workflowsCreateLabel: "[data-cy=workflow-create-label]", + workflowsCreateHelperText: '[data-cy="workflow-create-helper-text"]', + workflowsDeleteCheck: '[data-cy="workflow-delete-checkbox"]', + workflowsDeleteLabel: '[data-cy="workflow-delete-label"]', + workflowsDeleteHelperText: '[data-cy="workflow-delete-helper-text"]', + datasourcesCreateCheck: "[data-cy=datasource-create-checkbox]", + datasourcesCreateLabel: "[data-cy=datasource-create-label]", + datasourcesCreateHelperText: '[data-cy="datasource-create-helper-text"]', + datasourcesDeleteCheck: '[data-cy="datasource-delete-checkbox"]', + datasourcesDeleteLabel: '[data-cy="datasource-delete-label"]', + datasourcesDeleteHelperText: '[data-cy="datasource-delete-helper-text"]', foldersCreateCheck: "[data-cy=folder-create-checkbox]", foldersCreateLabel: "[data-cy=folder-create-label]", foldersHelperText: '[data-cy="folder-helper-text"]', workspaceVarCheckbox: '[data-cy="env-variable-checkbox"]', + appsText: '[data-cy="apps-text"]', appEditRadio: '[data-cy="app-edit-radio"]', appEditLabel: '[data-cy="app-edit-label"]', @@ -64,9 +84,26 @@ export const groupsSelector = { appHideCheckbox: '[data-cy="app-hide-from-dashboard-radio"]', appHideHelperText: '[data-cy="app-hide-from-dashboard-helper-text"]', appHideLabel: '[data-cy="app-hide-from-dashboard-label"]', + + workflowsText: '[data-cy="workflows-text"]', + workflowsBuildRadio: '[data-cy="workflow-build-radio"]', + workflowsBuildLabel: '[data-cy="workflow-build-label"]', + workflowsBuildHelperText: '[data-cy="workflow-build-helper-text"]', + workflowsExecuteRadio: '[data-cy="workflow-execute-radio"]', + workflowsExecuteLabel: '[data-cy="workflow-execute-label"]', + workflowsExecuteHelperText: '[data-cy="workflow-execute-helper-text"]', + + datasourcesText: '[data-cy="datasources-text"]', + datasourcesConfigureRadio: '[data-cy="datasource-configure-radio"]', + datasourcesConfigureLabel: '[data-cy="datasource-configure-label"]', + datasourcesConfigureHelperText: '[data-cy="datasource-configure-helper-text"]', + datasourcesBuildWithRadio: '[data-cy="datasource-build-with-radio"]', + datasourcesBuildWithLabel: '[data-cy="datasource-build-with-label"]', + datasourcesBuildWithHelperText: '[data-cy="datasource-build-with-helper-text"]', + appHidePermissionModalLabel: '[data-cy="hide-from-dashboard-permission-label"]', appHidePermissionModalHelperText: '[data-cy="hide-from-dashboard-permission-info-text"]', - addAppButton: '[data-cy="add-apps-buton"]', + addAppsButton: '[data-cy="add-apps-buton"]', addEditPermissionModal: '[data-cy = "add-edit-permission-modal"]', addEditPermissionModalTitle: '[data-cy="modal-title"]', permissionNameLabel: '[data-cy="permission-name-label"]', @@ -103,8 +140,8 @@ export const groupsSelector = { helperTextAdminPermissions: '[data-cy="helper-text-admin-permissions"]', updateGroupNameModalTitle: '[data-cy="update-group-title"]', editGranularPermissionIcon: '[data-cy="edit-permission-button"]', - granularAccessPermission: '[data-cy="granular-access-permission"]', - groupChip: '[data-cy="group-chip"]', + granularAccessPermission: '[data-cy="apps-granular-access"]', + groupChip: (accessType) => `[data-cy="${cyParamName(accessType)}-group-chip"]`, resourceContainer: '[data-cy="resources-container"]', groupLink: (groupname) => { return `[data-cy="${cyParamName(groupname)}-list-item"]`; @@ -150,7 +187,13 @@ export const groupsSelector = { granularEmptyPageIcon: '[data-cy="empty-page-svg"]', emptyPagePermissionHelperText: '[data-cy="empty-page-info-text"]', groupNameUpdateLink: '[data-cy="group-name-update-link"]', - - + addPermissionButton: '[data-cy="add-permission-button"]', + addAppButton: '[data-cy="add-app-button"]', + addWorkflowButton: '[data-cy="add-workflow-button"]', + addDatasourceButton: '[data-cy="add-data_source-button"]', + buildWorkflowradio: '[data-cy="build-permission-radio"]', + executeWorkflowradio: '[data-cy="execute-permission-radio"]', + configureDatasourceradio: '[data-cy="configure-permission-radio"]', + buildWithDatasourceRadio: '[data-cy="build-with-datasource-radio"]', }; diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index bc3f566129..eb2070f0fc 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -28,6 +28,7 @@ export const commonText = { createFolder: "Create folder", AddedToFolderToast: "Added to folder.", appCreatedToast: "App created successfully!", + appRenamedToast: "App name has been updated!", appRemovedFromFolderMessage: "The app will be removed from this folder, do you want to continue?", appRemovedFromFolderTaost: "Removed from folder.", @@ -135,6 +136,7 @@ export const commonText = { breadcrumbGlobalDatasourceTitle: "Global datasources", breadcrumbDatabaseTitle: "Databse", breadcrumbApplications: "Applications", + breadcrumbHome: "Home", breadcrumbSettings: "Settings", addNewDataSourceButton: "Add new datasource", @@ -261,3 +263,24 @@ export const widgetValue = (widgetName) => { export const customValidation = (name, message) => { return ["{{", `components.${name}.value ? true : '${message}'}}`]; }; + +export const settingsText = { + settingsHeader: "Settings", + allUsersListItem: "All Users", + allWorkspacesListItem: "All workspaces", + manageInstanceSettingsListItem: "Manage instance settings", + whiteLabellingListItem: "White labelling", + instanceLoginListItem: "Instance login", + emailProtocolListItem: "Email protocol (SMTP)", + licenseListItem: "License", +}; + +export const workspaceSettingsText = { + WorkspaceSettingsHeader: "Workspace settings", + usersListItem: "Users", + groupsListItem: "Groups", + workspaceLoginListItem: "Workspace login", + customStylesListItem: "Custom styles", + configureGitSyncListItem: "Configure git sync", + themesListItem: "Themes", +}; diff --git a/cypress-tests/cypress/constants/texts/dashboard.js b/cypress-tests/cypress/constants/texts/dashboard.js index 09b73d3711..c33b37ab36 100644 --- a/cypress-tests/cypress/constants/texts/dashboard.js +++ b/cypress-tests/cypress/constants/texts/dashboard.js @@ -49,4 +49,17 @@ export const dashboardText = { folderName: (folderName) => { return folderName; }, + homePageDividerText: "OR START WITH", + homePagePromptHeader: "What do you want to build today?", + appCardTitle: "Create a blank app", + appCardDescription: "Build custom apps that make internal processes efficient", + datasourceCardTitle: "Connect to a data source", + datasourceCardDescription: + "Link your tools to existing databases, spreadsheets, APIs, and more", + workflowCardTitle: "Create a workflow", + workflowCardDescription: + "Automate repetitive tasks to streamline business process", + exploreTemplateCardTitle: "Explore templates", + exploreTemplateCardDescription: + "Get started quickly with ready-to-deploy applications", }; diff --git a/cypress-tests/cypress/constants/texts/license.js b/cypress-tests/cypress/constants/texts/license.js new file mode 100644 index 0000000000..8e847cc08b --- /dev/null +++ b/cypress-tests/cypress/constants/texts/license.js @@ -0,0 +1,68 @@ +export const licenseText = { + comparePlansText: "Compare plans", + license: "License", + licenseOverviewTitle: "License overview", + licenseKeyLabel: "License Key", + limitsTabTitle: "Limits", + accessTabTitle: "Access", + domainTabTitle: "Domain", + updateButton: "Update", + + licenseKeyTab: { + licenseKeyTabTitle: "License Key", + licenseLabel: "License", + enterLicenseKeyPlaceholder: "Enter license key", + }, + + limitsTab: { + aiCreditsSubTab: "AI credits", + appsSubTab: "Apps", + workspacesSubTab: "Workspaces", + usersSubTab: "Users", + workflowsSubTab: "Workflows", + tablesSubTab: "Tables", + }, + + aiCreditsSubTab: { + monthlyRecurringLabel: "Monthly recurring", + addOnCreditsLabel: "Add on credits", + }, + + appsSubTab: { + noOfAppsLabel: "Number of Apps", + }, + + workspacesSubTab: { + noOfWorkspacesLabel: "Number of Workspaces", + }, + + usersSubTab: { + noOfTotalUsersLabel: "Number of Total Users", + noOfBuildersLabel: "Number of Builders", + noOfEndUsersLabel: "Number of End Users", + noOfSuperAdminsLabel: "Number of Super Admins", + }, + + workflowsSubTab: { + noOfWorkflowsLabel: "Number of Workflows", + }, + + tablesSubTab: { + noOfTablesLabel: "Number of Tables", + }, + + accessTab: { + openIDConnectLabel: "Open ID Connect", + apiKeyLabel: "Audit Logs", + ldapLabel: "LDAP", + samlLabel: "SAML", + customStylesLabel: "Custom styles", + multiEnvironmentLabel: "Multi-Environment", + gitSyncLabel: "GitSync", + }, + + domainTab: { + noDomainLinkedLabel: "No Domain Linked", + noDomainInfoText: "Please contact ToolJet team to link your domain", + }, +}; diff --git a/cypress-tests/cypress/constants/texts/manageGroups.js b/cypress-tests/cypress/constants/texts/manageGroups.js index a24c2a6118..770778f2b5 100644 --- a/cypress-tests/cypress/constants/texts/manageGroups.js +++ b/cypress-tests/cypress/constants/texts/manageGroups.js @@ -85,12 +85,29 @@ export const groupsText = { warningText: "Users must be always be part of one default group. This will define the user count in your plan.", continueButtonText: "Continue", roleUpdateToastMessage: "Role updated successfully", - endUserToBuilderMessage: "Changing the user role from end-user to builder will grant access the user access to all resources.Are you sure you want to continue?", - endUserToAdminMessage: "Changing the user role from end-user to admin will grant the user access to all resources and settings.Are you sure you want to continue?", - builderToEnduserMessage: "Changing the user role from builder to end-user will revoke their access to edit all resources.Are you sure you want to continue?", - builderToAdminMessage: "Changing user role from builder to admin will grant access to all resources and settings.Are you sure you want to continue?", + + endUserToBuilderMessage: Cypress.env('environment') === 'Community' + ? "Changing the user role from end-user to builder will grant access the user access to all resources.Are you sure you want to continue?" + : "Changing user default group from end-user to builder will affect the count of users covered by your plan.Are you sure you want to continue?", + + endUserToAdminMessage: Cypress.env('environment') === 'Community' + ? "Changing the user role from end-user to admin will grant the user access to all resources and settings.Are you sure you want to continue?" + : "Changing user default group from end-user to admin will affect the count of users covered by your plan.Are you sure you want to continue?", + + builderToEnduserMessage: Cypress.env('environment') === 'Community' + ? "Changing the user role from builder to end-user will revoke their access to edit all resources.Are you sure you want to continue?" + : "Changing user default group from builder to end-user will affect the count of users covered by your plan.This will also remove the user from any custom groups with builder-like permissions.Are you sure you want to continue?", + + builderToAdminMessage: Cypress.env('environment') === 'Community' + ? "Changing user role from builder to admin will grant access to all resources.Are you sure you want to continue?" + : "Changing user role from builder to admin will grant access to all resources and settings.Are you sure you want to continue?", + adminToBuilderMessage: "Changing your user default group from admin to builder will revoke your access to settings.Are you sure you want to continue?", - adminToEnduserMessage: "Changing the user role from admin to end-user will revoke their access to edit all resources and settings.Are you sure you want to continue?", + + adminToEnduserMessage: Cypress.env('environment') === 'Community' + ? "Changing the user role from admin to end-user will revoke their access to edit all resources and settings.Are you sure you want to continue?" + : "Changing your user group from admin to end-user will revoke your access to settings. This will also affect the count of users covered by your plan.Are you sure you want to continue?", + modalHeader: "Can not remove last active admin", modalMessage: "Cannot change role of last present admin, please add another admin and change the role", userAddedToast: "Users added to the group", diff --git a/cypress-tests/cypress/constants/texts/workflows.js b/cypress-tests/cypress/constants/texts/workflows.js index 7db7de9a7e..17f8dd2b88 100644 --- a/cypress-tests/cypress/constants/texts/workflows.js +++ b/cypress-tests/cypress/constants/texts/workflows.js @@ -33,8 +33,8 @@ FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';`, postgresResponseNodeQuery: "return postgresql1.data", - postgresExpectedValue: "employees", - + postgresExpectedValue: "server_side_pagination", + restApiUrl: "http://9.234.17.31:8000/delay/10s", restApiResponseNodeQuery: "return restapi1.data", restApiExpectedValue: "", @@ -47,7 +47,7 @@ AND table_type = 'BASE TABLE';`, runjsCodeForWebhooks: 'return "Verifying webhooks response"', runjsExpectedValueForWebhooks: "Verifying webhooks response", - expectedStatusCodeText: 201, + expectedStatusCodeText: 200, exportFixturePath: "cypress/fixtures/exportedApp.json", workflowLabel: "Workflow", }; diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js index 9d9dad964d..939363cff2 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js @@ -14,7 +14,7 @@ describe("Chaining of queries", () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-chaining-App`); cy.openApp(); - cy.apiFetchDataSourcesId(); + cy.apiFetchDataSourcesIdFromApp(); cy.viewport(1800, 1800); cy.dragAndDropWidget("Button"); resizeQueryPanel("80"); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js index 26a80bfb52..140af39282 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js @@ -1,15 +1,17 @@ import { fake } from "Fixtures/fake"; -import { commonSelectors } from "Selectors/common"; -import { importSelectors } from "Selectors/exportImport"; -import { commonText } from "Texts/common"; +import { commonSelectors } from "Selectors/common"; +import { importSelectors, exportAppModalSelectors } from "Selectors/exportImport"; +import { commonText } from "Texts/common"; import { exportAppModalText } from "Texts/exportImport"; + import { clickOnExportButtonAndVerify, exportAllVersionsAndVerify, verifyElementsOfExportModal, + validateExportedAppStructure, } from "Support/utils/exportImport"; -import { selectAppCardOption, closeModal } from "Support/utils/common"; +import { selectAppCardOption, closeModal, deleteDownloadsFolder } from "Support/utils/common"; describe("App Export", () => { const TEST_DATA = { @@ -19,35 +21,30 @@ describe("App Export", () => { }, }; - let data; - - data = { + const generateTestData = () => ({ workspaceName: fake.firstName, workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), appName: `${fake.companyName}-IE-App`, appReName: `${fake.companyName}-${fake.companyName}-IE-App`, dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), - }; + }); + + let data; beforeEach(() => { - data = { - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), - appName: `${fake.companyName}-IE-App`, - appReName: `${fake.companyName}-${fake.companyName}-IE-App`, - dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), - }; + data = generateTestData(); + + deleteDownloadsFolder(); cy.exec("mkdir -p ./cypress/downloads/"); - cy.exec("cd ./cypress/downloads/ && rm -rf *"); - cy.exec("mkdir -p ./cypress/downloads/"); - cy.wait(3000); cy.apiLogin(); cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); }); - it("Verify the elements of export dialog box", () => { + it("should verify elements of export dialog box", () => { + cy.intercept("POST", "**/api/v2/resources/import").as("importApp"); + cy.intercept("POST", "**/api/v2/resources/export").as("exportApp"); + cy.skipWalkthrough(); cy.apiLogin(); @@ -55,31 +52,25 @@ describe("App Export", () => { cy.get(importSelectors.importOptionInput) .eq(0) .selectFile(TEST_DATA.appFiles.multiVersion, { force: true }); - cy.wait(2000); cy.clearAndType(commonSelectors.appNameInput, data.appName); cy.get(importSelectors.importAppButton).click(); - cy.wait(3000); + cy.wait("@importApp"); cy.backToApps(); + cy.reload(); - // Select the app card option to export the app selectAppCardOption( data.appName, commonSelectors.appCardOptions(commonText.exportAppOption) ); - // Verify the elements of the export modal verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]); - // Close the modal closeModal(exportAppModalText.modalCloseButton); - // Ensure the modal title is no longer visible cy.get( commonSelectors.modalTitle(exportAppModalText.selectVersionTitle) ).should("not.exist"); - // Re-open the export modal and click the export button - cy.wait(2000); selectAppCardOption( data.appName, commonSelectors.appCardOptions(commonText.exportAppOption) @@ -90,95 +81,24 @@ describe("App Export", () => { const downloadedAppExportFileName = result.stdout.split("\n")[0]; const filePath = `./cypress/downloads/${downloadedAppExportFileName}`; - // Ensure the file name contains the expected app export name expect(downloadedAppExportFileName).to.contain( data.appName.toLowerCase() ); - // Read and validate the exported JSON file cy.readFile(filePath).then((appData) => { - // Validate the app name - const appNameFromFile = appData.app[0].definition.appV2.name; - expect(appNameFromFile).to.equal(data.appName); - - // Validate the schema for the student table in tooljetdb - const tooljetDatabase = appData.tooljet_database.find( - (db) => db.table_name === "student" - ); - expect(tooljetDatabase).to.exist; - expect(tooljetDatabase.schema).to.exist; - - // Validate components and queries - const components = appData.app[0].definition.appV2.components; - - const text2Component = components.find( - (component) => component.name === "text2" - ); - expect(text2Component).to.exist; - expect(text2Component.properties.text.value).to.equal( - "{{constants.pageHeader}}" - ); - - const textinput1 = components.find( - (component) => component.name === "textinput1" - ); - expect(textinput1).to.exist; - expect(textinput1.properties.value.value).to.include("queries"); - - const textinput2 = components.find( - (component) => component.name === "textinput2" - ); - expect(textinput2).to.exist; - expect(textinput2.properties.value.value).to.include("queries"); - - const textinput3 = components.find( - (component) => component.name === "textinput3" - ); - expect(textinput3).to.exist; - expect(textinput3.properties.value.value).to.include("queries"); - - // Validate the data queries - const dataQueries = appData.app[0].definition.appV2.dataQueries; - - const postgresqlQuery = dataQueries.find( - (query) => query.name === "postgresql1" - ); - expect(postgresqlQuery).to.exist; - expect(postgresqlQuery.options.query).to.include( - "Select * from {{secrets.db_name}}" - ); - - const restapiQuery = dataQueries.find( - (query) => query.name === "restapi1" - ); - expect(restapiQuery).to.exist; - expect(restapiQuery.options.url).to.equal( - "https://jsonplaceholder.typicode.com/users/1" - ); - - const tooljetdbQuery = dataQueries.find( - (query) => query.name === "tooljetdb1" - ); - expect(tooljetdbQuery).to.exist; - expect(tooljetdbQuery.options.operation).to.equal("list_rows"); - - // Ensure appVersions exists - const appVersions = appData.app[0].definition.appV2.appVersions; - expect(appVersions).to.exist; - - // Map and verify app version names - const versionNames = appVersions.map((version) => version.name); - expect(versionNames).to.include.members(["v1", "v2", "v3"]); + validateExportedAppStructure(appData, data.appName, { + expectedVersions: ["v1", "v2", "v3"], + }); }); }); - cy.exec("cd ./cypress/downloads/ && rm -rf *"); + deleteDownloadsFolder(); selectAppCardOption( data.appName, commonSelectors.appCardOptions(commonText.exportAppOption) ); - cy.get(`[data-cy="v1-radio-button"]`).check(); + cy.get(exportAppModalSelectors.versionRadioButton("v3")).check(); cy.get( commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion) ).click(); @@ -187,32 +107,32 @@ describe("App Export", () => { const downloadedAppExportFileName = result.stdout.split("\n")[0]; const filePath = `./cypress/downloads/${downloadedAppExportFileName}`; - // Ensure the file name contains the expected app export name expect(downloadedAppExportFileName).to.contain( data.appName.toLowerCase() ); - // Read and validate the exported JSON file cy.readFile(filePath).then((appData) => { - // Validate the app name - const appNameFromFile = appData.app[0].definition.appV2.name; - expect(appNameFromFile).to.equal(data.appName); + validateExportedAppStructure(appData, data.appName, { + validateVersions: false, + }); }); }); }); - it.skip("Verify 'Export app' functionality of an application inside app editor", () => { + it("should verify export app functionality inside app editor", () => { + cy.intercept("POST", "**/api/v2/resources/export").as("exportApp"); + data.appName2 = `${fake.companyName}-App`; cy.apiCreateApp(data.appName2); cy.openApp(data.appName2); - cy.dragAndDropWidget("Text Input", 50, 50); + cy.dragAndDropWidget("Text Input", 200, 200); - cy.get('[data-cy="left-sidebar-settings-button"]').click(); - cy.get('[data-cy="button-user-status-change"]').click(); + cy.get(commonSelectors.leftSideBarSettingsButton).click(); + cy.get(commonSelectors.buttonSelector("Export app")).click(); verifyElementsOfExportModal("v1"); - exportAllVersionsAndVerify(data.appName1, "v1"); + exportAllVersionsAndVerify(data.appName2, "v1"); }); -}); +}); \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js index cfe0c426d1..226776e2be 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js @@ -2,13 +2,16 @@ import { fake } from "Fixtures/fake"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { appVersionSelectors, importSelectors } from "Selectors/exportImport"; import { dashboardSelector } from "Selectors/dashboard"; -import { buttonText } from "Texts/button"; - import { importText } from "Texts/exportImport"; -import { importAndVerifyApp } from "Support/utils/exportImport"; +import { + importAndVerifyApp, + verifyImportModalElements, + setupDataSourceWithConstants, +} from "Support/utils/exportImport"; import { switchVersionAndVerify } from "Support/utils/version"; +import { renameApp } from 'Support/utils/editor/editorHeaderOperations'; -describe("App Import Functionality", () => { +describe("App Import", () => { const TEST_DATA = { toolJetImage: "cypress/fixtures/Image/tooljet.png", invalidApp: "cypress/fixtures/templates/invalid_app.json", @@ -19,112 +22,94 @@ describe("App Import Functionality", () => { }, }; + const generateTestData = () => ({ + workspaceName: fake.firstName, + workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), + appName: `${fake.companyName}-IE-App`, + appReName: `${fake.companyName}-${fake.companyName}-IE-App`, + dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), + }); + + const setupWorkspaceConstants = (dsEnv) => { + cy.apiCreateWorkspaceConstant("pageHeader", "Import and Export", ["Global"], [dsEnv]); + cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], [dsEnv]); + }; + + const getDataSourceEnvironment = () => { + const edition = Cypress.env("environment"); + return edition === "Community" ? "production" : "development"; + }; + + const verifyAppNameInEditor = (expectedName) => { + cy.get('[data-cy="edit-app-name-button"]') + .should("be.visible") + .verifyVisibleElement("have.text", expectedName); + }; + + const setupCommunityOrEnterpriseDataSource = () => { + const edition = Cypress.env("environment"); + if (edition === "Community" || edition === "Enterprise") { + const dsEnv = getDataSourceEnvironment(); + setupDataSourceWithConstants(dsEnv); + setupWorkspaceConstants(dsEnv); + return dsEnv; + } + return null; + }; + let data; beforeEach(() => { cy.viewport(1400, 1400); - data = { - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), - appName: `${fake.companyName}-IE-App`, - appReName: `${fake.companyName}-${fake.companyName}-IE-App`, - dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), - }; + data = generateTestData(); cy.apiLogin(); - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); + cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug).then((workspace) => { + Cypress.env("workspaceId", workspace.body.organization_id); + }); cy.skipWalkthrough(); - }); - - it("should verify app import functionality", () => { - cy.apiLogin(); cy.visit(`${data.workspaceSlug}`); - - // Test invalid file import - cy.get(dashboardSelector.importAppButton).click(); - importAndVerifyApp( - TEST_DATA.toolJetImage, - importText.couldNotImportAppToastMessage - ); - - cy.wait(500); - cy.get(dashboardSelector.importAppButton).click(); - importAndVerifyApp( - TEST_DATA.invalidApp, - "Could not import: SyntaxError: Expected ',' or '}' after property value in JSON at position 246 (line 11 column 13)" - ); - - cy.wait(500); - - // Test valid app import + }); + it("should verify invalid import files", () => { cy.get(importSelectors.dropDownMenu).should("be.visible").click(); cy.get(importSelectors.importOptionLabel).verifyVisibleElement( "have.text", importText.importOption ); + cy.get(dashboardSelector.importAppButton).click(); + importAndVerifyApp( + TEST_DATA.toolJetImage, + importText.couldNotImportAppToastMessage + ); + + cy.get(dashboardSelector.importAppButton).should("be.visible").click(); + importAndVerifyApp( + TEST_DATA.invalidApp, + "Could not import: SyntaxError: Expected ',' or '}' after property value in JSON at position 246 (line 11 column 13)" + ); + }); + + it("should verify app with multiple version", () => { cy.intercept("POST", "/api/v2/resources/import").as("importApp"); + cy.get(importSelectors.importOptionInput) .eq(0) - .selectFile(TEST_DATA.appFiles.multiVersion, { - force: true, - }); - cy.wait(1500); + .selectFile(TEST_DATA.appFiles.multiVersion, { force: true }); - cy.get(importSelectors.importAppTitle).verifyVisibleElement( - "have.text", - "Import app" - ); - cy.get(commonSelectors.appNameLabel).verifyVisibleElement( - "have.text", - "App name" - ); - cy.get(commonSelectors.appNameInput) - .should("be.visible") - .and("have.value", "three-versions"); - cy.get(commonSelectors.appNameInfoLabel).verifyVisibleElement( - "have.text", - "App name must be unique and max 50 characters" - ); - cy.get(commonSelectors.cancelButton) - .should("be.visible") - .and("have.text", "Cancel"); - cy.get(commonSelectors.importAppButton).verifyVisibleElement( - "have.text", - "Import app" - ); + verifyImportModalElements("three-versions"); cy.get(importSelectors.importAppButton).click(); - cy.get(".go3958317564") + cy.get(commonSelectors.toastMessage) .should("be.visible") .and("have.text", importText.appImportedToastMessage); - // Verify imported app cy.get(commonSelectors.toastCloseButton).click(); - cy.wait(500); - cy.get(commonSelectors.appNameInput).verifyVisibleElement( - "contain.value", - "three-versions" - ); + verifyAppNameInEditor("three-versions"); cy.get(appVersionSelectors.currentVersionField("v3")).should("be.visible"); - // Configure app - cy.skipEditorPopover(); - cy.dragAndDropWidget(buttonText.defaultWidgetText); - cy.get(appVersionSelectors.appVersionLabel).should("be.visible"); - cy.get(commonWidgetSelector.draggableWidget("button1")).should( - "be.visible" - ); - - cy.renameApp(data.appName); - cy.get(commonSelectors.appNameInput).verifyVisibleElement( - "contain.value", - data.appName - ); - cy.waitForAutoSave(); - - // Verify initial widget states + renameApp(data.appName); + verifyAppNameInEditor(data.appName); verifyCommonData({ text2: "", @@ -132,72 +117,16 @@ describe("App Import Functionality", () => { textInput2: "Leanne Graham", }); - // cy.get( - // commonWidgetSelector.draggableWidget("textInput3") - // ).verifyVisibleElement("have.value", ""); - - // Setup database and data sources cy.visit(`${data.workspaceSlug}/database`); - cy.get('[data-cy="student-table"]').verifyVisibleElement( - "have.text", - "student" - ); - - // cy.apiAddDataToTable("student", { - // name: "Paramu", - // country: "India", - // state: "Kerala", - // }); + cy.get('[data-cy="student-table"]').verifyVisibleElement("have.text", "student"); cy.visit(`${data.workspaceSlug}/data-sources`); cy.get('[data-cy="postgresql-button"]').should("be.visible"); + setupCommunityOrEnterpriseDataSource(); - cy.ifEnv("Community", () => { - cy.apiUpdateDataSource("postgresql", "production", { - options: [ - { - key: "password", - value: `${Cypress.env("pg_password")}`, - encrypted: true, - }, - ], - }); - }); - cy.ifEnv("Enterprise", () => { - cy.apiUpdateDataSource("postgresql", "development", { - options: [ - { - key: "password", - value: `${Cypress.env("pg_password")}`, - encrypted: true, - }, - ], - }); - }); - - cy.ifEnv("Community", () => { - cy.apiCreateWorkspaceConstant( - "pageHeader", - "Import and Export", - ["Global"], - ["production"] - ); - cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], ["production"]); - }); - - cy.ifEnv("Enterprise", () => { - cy.apiCreateWorkspaceConstant( - "pageHeader", - "Import and Export", - ["Global"], - ["development"] - ); - cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], ["development"]); - }); - - // Verify app after setup cy.wait("@importApp").then((interception) => { const appId = interception.response.body.imports.app[0].id; + cy.log(`Imported app id: ${appId}`); cy.openApp( "", Cypress.env("workspaceId"), @@ -211,31 +140,28 @@ describe("App Import Functionality", () => { textInput1: "John", textInput2: "Leanne Graham", }); - // cy.get( - // commonWidgetSelector.draggableWidget("textInput3") - // ).verifyVisibleElement("have.value", "India"); switchVersionAndVerify("v3", "v1"); - verifyCommonData({ text2: "Import and Export", textInput1: "John", textInput2: "Leanne Graham", }); + }); - cy.wait(1000); - cy.backToApps(); - - // Test single version import + it("should verify app with single version", () => { cy.get(importSelectors.dropDownMenu).click(); + const dsEnv = setupCommunityOrEnterpriseDataSource(); + importAndVerifyApp(TEST_DATA.appFiles.singleVersion); - // Verify final state - cy.get(commonSelectors.appNameInput).verifyVisibleElement( - "contain.value", - "one_version" - ); + verifyAppNameInEditor("one_version"); + if (dsEnv) { + setupDataSourceWithConstants(dsEnv); + } + + cy.reload(); verifyCommonData({ text2: "Import and Export", textInput1: "John", diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js index c4a2c674ff..6ee125e012 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js @@ -1,53 +1,40 @@ -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { fake } from "Fixtures/fake"; -import { releaseApp } from "Support/utils/common"; +import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { + resolveHost, verifySlugValidations, verifySuccessfulSlugUpdate, verifyURLs, - resolveHost, } from "Support/utils/apps"; -import { appPromote } from "Support/utils/platform/multiEnv"; +import { releaseApp } from "Support/utils/common"; describe("App Slug", () => { - const data = {}; - const host = resolveHost(); - - beforeEach(() => { - data.slug = `${fake.companyName.toLowerCase()}-app`; - data.appName = `${fake.companyName} App`; - cy.defaultWorkspaceLogin(); + const generateTestData = () => ({ + slug: `${fake.companyName.toLowerCase()}-app`, + appName: `${fake.companyName} App`, }); - before(() => { - data.appName = `${fake.companyName} App`; + const host = resolveHost(); + let data; + + beforeEach(() => { + data = generateTestData(); cy.apiLogin(); + cy.skipWalkthrough(); cy.apiCreateApp(data.appName); - cy.wait(1000); - cy.apiLogout(); + cy.openApp() }); it("Verify app slug cases in global settings", () => { - const workspaceId = Cypress.env("workspaceId"); - const appId = Cypress.env("appId"); - const appUrl = `${host}/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`; - cy.apiLogin(); - cy.skipWalkthrough(); - cy.visit(appUrl); - cy.url().then((url) => { - if (url !== appUrl) { - cy.visit(appUrl); - } - }); - cy.url().should("eq", appUrl); - cy.wait(1000); + cy.url().should("eq", `${host}/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`); + + cy.get('[data-cy="query-manager-toggle-button"]', { timeout: 20000 }).should("be.visible").click(); cy.get(commonSelectors.leftSideBarSettingsButton).click(); - // Verify initial state cy.get(commonWidgetSelector.appSlugLabel).verifyVisibleElement( "have.text", "Unique app slug" @@ -68,26 +55,21 @@ describe("App Slug", () => { cy.get(commonWidgetSelector.appLinkField).verifyVisibleElement( "have.text", - `${host}/${workspaceId}/apps/${appId}` + `${host}/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}` ); - // Validate all error cases verifySlugValidations(commonWidgetSelector.appSlugInput); - // Verify successful slug update cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug); - verifySuccessfulSlugUpdate(workspaceId, data.slug); + verifySuccessfulSlugUpdate(Cypress.env("workspaceId"), data.slug); - // Verify persistence cy.get('[data-cy="left-sidebar-debugger-button"]').click(); cy.get(commonSelectors.leftSideBarSettingsButton).click(); cy.get(commonWidgetSelector.appSlugInput).should("have.value", data.slug); - // Release and verify URLs releaseApp(); - verifyURLs(workspaceId, data.slug, true); + verifyURLs(Cypress.env("workspaceId"), data.slug, true); - // Verify duplicate slug validation cy.visit("/my-workspace"); cy.apiCreateApp(data.slug); cy.openApp("my-workspace"); @@ -101,20 +83,14 @@ describe("App Slug", () => { }); it("Verify app slug cases in share modal", () => { - cy.apiLogin(); - const workspaceId = Cypress.env("workspaceId"); - cy.apiCreateApp(data.appName); - cy.openApp("my-workspace"); - // Set up initial slug cy.get(commonSelectors.leftSideBarSettingsButton).click(); cy.get(commonWidgetSelector.appSlugInput).clear(); cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug); releaseApp(); - // Verify share modal cy.get(commonWidgetSelector.shareAppButton).click(); cy.get(commonWidgetSelector.appLink).verifyVisibleElement( "have.text", @@ -125,12 +101,11 @@ describe("App Slug", () => { data.slug ); - // Validate all error cases in share modal verifySlugValidations(commonWidgetSelector.appNameSlugInput); - cy.wait(500); cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug); cy.get('[data-cy="app-slug-info-label"]') + .should("be.visible") .invoke("text") .then((text) => { expect(text.trim()).to.eq( @@ -138,26 +113,23 @@ describe("App Slug", () => { ); }); - // Verify successful slug update in share modal - data.slug = `${fake.companyName.toLowerCase()}-app`; - cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug); + const newSlug = `${fake.companyName.toLowerCase()}-app`; + cy.clearAndType(commonWidgetSelector.appNameSlugInput, newSlug); cy.get('[data-cy="app-slug-accepted-label"]').verifyVisibleElement( "have.text", "Slug accepted!" ); - // Close modal and verify URLs cy.get(commonWidgetSelector.modalCloseButton).click(); - verifyURLs(workspaceId, data.slug, true); + verifyURLs(Cypress.env("workspaceId"), newSlug, true); - // Verify duplicate slug validation in share modal cy.visit("/my-workspace"); - cy.apiCreateApp(data.slug); + cy.apiCreateApp(newSlug); cy.openApp("my-workspace"); releaseApp(); cy.get(commonWidgetSelector.shareAppButton).click(); - cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug); + cy.clearAndType(commonWidgetSelector.appNameSlugInput, newSlug); cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement( "have.text", "This app slug is already taken." diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appVersion.cy.js similarity index 66% rename from cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js rename to cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appVersion.cy.js index 433d74b05e..7ce9997c42 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appVersion.cy.js @@ -11,6 +11,7 @@ import { verifyElementsOfCreateNewVersionModal, navigateToEditVersionModal, switchVersionAndVerify, + openPreviewSettings, } from "Support/utils/version"; import { appVersionSelectors } from "Selectors/exportImport"; import { editVersionSelectors } from "Selectors/version"; @@ -27,48 +28,48 @@ import { createRestAPIQuery } from "Support/utils/dataSource"; import { deleteQuery } from "Support/utils/queries"; import { selectEnv, appPromote } from "Support/utils/platform/multiEnv"; describe("App Version", () => { + const generateTestData = () => ({ + appName: `${fake.companyName}-Version-App`, + datasourceName: fake.firstName.toLowerCase(), + query1: fake.firstName.toLowerCase(), + query2: fake.firstName.toLowerCase(), + }); + + const verifyWidget = (selector, assertion, expectedValue) => { + cy.get(commonWidgetSelector.draggableWidget(selector)) + .verifyVisibleElement(assertion, expectedValue); + }; + let data; - let currentVersion = ""; - let newVersion = []; - let versionFrom = ""; - beforeEach(() => { - data = { - appName: `${fake.companyName}-Version-App`, - datasourceName: fake.firstName.toLowerCase(), - query1: fake.firstName.toLowerCase(), - query2: fake.firstName.toLowerCase(), - }; + data = generateTestData(); cy.defaultWorkspaceLogin(); cy.apiCreateApp(data.appName); cy.openApp(); cy.viewport(1400, 1400); - }); it("should verify basic version management operations", () => { - // Version modal verification cy.get(appVersionSelectors.appVersionLabel).should("be.visible"); navigateToCreateNewVersionModal("v1"); verifyElementsOfCreateNewVersionModal(["v1"]); - // Empty version name validation navigateToCreateNewVersionModal("v1"); - cy.get('[data-cy="create-new-version-button"]').click(); + cy.get(appVersionSelectors.createNewVersionButton).click(); + cy.get(appVersionSelectors.createNewVersionButton).click(); + cy.verifyToastMessage( commonSelectors.toastMessage, "Version name should not be empty" ); - cy.wait(2000); + cy.get(appVersionSelectors.createVersionTitle).should("not.exist"); - // Duplicate version name check verifyDuplicateVersion(["v1"], "v1"); closeModal(commonText.closeButton); - // Version edit modal verification navigateToEditVersionModal("v1"); verifyModal( editVersionText.editVersionTitle, @@ -76,45 +77,39 @@ describe("App Version", () => { editVersionSelectors.versionNameInputField ); closeModal(commonText.closeButton); - cy.wait(1000); + cy.get(editVersionSelectors.editVersionTitle).should("not.exist"); - // Version editing editVersionAndVerify( "v1", ["v2"], editVersionText.VersionNameUpdatedToastMessage ); - // Component operations in version verifyComponentinrightpannel("table"); + cy.get(commonSelectors.rightSidebarPlusButton).click(); cy.dragAndDropWidget("text"); cy.waitForAutoSave(); - // New version creation navigateToCreateNewVersionModal("v2"); createNewVersion(["v3"], "v2"); cy.waitForAutoSave(); verifyComponentinrightpannel("table"); - // Component deletion deleteComponentAndVerify("text1"); cy.waitForAutoSave(); - cy.wait(2000); + cy.get(commonWidgetSelector.draggableWidget("text1")).should("not.exist"); - // Version deletion deleteVersionAndVerify( "v3", onlydeleteVersionText.deleteToastMessage("v3") ); cy.get(appVersionSelectors.currentVersionField("v2")).should("be.visible"); - cy.wait(3000); + cy.get(appVersionSelectors.currentVersionField("v3")).should("not.exist"); - // cy.reload(); cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible", { timeout: 10000, }); - // Preview and release verification cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.ifEnv("Community", () => { @@ -135,7 +130,6 @@ describe("App Version", () => { }); it("should verify version management with components and queries", () => { - // Initial setup with component and datasource cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, data.datasourceName, @@ -157,20 +151,15 @@ describe("App Version", () => { appPromote("development", "production"); }); - // Version v2 creation and verification and v2 is created from v1 production environment navigateToCreateNewVersionModal("v1"); createNewVersion(["v2"], "v1"); - cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( - "have.text", - "Leanne Graham" - ); + verifyWidget("text1", "have.text", "Leanne Graham"); cy.get(`[data-cy="list-query-${data.query1}"]`).should("be.visible"); - // Modify v2 with new components and queries deleteComponentAndVerify("text1"); cy.waitForAutoSave(); deleteQuery(data.query1); - cy.get('[data-cy="modal-confirm-button"]').click(); + cy.get(commonSelectors.modalConfirmButton).click(); createRestAPIQuery(data.query2, data.datasourceName, "", "", "/2", true); cy.apiAddComponentToApp( data.appName, @@ -181,7 +170,6 @@ describe("App Version", () => { ); cy.waitForAutoSave(); - // Version creation and state verification const versionChecks = [ { create: { version: "v3", from: "v2" }, @@ -210,29 +198,21 @@ describe("App Version", () => { navigateToCreateNewVersionModal(check.create.from); createNewVersion([check.create.version], check.create.from); cy.waitForAutoSave(); - cy.wait(1000); - if (check.verify.component.value) { - cy.get( - commonWidgetSelector.draggableWidget(check.verify.component.selector) - ).verifyVisibleElement("have.value", check.verify.component.value); - } else { - cy.get( - commonWidgetSelector.draggableWidget(check.verify.component.selector) - ).verifyVisibleElement("have.text", check.verify.component.text); - } - cy.get(`[data-cy="list-query-${check.verify.query}"]`).should( - "be.visible" - ); + cy.get(appVersionSelectors.currentVersionField(check.create.version)).should("be.visible"); + + const assertion = check.verify.component.value ? "have.value" : "have.text"; + const expected = check.verify.component.value || check.verify.component.text; + verifyWidget(check.verify.component.selector, assertion, expected); + + cy.get(`[data-cy="list-query-${check.verify.query}"]`).should("be.visible"); }); - // Release and version state verification releasedVersionAndVerify("v5"); cy.get(appVersionSelectors.currentVersionField("v5")).should( "have.class", "color-light-green" ); - // Version switching and component verification cy.ifEnv("Enterprise", () => { selectEnv("development"); }); @@ -242,13 +222,9 @@ describe("App Version", () => { "not.have.class", "color-light-green" ); - cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( - "have.text", - "Leanne Graham" - ); + verifyWidget("text1", "have.text", "Leanne Graham"); cy.get(`[data-cy="list-query-${data.query1}"]`).should("be.visible"); - // Preview and version switching verification cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.ifEnv("Community", () => { @@ -258,17 +234,12 @@ describe("App Version", () => { cy.url().should("include", "/home?env=development&version=v4"); }); - cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( - "have.text", - "Leanne Graham" - ); + verifyWidget("text1", "have.text", "Leanne Graham"); - cy.get('[data-cy="preview-settings"]').click(); + openPreviewSettings(); switchVersionAndVerify("v4", "v5"); - cy.get( - commonWidgetSelector.draggableWidget("textInput") - ).verifyVisibleElement("have.value", "Ervin Howell"); + verifyWidget("textInput", "have.value", "Ervin Howell"); cy.ifEnv("Enterprise", () => { cy.openApp( @@ -281,62 +252,43 @@ describe("App Version", () => { navigateToCreateNewVersionModal("v5"); createNewVersion(["v6"], "v5"); cy.waitForAutoSave(); - cy.wait(1000); + cy.get(appVersionSelectors.currentVersionField("v6")).should("be.visible"); appPromote("development", "staging"); - cy.get( - commonWidgetSelector.draggableWidget("textInput") - ).verifyVisibleElement("have.value", "Ervin Howell"); + verifyWidget("textInput", "have.value", "Ervin Howell"); cy.get(`[data-cy="list-query-${data.query2}"]`).should("be.visible"); appPromote("staging", "production"); - cy.get( - commonWidgetSelector.draggableWidget("textInput") - ).verifyVisibleElement("have.value", "Ervin Howell"); + verifyWidget("textInput", "have.value", "Ervin Howell"); cy.get(`[data-cy="list-query-${data.query2}"]`).should("be.visible"); cy.openInCurrentTab(commonWidgetSelector.previewButton); - cy.get( - commonWidgetSelector.draggableWidget("textInput") - ).verifyVisibleElement("have.value", "Ervin Howell"); + verifyWidget("textInput", "have.value", "Ervin Howell"); cy.url().should("include", "/home?env=production&version=v6"); - cy.wait(1000); - - cy.get('[data-cy="preview-settings"]').click(); + openPreviewSettings(); switchVersionAndVerify("v6", "v1"); - cy.get( - commonWidgetSelector.draggableWidget("text1") - ).verifyVisibleElement("have.text", "Leanne Graham"); - // url bug - // cy.url().should("include", "/home?env=production&version=v1"); - cy.wait(1000); - cy.get('[data-cy="preview-settings"]').click(); + verifyWidget("text1", "have.text", "Leanne Graham"); + + openPreviewSettings(); switchVersionAndVerify("v1", "v6"); - cy.wait(1000); - cy.get('[data-cy="preview-settings"]').click(); + openPreviewSettings(); + cy.forceClickOnCanvas(); + openPreviewSettings(); selectEnv("staging"); - cy.get( - commonWidgetSelector.draggableWidget("textInput") - ).verifyVisibleElement("have.value", "Ervin Howell"); - // cy.url().should("include", "/home?env=staging&version=v6"); + verifyWidget("textInput", "have.value", "Ervin Howell"); - - cy.wait(1000); - cy.get('[data-cy="preview-settings"]').click(); + openPreviewSettings(); selectEnv("development"); - cy.wait(1000); - cy.get('[data-cy="preview-settings"]').click(); + openPreviewSettings(); switchVersionAndVerify("v6", "v1"); - cy.get( - commonWidgetSelector.draggableWidget("text1") - ).verifyVisibleElement("have.text", "Leanne Graham"); + verifyWidget("text1", "have.text", "Leanne Graham"); }); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js index a1e3ffadc5..a79bc9d7ec 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js @@ -1,11 +1,11 @@ import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { fake } from "Fixtures/fake"; import { logout, releaseApp } from "Support/utils/common"; -import { inviteUserToWorkspace } from "Support/utils/manageUsers"; +import { inviteUserToWorkspace, fetchAndVisitInviteLinkViaMH } from "Support/utils/manageUsers"; import { setSignupStatus } from "Support/utils/manageSSO"; import { onboardingSelectors } from "Selectors/onboarding"; import { commonText } from "Texts/common"; -import { userSignUp, addNewUser } from "Support/utils/onboarding"; +import { userSignUp } from "Support/utils/onboarding"; import { setUpSlug, setupAppWithSlug, @@ -21,35 +21,46 @@ describe( retries: { runMode: 2 }, }, () => { + const generateTestData = () => ({ + appName: `${fake.companyName} P P App`, + slug: `${fake.companyName} P P App`.toLowerCase().replace(/\s+/g, "-"), + firstName: fake.firstName, + email: fake.email.toLowerCase(), + workspaceName: fake.firstName, + workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), + appPublicSlug: `${fake.companyName} Public App` + .toLowerCase() + .replace(/\s+/g, "-"), + appPublicName: `${fake.companyName} Public App`, + appPrivateSlug: `${fake.companyName} Private App` + .toLowerCase() + .replace(/\s+/g, "-"), + appPrivateName: `${fake.companyName} Private App`, + }); + + const verifyWidget = (widgetName) => { + cy.get(commonWidgetSelector.draggableWidget(widgetName)).should("be.visible"); + }; + + const getAppUrl = (slug) => `${Cypress.config("baseUrl")}/applications/${slug}`; + let data; beforeEach(() => { - data = { - appName: `${fake.companyName} P P App`, - slug: `${fake.companyName} P P App`.toLowerCase().replace(/\s+/g, "-"), - firstName: fake.firstName, - email: fake.email.toLowerCase(), - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), - }; - + data = generateTestData(); cy.defaultWorkspaceLogin(); cy.skipWalkthrough(); }); - it("Verify private and public app share functionality", () => { + it("should verify private and public app share functionality", () => { cy.apiCreateApp(data.appName); cy.openApp(); - cy.apiAddComponentToApp(data.appName, "text1"); + cy.dragAndDropWidget("text", 500, 500); - // Check unreleased version state - cy.get('[data-cy="share-button-link"]>span').should("be.visible").click(); - cy.contains("This version has not been released yet").should( - "be.visible" - ); + cy.get(commonWidgetSelector.shareAppButton).should("be.visible").click(); + cy.contains("This version has not been released yet").should("be.visible"); cy.get(commonWidgetSelector.modalCloseButton).click(); - // Release and verify share modal releaseApp(); cy.get(commonWidgetSelector.shareAppButton).click(); for (const elements in commonWidgetSelector.shareModalElements) { @@ -61,7 +72,6 @@ describe( ); } - // Verify share modal elements const shareModalSelectors = [ "copyAppLinkButton", "makePublicAppToggle", @@ -73,9 +83,8 @@ describe( cy.get(commonWidgetSelector[selector]).should("be.visible"); }); - // Configure and verify slug cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug); - cy.get('[data-cy="app-slug-accepted-label"]') + cy.get(commonSelectors.appSlugAccept) .should("be.visible") .and("have.text", "Slug accepted!"); @@ -83,24 +92,14 @@ describe( cy.forceClickOnCanvas(); cy.backToApps(); - // Test private access logout(); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); - cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should( - "be.visible" - ); - cy.wait(2000); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); cy.appUILogin(); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + verifyWidget("text1"); - // Test public access - // cy.get(commonSelectors.viewerPageLogo).click(); cy.openApp( "appSlug", Cypress.env("workspaceId"), @@ -113,75 +112,39 @@ describe( cy.backToApps(); logout(); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); + verifyWidget("text1"); }); - it("Verify app private and public app visibility for the same workspace user", () => { + it("should verify app private and public app visibility for the same workspace user", () => { setupAppWithSlug(data.appName, data.slug); inviteUserToWorkspace(data.firstName, data.email); logout(); - cy.visit("/"); - cy.wait(2000); - cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should( - "be.visible" - ); - - // Test private access - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); cy.wait(2000); cy.appUILogin(data.email, "password"); + verifyWidget("text1"); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); + verifyWidget("text1"); - // Test with private app valid session - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); - - // cy.get(commonSelectors.viewerPageLogo).click(); - - // Test public access cy.defaultWorkspaceLogin(); - cy.wait(1000); cy.apiMakeAppPublic(); logout(); - cy.wait(1000); - cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should( - "be.visible" - ); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); + verifyWidget("text1"); - // Test with public app with valid session cy.apiLogin(data.email, "password"); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); + verifyWidget("text1"); }); - it("Verify app private and public app visibility for the same instance user", () => { + it("should verify app private and public app visibility for the same instance user", () => { setupAppWithSlug(data.appName, data.slug); cy.apiLogout(); @@ -189,131 +152,77 @@ describe( InstanceSSO(true, true, true); }); userSignUp(data.firstName, data.email, data.workspaceName); - cy.wait(1000); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); cy.visit("/"); logout(); - // Test public access cy.defaultWorkspaceLogin(); cy.apiMakeAppPublic(); logout(); - cy.wait(1000); - cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should( - "be.visible" - ); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); + verifyWidget("text1"); - // Verify public app with valid session cy.apiLogin(data.email, "password"); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }); + verifyWidget("text1"); }); - it("Should redirect to workspace login and handle signup flow of existing and non-existing user", () => { - setSignupStatus(true); - setupAppWithSlug(data.appName, data.slug); - - cy.apiLogout(); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - - cy.get(commonSelectors.workspaceName).verifyVisibleElement( - "have.text", - "My workspace" - ); - - // Test signup flow + it("should redirect to workspace login and handle signup flow of existing and non-existing user", () => { cy.intercept("POST", "/api/onboarding/signup").as("signup"); - cy.get(commonSelectors.createAnAccountLink).click(); - cy.wait(3000); + cy.intercept('GET', '**/api/white-labelling').as('whiteLabelling'); + + cy.apiUserInvite(`${data.firstName}_invited`, `invited_${data.email}`); + + setSignupStatus(true); + setupAppWithSlug(data.appPublicName, data.appPublicSlug, 'public'); + const publicAppId = Cypress.env("appId"); + cy.apiMakeAppPublic(publicAppId); + setupAppWithSlug(data.appPrivateName, data.appPrivateSlug); + cy.visitSlug({ actualUrl: getAppUrl(data.slug) }) + + cy.visitSlug({ actualUrl: getAppUrl(data.appPublicSlug) }); + verifyWidget('public') + + cy.visitSlug({ actualUrl: getAppUrl(data.appPrivateSlug) }); + verifyWidget('private') + + cy.apiLogout(); + + cy.visitSlug({ actualUrl: getAppUrl(data.appPublicSlug) }); + verifyWidget('public') + + cy.visitSlug({ actualUrl: getAppUrl(data.appPrivateSlug) }); + cy.wait('@whiteLabelling'); + + cy.get(commonSelectors.createAnAccountLink, { timeout: 20000 }).click(); + + cy.get(onboardingSelectors.loginPasswordInput).should("be.visible").click({ force: true }); cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName); - cy.clearAndType(commonSelectors.inputFieldEmailAddress, data.email); + cy.clearAndType('[data-cy="email-input"]', data.email); cy.clearAndType(onboardingSelectors.loginPasswordInput, "password"); cy.get(commonSelectors.signUpButton).click(); + cy.wait('@signup'); + cy.get(`[data-cy="resend-verification-email-button"]`).should("be.visible"); - cy.wait("@signup").then((interception) => { - expect(interception.response.statusCode).to.eq(201); - }); + fetchAndVisitInviteLinkViaMH(data.email); + verifyWidget('private') - // Process invitation - onboardUserFromAppLink(data.email, data.slug); + // cy.apiLogout(); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); + // cy.visitSlug({ actualUrl: getAppUrl(data.appPrivateSlug) }) + // cy.get(onboardingSelectors.loginPasswordInput).should("be.visible").click({ force: true }); + // cy.clearAndType('[data-cy="email-input"]', `invited_${data.email}`); + // cy.clearAndType(onboardingSelectors.loginPasswordInput, "password"); - // cy.get('[data-cy="viewer-page-logo"]').click(); - cy.visit("/my-workspace"); - cy.wait(2000); - logout(); - cy.wait(1000); - cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should( - "be.visible" - ); - // Setup new workspace and app - cy.defaultWorkspaceLogin(); - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); - cy.apiLogin(); - cy.visit(`${data.workspaceSlug}`); - setSignupStatus(true, data.workspaceName); - data.slug = fake.firstName.toLowerCase().replace(/\s+/g, "-"); - - cy.createApp(data.appName); - - cy.wait(2000); - cy.dragAndDropWidget("Text", 500, 500); - releaseApp(); - setUpSlug(data.slug); - cy.forceClickOnCanvas(); - cy.backToApps(); - - // Test signup flow in new workspace - cy.apiLogout(); - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); - - cy.get(commonSelectors.workspaceName).verifyVisibleElement( - "have.text", - data.workspaceName - ); - - cy.get(commonSelectors.createAnAccountLink).click(); - cy.wait(3000); - cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName); - cy.clearAndType(commonSelectors.inputFieldEmailAddress, data.email); - cy.clearAndType(onboardingSelectors.loginPasswordInput, "password"); - cy.get(commonSelectors.signUpButton).click(); - cy.wait("@signup").then((interception) => { - expect(interception.response.statusCode).to.eq(201); - }); - - onboardUserFromAppLink(data.email, data.slug, data.workspaceName, false); - cy.get(commonWidgetSelector.draggableWidget("text1")).should( - "be.visible" - ); }); - it("Should verify restricted app access", () => { + it("should verify restricted app access", () => { data.workspaceName = fake.firstName; data.workspaceSlug = fake.firstName.toLowerCase().replace(/\s+/g, "-"); @@ -330,65 +239,12 @@ describe( inviteUserToWorkspace(data.firstName, data.email); - // Verify restricted access - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }); verifyRestrictedAccess(); - cy.get('[data-cy="back-to-home-button"]').click(); + cy.get(commonSelectors.backToHomeButton).click(); cy.get(commonSelectors.homePageLogo).should("be.visible"); cy.apiLogout(); }); - it.skip("Should verify private app access for different workspace users", () => { - const firstName1 = fake.firstName; - const email1 = fake.email.toLowerCase(); - const permissionName = fake.firstName.toLowerCase(); // Defined but not used in original - const urls = { - editor: `${Cypress.config("baseUrl")}/my-workspace/apps/${data.slug}/home`, - preview: `${Cypress.config("baseUrl")}/applications/${data.slug}/home?version=v1`, - released: `${Cypress.config("baseUrl")}/applications/${data.slug}`, - }; - - // Setup workspace and app - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); - cy.apiLogin(); - cy.visit(`${data.workspaceSlug}`); - setupAppWithSlug(data.appName, data.slug); - - // Invite workspace user - addNewUser(data.firstName, data.email); - cy.wait(500); - - // Verify access restrictions - cy.visitSlug({ actualUrl: urls.editor }); - verifyRestrictedAccess(); - cy.get('[data-cy="back-to-home-button"]').click(); - cy.get(commonSelectors.homePageLogo).should("be.visible"); - - cy.visitSlug({ actualUrl: urls.preview }); - - // Switch users and verify access - cy.apiLogout(); - cy.apiLogin(); - cy.apiDeleteGranularPermission("end-user"); - - cy.apiLogin(data.email, "password"); - cy.visitSlug({ actualUrl: urls.editor }); - verifyRestrictedAccess(); - cy.get('[data-cy="back-to-home-button"]').click(); - cy.get(commonSelectors.homePageLogo).should("be.visible"); - cy.visitSlug({ actualUrl: urls.preview }); - - cy.apiLogout(); - - // Test with new user - userSignUp(firstName1, email1, data.workspaceName); - cy.visitSlug({ actualUrl: urls.editor }); - cy.visitSlug({ actualUrl: urls.preview }); - cy.visitSlug({ actualUrl: urls.released }); - }); } ); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/groupDuplication.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/groupDuplication.cy.js deleted file mode 100644 index ce82392019..0000000000 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/groupDuplication.cy.js +++ /dev/null @@ -1,143 +0,0 @@ -import { commonSelectors } from "Selectors/common"; -import { groupsSelector } from "Selectors/manageGroups"; -import { fake } from "Fixtures/fake"; -import { - navigateToManageGroups, - viewAppCardOptions, -} from "Support/utils/common"; -import { - OpenGroupCardOption, - verifyGroupCardOptions, - duplicateMultipleGroups, - createGroupAddAppAndUserToGroup, - groupPermission, -} from "Support/utils/manageGroups"; -import { cyParamName } from "Selectors/common"; -import { roleBasedOnboarding } from "Support/utils/onboarding"; - -const data = {}; -data.groupName = fake.firstName.replaceAll("[^A-Za-z]", ""); -data.appName = `${fake.companyName}-App`; -const workspaceName = fake.firstName; -const workspaceSlug = fake.firstName.toLowerCase().replace(/[^A-Za-z]/g, ""); - -describe("Groups duplication", () => { - beforeEach(() => { - cy.defaultWorkspaceLogin(); - cy.apiCreateWorkspace(workspaceName, workspaceSlug); - cy.visit(`${workspaceSlug}`); - cy.apiLogout(); - cy.apiLogin(); - cy.visit(`${workspaceSlug}`); - groupPermission( - [ - "appsCreateCheck", - "appsDeleteCheck", - "workspaceVarCheckbox", - "foldersCreateCheck", - ], - "Admin" - ); - cy.apiCreateApp(data.appName); - - }); - - it("Should verify the group duplication feature", () => { - data.firstName = fake.firstName; - data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""); - cy.visit(`${workspaceSlug}`); - roleBasedOnboarding(data.firstName, data.email, "builder"); - cy.apiLogout(); - - cy.apiLogin(); - cy.visit(`${workspaceSlug}`); - navigateToManageGroups(); - verifyGroupCardOptions("Admin"); - cy.wait(3000); - cy.get('[datacy="groups-list-option-button"]').click(); - cy.get('[data-cy="delete-group-card-option"] > .col').should( - "have.class", - "disable" - ); - duplicateMultipleGroups(["Admin", "Builder", "End-user"]); - createGroupAddAppAndUserToGroup(data.groupName, data.email); - groupPermission( - [ - "appsCreateCheck", - "appsDeleteCheck", - "workspaceVarCheckbox", - "foldersCreateCheck", - ], - data.groupName, - true - ); - cy.wait(1000); - verifyGroupCardOptions(data.groupName); - cy.get(groupsSelector.duplicateOption).click(); - - cy.get(commonSelectors.defaultModalTitle).verifyVisibleElement( - "have.text", - "Duplicate group" - ); - cy.get(commonSelectors.modalMessage).verifyVisibleElement( - "have.text", - "Duplicate the following parts of the group" - ); - cy.get(groupsSelector.usersCheckInput).should("be.visible"); - cy.verifyLabel("Users"); - cy.get(groupsSelector.permissionCheckInput).should("be.visible"); - cy.verifyLabel("Permissions"); - cy.get(groupsSelector.appsCheckInput).should("be.visible"); - cy.verifyLabel("Apps"); - cy.get(commonSelectors.cancelButton).verifyVisibleElement( - "have.text", - "Cancel" - ); - cy.get(groupsSelector.confimButton).verifyVisibleElement( - "have.text", - "Duplicate" - ); - - cy.get(groupsSelector.confimButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - "Group duplicated successfully!" - ); - - cy.wait(500); - cy.get( - groupsSelector.duplicatedGroupLink(data.groupName) - ).verifyVisibleElement("have.text", `${data.groupName}_copy`); - - OpenGroupCardOption(data.groupName); - cy.get(groupsSelector.deleteGroupOption).click(); - cy.get(commonSelectors.buttonSelector("Yes")).click(); - cy.apiLogout(); - - cy.apiLogin(data.email, "password"); - cy.visit(`${workspaceSlug}`); - cy.wait(2000); - cy.get(commonSelectors.appCreateButton).should("be.visible"); - cy.get(commonSelectors.createNewFolderButton).should("be.visible"); - cy.wait(2000); - cy.reload(); - viewAppCardOptions(data.appName); - cy.contains("Delete app").should("exist"); - cy.get(commonSelectors.workspaceConstantsIcon).should("be.visible"); - cy.apiLogout(); - - cy.apiLogin(); - cy.visit(`${workspaceSlug}`); - navigateToManageGroups(); - OpenGroupCardOption(`${data.groupName}_copy`); - cy.get(groupsSelector.deleteGroupOption).click(); - cy.get(commonSelectors.buttonSelector("Yes")).click(); - cy.apiLogout(); - - cy.apiLogin(data.email, "password"); - cy.visit(`${workspaceSlug}`); - cy.get(commonSelectors.appCreateButton).should("not.exist"); - cy.get(commonSelectors.createNewFolderButton).should("not.exist"); - cy.get(commonSelectors.workspaceConstantsIcon).should("not.exist"); - }); -}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js index 02eb2dc9d4..45135ebbc9 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js @@ -31,21 +31,17 @@ describe("dashboard", () => { folderName: `${fake.companyName.toLowerCase()}-folder`, cloneAppName: `cloned-${fake.companyName}-App`, updatedFolderName: `new-${fake.companyName.toLowerCase()}-folder`, - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""), }; cy.intercept("GET", "/api/library_apps").as("appLibrary"); cy.intercept("DELETE", "/api/folders/*").as("folderDeleted"); - cy.skipWalkthrough(); - cy.apiLogin(); - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); - cy.apiLogin(); - cy.visit(`${data.workspaceSlug}`); }); it("should verify the elements on empty dashboard", () => { + cy.intercept("GET", "/api/apps*", { + fixture: 'intercept/emptyDashboard.json' + }).as("dashboardPage"); + cy.intercept("GET", "/api/metadata", { body: { installed_version: "2.9.2", @@ -53,13 +49,13 @@ describe("dashboard", () => { }, }).as("version"); - cy.get(commonSelectors.homePageLogo).should("be.visible"); + cy.visit("/"); + cy.get(commonSelectors.workspaceName).verifyVisibleElement( "have.text", - data.workspaceName + "My workspace" ); - cy.get(commonSelectors.workspaceName).click(); - // cy.get(commonSelectors.editRectangleIcon).should("be.visible"); + cy.get(commonSelectors.appCreateButton).verifyVisibleElement( "have.text", "Create an app" @@ -105,6 +101,12 @@ describe("dashboard", () => { cy.wait(500); cy.get(commonSelectors.settingsIcon).click(); + + cy.get(dashboardSelector.versionLabel).verifyVisibleElement( + "have.text", + "Version 2.9.2" + ); + cy.get(commonSelectors.marketplaceOption).verifyVisibleElement( "have.text", "Marketplace" @@ -122,7 +124,7 @@ describe("dashboard", () => { commonText.logoutLink ); - cy.get(commonSelectors.breadcrumbTitle).should(($el) => { + cy.get(commonSelectors.breadcrumbHeaderTitle).should(($el) => { expect($el.contents().first().text().trim()).to.eq( commonText.breadcrumbApplications ); @@ -132,10 +134,6 @@ describe("dashboard", () => { dashboardText.dashboardAppsHeaderLabel ); - cy.get(dashboardSelector.versionLabel).verifyVisibleElement( - "have.text", - "Version 2.9.2" - ); cy.get(dashboardSelector.emptyPageImage).should("be.visible"); cy.get(dashboardSelector.emptyPageHeader).verifyVisibleElement( "have.text", @@ -158,6 +156,12 @@ describe("dashboard", () => { cy.get(dashboardSelector.appTemplateRow).should("be.visible"); cy.reload(); + const env = Cypress.env("environment"); + if (env === "Enterprise" || env === "Cloud") { + cy.get(commonSelectors.homePageLogo).should("be.visible"); + verifyTooltip(commonSelectors.homePageIcon, "Home"); + verifyTooltip(commonSelectors.globalWorkFlowsIcon, "Workflows"); + }; verifyTooltip(commonSelectors.dashboardIcon, "Apps"); verifyTooltip(commonSelectors.databaseIcon, "ToolJet Database"); verifyTooltip(commonSelectors.globalDataSourceIcon, "Data sources"); @@ -171,17 +175,17 @@ describe("dashboard", () => { it("Should verify app card elements and app card operations", () => { cy.exec("mkdir -p ./cypress/downloads/"); - cy.exec("cd ./cypress/downloads/ && rm -rf *"); + cy.exec("cd ./cypress/downloads/ && rm -rf '*'"); const customLayout = { desktop: { top: 100, left: 20 }, mobile: { width: 8, height: 50 }, }; - + cy.visit("/"); cy.apiCreateApp(data.appName); - cy.visit(`${data.workspaceSlug}`); - cy.wait(2000); + // cy.ifEnv(["Enterprise", "Cloud"], () => { cy.get('.basic-plan-migration-banner').invoke('css', 'display', 'none') }); + // cy.wait(2000); cy.get(commonSelectors.appCreationDetails).should("be.visible"); cy.get(commonSelectors.appCard(data.appName)).should("be.visible"); cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement( @@ -189,6 +193,7 @@ describe("dashboard", () => { data.appName ); + viewAppCardOptions(data.appName); cy.get( commonSelectors.appCardOptions(commonText.changeIconOption) @@ -205,7 +210,6 @@ describe("dashboard", () => { cy.get( commonSelectors.appCardOptions(commonText.deleteAppOption) ).verifyVisibleElement("have.text", commonText.deleteAppOption); - modifyAndVerifyAppCardIcon(data.appName); createFolder(data.folderName); @@ -261,12 +265,15 @@ describe("dashboard", () => { commonText.appRemovedFromFolderTaost, false ); + cy.get(commonSelectors.modalComponent).should("not.exist"); cy.get(commonSelectors.empytyFolderImage).should("be.visible"); + cy.wait(1000); cy.get(commonSelectors.emptyFolderText).verifyVisibleElement( "have.text", commonText.emptyFolderText ); + cy.get(commonSelectors.allApplicationsLink).click(); deleteFolder(data.folderName); @@ -277,6 +284,7 @@ describe("dashboard", () => { cy.wait(2000); cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click(); cy.get(commonSelectors.exportAllButton).click(); + cy.wait(2000) cy.exec("ls ./cypress/downloads/").then((result) => { @@ -292,9 +300,12 @@ describe("dashboard", () => { .and("have.text", dashboardText.appClonedToast); cy.wait(3000); + cy.get(commonSelectors.editorAppNameInput).click(); cy.renameApp(data.cloneAppName); cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25); + cy.backToApps(); + cy.ifEnv(["Enterprise", "Cloud"], () => { cy.get('.basic-plan-migration-banner').invoke('css', 'display', 'none') }); cy.wait("@appLibrary"); cy.wait(1000); @@ -303,6 +314,7 @@ describe("dashboard", () => { cy.wait(1000); viewAppCardOptions(data.cloneAppName); + cy.get(commonSelectors.deleteAppOption).click(); cy.get(commonSelectors.modalMessage).verifyVisibleElement( "have.text", @@ -337,7 +349,7 @@ describe("dashboard", () => { desktop: { top: 100, left: 20 }, mobile: { width: 8, height: 50 }, }; - + cy.visit("/"); cy.createApp(data.appName); cy.apiAddComponentToApp(data.appName, "text1", customLayout); @@ -349,11 +361,11 @@ describe("dashboard", () => { ); navigateToAppEditor(data.appName); - // cy.get(commonSelectors.canvas).should("contain", "text1"); cy.get(".text-widget-section > div").should("be.visible"); cy.backToApps(); cy.wait("@appLibrary"); - + cy.ifEnv(["Enterprise", "Cloud"], () => { cy.get('.basic-plan-migration-banner').invoke('css', 'display', 'none') }); + cy.wait(2000); cy.deleteApp(data.appName); cy.get(commonSelectors.appCard(data.appName)).should("not.exist"); @@ -364,7 +376,7 @@ describe("dashboard", () => { desktop: { top: 100, left: 20 }, mobile: { width: 8, height: 50 }, }; - + cy.visit("/"); cy.createApp(data.appName); cy.apiAddComponentToApp(data.appName, "text1", customLayout); cy.backToApps(); @@ -473,7 +485,8 @@ describe("dashboard", () => { cy.get(dashboardSelector.folderName(data.updatedFolderName)).should( "not.exist" ); - + cy.ifEnv(["Enterprise", "Cloud"], () => { cy.get('.basic-plan-migration-banner').invoke('css', 'display', 'none') }); + cy.wait(2000); cy.get(commonSelectors.allApplicationsLink).click(); cy.deleteApp(data.appName); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js index 782f8543cb..0b715ea8a4 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js @@ -1,10 +1,7 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { groupsSelector } from "Selectors/manageGroups"; -import { workspaceConstantsSelectors } from "Selectors/workspaceConstants"; import { - createFolder, - deleteFolder, navigateToManageGroups, selectAppCardOption, } from "Support/utils/common"; @@ -12,15 +9,25 @@ import { createGroupsAndAddUserInGroup, setupWorkspaceAndInviteUser, updateRole, +} from "Support/utils/manageGroups"; +import { + uiAppCRUDWorkflow, + uiDataSourceCRUDWorkflow, + uiFolderCRUDWorkflow, + uiVerifyAdminPrivileges, + uiVerifyBuilderPrivileges, + uiWorkflowCRUDWorkflow, + uiWorkspaceConstantCRUDWorkflow, +} from "Support/utils/uiPermissions"; +import { verifyBasicPermissions, verifySettingsAccess, -} from "Support/utils/manageGroups"; -import { addAndVerifyConstants } from "Support/utils/workspaceConstants"; +} from "Support/utils/userPermissions"; import { commonText } from "Texts/common"; import { dashboardText } from "Texts/dashboard"; import { groupsText } from "Texts/manageGroups"; -describe("Manage Groups", () => { +describe("Basic Permissions", () => { let data = {}; before(() => { @@ -52,9 +59,98 @@ describe("Manage Groups", () => { ); verifyBasicPermissions(false); + verifySettingsAccess(false); }); - it("should verify builder privileges and role updates in custom groups", () => { + it("should verify builder privileges", () => { + setupWorkspaceAndInviteUser( + data.workspaceName, + data.workspaceSlug, + data.firstName, + data.email, + "builder" + ); + + // UI-based privilege verification for Builder + cy.get(".basic-plan-migration-banner").invoke("css", "display", "none"); + uiVerifyBuilderPrivileges(); + + // UI CRUD workflows validation + cy.get(commonSelectors.dashboardIcon).click(); + const uiTestAppName = `${data.appName}_ui`; + const uiTestFolderName = `${data.folderName}-ui`; + const uiTestConstName = `${data.firstName}_const`; + const uiTestConstValue = "test_value"; + + // Perform UI-based CRUD operations + uiAppCRUDWorkflow(uiTestAppName); + uiFolderCRUDWorkflow(uiTestFolderName); + uiWorkspaceConstantCRUDWorkflow(uiTestConstName, uiTestConstValue); + + // Enterprise-specific UI workflows + cy.ifEnv("Enterprise", () => { + const uiTestDsName = `${data.appName}_ds`; + const uiTestWorkflowName = `${data.appName}_wf`; + uiDataSourceCRUDWorkflow(uiTestDsName, "restapi"); + uiWorkflowCRUDWorkflow(uiTestWorkflowName); + }); + + cy.get(commonSelectors.dashboardIcon).click(); + cy.apiCreateApp(data.appName); + cy.openApp(); + cy.releaseApp(); + + //verify clone access + cy.visit(data.workspaceSlug); + selectAppCardOption( + data.appName, + commonSelectors.appCardOptions(commonText.cloneAppOption) + ); + cy.get(commonSelectors.cloneAppButton).click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + dashboardText.appClonedToast, + false + ); + }); + + it("should verify admin privileges", () => { + setupWorkspaceAndInviteUser( + data.workspaceName, + data.workspaceSlug, + data.firstName, + data.email, + "admin" + ); + + // API-based verification + verifyBasicPermissions(true); + + // UI-based privilege verification for Admin (includes settings access) + uiVerifyAdminPrivileges(); + + // UI CRUD workflows for validation + cy.get(commonSelectors.dashboardIcon).click(); + const uiTestAppName = `${data.appName}_admin_ui`; + const uiTestFolderName = `${data.folderName}-admin-ui`; + const uiTestConstName = `${data.firstName}_admin_const`; + const uiTestConstValue = "admin_test_value"; + + // Perform UI-based CRUD operations + uiAppCRUDWorkflow(uiTestAppName); + uiFolderCRUDWorkflow(uiTestFolderName); + uiWorkspaceConstantCRUDWorkflow(uiTestConstName, uiTestConstValue); + + // Enterprise-specific UI workflows + cy.ifEnv("Enterprise", () => { + const uiTestDsName = `${data.appName}_admin_ds`; + const uiTestWorkflowName = `${data.appName}_admin_wf`; + uiDataSourceCRUDWorkflow(uiTestDsName, "restapi"); + uiWorkflowCRUDWorkflow(uiTestWorkflowName); + }); + }); + + it("should verify role updates in custom groups", () => { const builderGroup = fake.firstName.replace(/[^A-Za-z]/g, ""); const endUserGroup = fake.firstName.replace(/[^A-Za-z]/g, ""); @@ -66,47 +162,10 @@ describe("Manage Groups", () => { "builder" ); - // Verify builder permissions - verifyBasicPermissions(true); - - // App operations - cy.apiCreateApp(data.appName); - cy.apiDeleteApp(); - - // Folder operations - cy.apiCreateFolder(data.folderName); - cy.apiDeleteFolder(); - - // Constants management - cy.get(commonSelectors.workspaceConstantsIcon).click(); - addAndVerifyConstants(data.firstName, data.appName); - cy.get( - workspaceConstantsSelectors.constDeleteButton(data.firstName) - ).click(); - cy.get(commonSelectors.yesButton).click(); - - verifySettingsAccess(false); - - cy.get(commonSelectors.dashboardIcon).click(); - cy.apiCreateApp(data.appName); - - //verify clone access - cy.reload(); - selectAppCardOption( - data.appName, - commonSelectors.appCardOptions(commonText.cloneAppOption) - ); - cy.get(commonSelectors.cloneAppButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - dashboardText.appClonedToast, - false - ); - // cy.get(commonSelectors.cancelButton).click(); cy.apiLogout(); - cy.apiLogin(); cy.visit(data.workspaceSlug); + cy.apiCreateApp(`${data.appName}_builder`); navigateToManageGroups(); [builderGroup, endUserGroup].forEach((group) => { @@ -140,43 +199,10 @@ describe("Manage Groups", () => { cy.apiLogout(); cy.apiLogin(data.email, "password"); cy.visit(data.workspaceSlug); - cy.get(commonSelectors.appCard(data.appName)) + cy.get(commonSelectors.appCard(`${data.appName}_builder`)) .trigger("mouseover") .trigger("mouseenter") .find(commonSelectors.editButton) .should("not.exist"); }); - - it("should verify admin privileges", () => { - setupWorkspaceAndInviteUser( - data.workspaceName, - data.workspaceSlug, - data.firstName, - data.email, - "admin" - ); - - verifyBasicPermissions(true); - - // App operations - cy.apiCreateApp(data.appName); - cy.apiDeleteApp(); - - // Folder operations - cy.apiCreateFolder(data.folderName); - cy.apiDeleteFolder(); - - // Constants management - cy.get(commonSelectors.workspaceConstantsIcon).click(); - addAndVerifyConstants(data.firstName, data.appName); - cy.get( - workspaceConstantsSelectors.constDeleteButton(data.firstName) - ).click(); - cy.get(commonSelectors.yesButton).click(); - - // Settings access check - explicitly verify workspace settings - cy.get(commonSelectors.settingsIcon).click(); - cy.get(commonSelectors.workspaceSettings).should("exist"); - cy.wait(1000); - }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupGranularAccess.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupGranularAccess.cy.js new file mode 100644 index 0000000000..02fc2cae58 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupGranularAccess.cy.js @@ -0,0 +1,227 @@ +import { fake } from "Fixtures/fake"; +import { commonSelectors } from "Selectors/common"; +import { dataSourceSelector } from "Selectors/dataSource"; +import { groupsSelector } from "Selectors/manageGroups"; +import { navigateToManageGroups } from "Support/utils/common"; +import { + createGroupsAndAddUserInGroup, + setupWorkspaceAndInviteUser, +} from "Support/utils/manageGroups"; +import { getGroupPermissionInput } from "Support/utils/userPermissions"; +import { groupsText } from "Texts/manageGroups"; + +describe("Custom Group Granular Access", () => { + let data = {}; + const isEnterprise = Cypress.env("environment") === "Enterprise"; + + before(() => { + cy.exec("mkdir -p ./cypress/downloads/"); + cy.wait(3000); + }); + + beforeEach(() => { + data = { + firstName: fake.firstName, + appName: fake.companyName, + email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + workspaceName: fake.lastName.toLowerCase().replace(/[^A-Za-z]/g, ""), + workspaceSlug: fake.lastName.toLowerCase().replace(/[^A-Za-z]/g, ""), + folderName: fake.companyName, + dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), + }; + + cy.defaultWorkspaceLogin(); + cy.intercept("DELETE", "/api/folders/*").as("folderDeleted"); + cy.skipWalkthrough(); + cy.viewport(2400, 2000); + }); + + it("should verify the granular permissions in custom groups", () => { + const groupName = fake.firstName.replace(/[^A-Za-z]/g, ""); + const appName2 = fake.companyName; + const appName3 = fake.companyName; + const appSlug = appName3.toLowerCase().replace(/\s+/g, "-"); + const workflowName1 = `${data.appName}-workflow`; + const workflowName2 = `${appName2}-workflow`; + const datasourceName1 = `${data.appName}-datasource`; + const datasourceName2 = `${appName2}-datasource`; + + setupWorkspaceAndInviteUser( + data.workspaceName, + data.workspaceSlug, + data.firstName, + data.email, + "builder" + ); + + cy.apiLogin(); + cy.visit(data.workspaceSlug); + cy.apiUpdateGroupPermission( + "builder", + getGroupPermissionInput(isEnterprise, false) + ); + + cy.apiDeleteGranularPermission("builder", []); + + navigateToManageGroups(); + createGroupsAndAddUserInGroup(groupName, data.email); + + cy.apiCreateApp(data.appName); + cy.apiCreateApp(appName2); + + // App Hide from dashboard + cy.apiCreateApp(appName3); + cy.openApp(); + cy.apiAddComponentToApp(appName3, "text1"); + cy.apiPromoteAppVersion().then(() => { + const stagingId = Cypress.env("stagingEnvId"); + cy.apiPromoteAppVersion(Cypress.env("stagingEnvId")); + }); + cy.apiReleaseApp(appName3); + cy.apiAddAppSlug(appName3, appSlug); + cy.go("back"); + + // Configure app permissions + navigateToManageGroups(); + cy.get(groupsSelector.groupLink(groupName)).click(); + cy.get(groupsSelector.granularLink).click(); + + // Setup permissions for both apps + [data.appName, appName2, appName3].forEach((app) => { + cy.ifEnv("Community", () => { + cy.get(groupsSelector.addAppsButton).click(); + }); + cy.ifEnv("Enterprise", () => { + cy.get(groupsSelector.addPermissionButton).click(); + cy.get(groupsSelector.addAppButton).click(); + }); + + cy.clearAndType(groupsSelector.permissionNameInput, app); + cy.get(groupsSelector.customRadio).check(); + cy.get(".css-1gfides").click({ force: true }).type(`${app}{enter}`); + cy.get(groupsSelector.confimButton).click({ force: true }); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.createPermissionToast + ); + }); + + cy.get(groupsSelector.groupChip).contains(data.appName).click(); + cy.get(groupsSelector.editPermissionRadio).click(); + cy.get(groupsSelector.confimButton).click(); + + //To hide app + cy.get(groupsSelector.groupChip).contains(appName3).click(); + cy.get(groupsSelector.hidePermissionInput).check(); + cy.get(groupsSelector.confimButton).click(); + + cy.ifEnv("Enterprise", () => { + cy.apiCreateWorkflow(workflowName1); + cy.apiCreateWorkflow(workflowName2); + + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + datasourceName1, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); + + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + datasourceName2, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); + + cy.get(groupsSelector.groupLink("Builder")).click(); + cy.get(groupsSelector.groupLink(groupName)).click(); + cy.get(groupsSelector.granularLink).click(); + + [workflowName1, workflowName2].forEach((workflow) => { + cy.get(groupsSelector.addPermissionButton).click(); + cy.get(groupsSelector.addWorkflowButton).click(); + + cy.clearAndType(groupsSelector.permissionNameInput, workflow); + cy.get(groupsSelector.customRadio).check(); + cy.get(".css-1gfides") + .click({ force: true }) + .type(`${workflow}{enter}`); + cy.get(groupsSelector.confimButton).click({ force: true }); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.createPermissionToast + ); + }); + cy.get(groupsSelector.groupChip).contains(workflowName1).click(); + cy.get(groupsSelector.buildWorkflowradio).click(); + cy.get(groupsSelector.confimButton).click(); + + [datasourceName1, datasourceName2].forEach((datasource) => { + cy.get(groupsSelector.addPermissionButton).click(); + cy.get(groupsSelector.addDatasourceButton).click(); + + cy.clearAndType(groupsSelector.permissionNameInput, datasource); + cy.get(groupsSelector.customRadio).check(); + cy.get(".css-1gfides") + .click({ force: true }) + .type(`${datasource}{enter}`); + cy.get(groupsSelector.confimButton).click({ force: true }); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.createPermissionToast + ); + }); + cy.get(groupsSelector.groupChip).contains(datasourceName1).click(); + cy.get(groupsSelector.configureDatasourceradio).click(); + cy.get(groupsSelector.confimButton).click(); + }); + + // Verify as builder + cy.wait(1000); + cy.apiLogout(); + cy.apiLogin(data.email); + cy.visit(data.workspaceSlug); + + cy.get(commonSelectors.dashboardIcon).click(); + cy.get(commonSelectors.appCreateButton).should("not.exist"); + cy.get('.appcard-buttons-wrap [data-cy="edit-button"]').should( + "have.lengthOf", + 1 + ); + cy.get('.appcard-buttons-wrap [data-cy="launch-button"]').should( + "have.lengthOf", + 2 + ); + + cy.ifEnv("Enterprise", () => { + cy.get(commonSelectors.globalWorkFlowsIcon).click(); + cy.get('[data-cy="create-new-workflows-button"]').should("not.exist"); + cy.get('.appcard-buttons-wrap [data-cy="edit-button"]').should( + "have.lengthOf", + 1 + ); + cy.get('.appcard-buttons-wrap [data-cy="launch-button"]').should( + "have.lengthOf", + 2 + ); + + cy.get(commonSelectors.globalDataSourceIcon).click(); + cy.get( + dataSourceSelector.dataSourceNameButton(datasourceName1.toLowerCase()) + ).click(); + cy.get(dataSourceSelector.dsNameInputField).should("be.enabled"); + cy.get( + dataSourceSelector.dataSourceNameButton(datasourceName2.toLowerCase()) + ).click(); + cy.get(dataSourceSelector.dsNameInputField).should("be.disabled"); + + cy.get(dataSourceSelector.commonDsLabelAndCount).click(); + cy.get('[data-cy="rest-api-add-button"]').should("be.disabled"); + }); + + //Visit hidden app url + cy.visitSlug({ + actualUrl: `${Cypress.config("baseUrl")}/applications/${appSlug}`, + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupPermissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupPermissions.cy.js new file mode 100644 index 0000000000..021f212641 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupPermissions.cy.js @@ -0,0 +1,206 @@ +import { fake } from "Fixtures/fake"; +import { commonSelectors } from "Selectors/common"; +import { dataSourceSelector } from "Selectors/dataSource"; +import { groupsSelector } from "Selectors/manageGroups"; +import { navigateToManageGroups } from "Support/utils/common"; +import { + createGroupsAndAddUserInGroup, + setupWorkspaceAndInviteUser, +} from "Support/utils/manageGroups"; +import { + getGroupPermissionInput, + verifyBuilderPermissions, +} from "Support/utils/userPermissions"; +import { groupsText } from "Texts/manageGroups"; + +describe("Custom Group Permissions", () => { + let data = {}; + const isEnterprise = Cypress.env("environment") === "Enterprise"; + + before(() => { + cy.exec("mkdir -p ./cypress/downloads/"); + cy.wait(3000); + }); + + beforeEach(() => { + data = { + firstName: fake.firstName, + appName: fake.companyName, + email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + workspaceName: fake.lastName.toLowerCase().replace(/[^A-Za-z]/g, ""), + workspaceSlug: fake.lastName.toLowerCase().replace(/[^A-Za-z]/g, ""), + folderName: fake.companyName, + dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), + }; + + cy.defaultWorkspaceLogin(); + cy.intercept("DELETE", "/api/folders/*").as("folderDeleted"); + cy.skipWalkthrough(); + cy.viewport(2400, 2000); + }); + + it("should verify user permissions in custom groups", () => { + const groupName = fake.firstName.replace(/[^A-Za-z]/g, ""); + const workflowName1 = fake.companyName; + const datasourceName1 = fake.companyName + .toLowerCase() + .replace(/[^A-Za-z]/g, ""); + + setupWorkspaceAndInviteUser( + data.workspaceName, + data.workspaceSlug, + data.firstName, + data.email + ); + cy.apiLogout(); + + // Setup custom group + cy.apiLogin(); + cy.visit(data.workspaceSlug); + cy.get(".basic-plan-migration-banner").invoke("css", "display", "none"); + + cy.apiUpdateGroupPermission( + "builder", + getGroupPermissionInput(isEnterprise, false) + ); + + cy.visit(data.workspaceSlug); + navigateToManageGroups(); + + createGroupsAndAddUserInGroup(groupName, data.email); + + // Permission configuration and verification + cy.get(groupsSelector.permissionsLink).click(); + + // App creation permission + cy.get(groupsSelector.appsCreateCheck).check(); + cy.get(commonSelectors.defaultModalTitle).contains( + groupsText.changeUserRoleHeader + ); + cy.get(groupsSelector.changeRoleModalMessage).contains( + groupsText.changeUserRoleMessage + ); + cy.get(".item-list").contains(data.email); + cy.get(groupsSelector.confimButton).should( + "have.text", + groupsText.continueButtonText + ); + cy.get(commonSelectors.cancelButton).click(); + + // Other permissions + const permissions = + Cypress.env("environment") === "Community" + ? [ + groupsSelector.appsDeleteCheck, + groupsSelector.foldersCreateCheck, + groupsSelector.workspaceVarCheckbox, + ] + : [ + groupsSelector.appsDeleteCheck, + groupsSelector.appPromoteCheck, + groupsSelector.appReleaseCheck, + groupsSelector.workflowsCreateCheck, + groupsSelector.workflowsDeleteCheck, + groupsSelector.datasourcesCreateCheck, + groupsSelector.datasourcesDeleteCheck, + groupsSelector.foldersCreateCheck, + groupsSelector.workspaceVarCheckbox, + ]; + + permissions.forEach((permission) => { + cy.get(permission).check(); + cy.get(".modal-content").should("be.visible"); + cy.get(commonSelectors.cancelButton).click(); + }); + + // Granular permissions + cy.get(groupsSelector.granularLink).click(); + + cy.ifEnv("Community", () => { + cy.get(groupsSelector.addAppsButton).click(); + }); + cy.ifEnv("Enterprise", () => { + cy.get(groupsSelector.addPermissionButton).click(); + cy.get(groupsSelector.addAppButton).click(); + }); + + cy.clearAndType(groupsSelector.permissionNameInput, data.firstName); + cy.get(groupsSelector.editPermissionRadio).click(); + cy.get(groupsSelector.confimButton).click(); + + // Verify modal + cy.get(".modal-content").should("be.visible"); + cy.get(groupsSelector.modalHeader).should( + "have.text", + groupsText.cantCreatePermissionModalHeader + ); + cy.get(groupsSelector.modalMessage).should( + "have.text", + groupsText.cantCreatePermissionModalMessage + ); + + cy.get(".item-list").contains(data.email); + cy.get(commonSelectors.closeButton).click(); + + // Role transition + cy.get(groupsSelector.permissionsLink).click(); + cy.get(groupsSelector.appsCreateCheck).check(); + cy.get(groupsSelector.confimButton).click(); + permissions.forEach((permission) => { + cy.get(permission).check(); + }); + cy.get(groupsSelector.groupLink("Builder")).click(); + cy.get(groupsSelector.usersLink).click(); + cy.get(`[data-cy="${data.email}-user-row"]`).should("be.visible"); + cy.apiLogout(); + cy.apiLogin(data.email); + cy.visit(data.workspaceSlug); + + verifyBuilderPermissions( + data.appName, + data.folderName, + data.firstName, + data.appName + ); + + cy.apiLogout(); + + cy.apiLogin(); + cy.visit(data.workspaceSlug); + + // Reset permissions + cy.apiDeleteGranularPermission("builder", []); + cy.apiDeleteGranularPermission(groupName, []); + cy.apiCreateApp(data.appName); + + cy.ifEnv("Enterprise", () => { + cy.apiCreateWorkflow(workflowName1); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + datasourceName1, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); + }); + + cy.apiLogout(); + cy.apiLogin(data.email); + cy.visit(data.workspaceSlug); + + cy.get(commonSelectors.appCreateButton).should("be.enabled"); + cy.get(commonSelectors.appCard(data.appName)).should("not.exist"); + + cy.ifEnv("Enterprise", () => { + cy.get(commonSelectors.globalWorkFlowsIcon).click(); + cy.get('[data-cy="create-new-workflows-button"]').should("exist"); + cy.get(commonSelectors.appCard(workflowName1)).should("not.exist"); + + cy.get(commonSelectors.globalDataSourceIcon).click(); + cy.get(dataSourceSelector.dataSourceNameButton(datasourceName1)).should( + "exist" + ); + cy.get(dataSourceSelector.commonDsLabelAndCount).click(); + cy.get('[data-cy="rest-api-add-button"]').should("be.enabled"); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupUI.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupUI.cy.js new file mode 100644 index 0000000000..c9ee0e6498 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/customGroupUI.cy.js @@ -0,0 +1,273 @@ +import { fake } from "Fixtures/fake"; + +import { commonSelectors } from "Selectors/common"; +import { commonEeSelectors } from "Selectors/eeCommon"; +import { groupsSelector } from "Selectors/manageGroups"; +import { navigateToManageGroups } from "Support/utils/common"; +import { + apiAddUserToGroup, + apiCreateGroup, + apiDeleteGroup, +} from "Support/utils/manageGroups"; +import { + addGranularPermissionViaUI, + createGroupViaUI, + deleteGroupViaUI, + openGroupThreeDotMenu, + renameGroupViaUI, + switchBetweenAllAndCustom, + verifyDuplicateModal, + verifyGroupCreatedInSidebar, + verifyGroupRemovedFromSidebar, +} from "Support/utils/platform/customGroups"; + +import { + verifyCheckPermissionStates, + verifyEmptyStates, + verifyGranularAccessByRole, + verifyGranularEditModal, + verifyGranularPermissionModalStates, + verifyGroupLinks, + verifyPermissionCheckBoxLabelsAndHelperTexts, + verifyUserRow, +} from "Support/utils/platform/groupsUI"; + +import { getGroupPermissionInput } from "Support/utils/userPermissions"; + +describe("Custom groups UI and Functionality verification", () => { + const isEnterprise = Cypress.env("environment") === "Enterprise"; + + const groupName = fake.firstName.replaceAll("[^A-Za-z]", ""); + const newGroupname = `New ${groupName}`; + const groupName2 = `${fake.firstName.replaceAll("[^A-Za-z]", "")}2`; + const groupName3 = `${fake.firstName.replaceAll("[^A-Za-z]", "")}3`; + const duplicatedGroupName = `${groupName3}_copy`; + const appPermissionName = "Apps"; + const workflowPermissionName = "Workflows"; + const datasourcePermissionName = " Data sources"; + const permissionName2 = "Permission2"; + const data = { + firstName: fake.firstName, + email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + workspaceName: fake.firstName, + workspaceSlug: fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""), + }; + let groupId3; + + before(() => { + cy.apiLogin(); + cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); + }); + + beforeEach(() => { + cy.apiLogin(); + cy.visit(`${data.workspaceSlug}`); + cy.viewport(2000, 1900); + }); + + it("should create, rename, and delete a custom group", () => { + navigateToManageGroups(); + createGroupViaUI(groupName); + + verifyGroupCreatedInSidebar(groupName); + + renameGroupViaUI(groupName, newGroupname); + + verifyGroupCreatedInSidebar(newGroupname); + + deleteGroupViaUI(newGroupname); + + verifyGroupRemovedFromSidebar(newGroupname); + }); + + it("should create custom group, verify empty states, add permissions, and manage granular access", () => { + apiCreateGroup(groupName2); + + cy.apiCreateApp(groupName); + cy.ifEnv("Enterprise", () => { + cy.apiCreateWorkflow(groupName); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + groupName, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); + }); + + navigateToManageGroups(); + cy.get(groupsSelector.groupLink(groupName2)).click(); + + verifyGroupLinks(); + verifyEmptyStates(true); + + cy.get(groupsSelector.permissionsLink).click(); + verifyCheckPermissionStates("custom"); + verifyPermissionCheckBoxLabelsAndHelperTexts(); + + cy.get(groupsSelector.granularLink).click(); + addGranularPermissionViaUI(appPermissionName, { + resourceType: "app", + permission: "edit", + scope: "all", + }); + + cy.ifEnv("Enterprise", () => { + addGranularPermissionViaUI(workflowPermissionName, { + resourceType: "workflow", + permission: "build", + scope: "all", + }); + + addGranularPermissionViaUI(datasourcePermissionName, { + resourceType: "datasource", + permission: "configure", + scope: "all", + }); + }); + verifyGranularAccessByRole("builder"); + verifyGranularEditModal("custom"); + + cy.get('[data-cy="apps-granular-access"]').realHover(); + cy.get('[data-cy="edit-apps-granular-access"]').click(); + + verifyGranularPermissionModalStates("app", "custom"); + + cy.get(groupsSelector.viewPermissionRadio).check(); + switchBetweenAllAndCustom("custom"); + + verifyGranularPermissionModalStates("app", "custom", { + editRadio: { checked: false, enabled: true }, + viewRadio: { checked: true, enabled: true }, + hideCheckbox: { enabled: true }, + allAppsRadio: { checked: false, enabled: true }, + customRadio: { checked: true, enabled: true }, + }); + + switchBetweenAllAndCustom("all"); + verifyGranularPermissionModalStates("app", "custom", { + editRadio: { checked: false, enabled: true }, + viewRadio: { checked: true, enabled: true }, + hideCheckbox: { enabled: true }, + allAppsRadio: { checked: true, enabled: true }, + customRadio: { checked: false, enabled: true }, + }); + + cy.get(groupsSelector.editPermissionRadio).check(); + verifyGranularPermissionModalStates("app", "custom", { + editRadio: { checked: true, enabled: true }, + viewRadio: { checked: false, enabled: true }, + hideCheckbox: { enabled: false }, + allAppsRadio: { checked: true, enabled: true }, + customRadio: { checked: false, enabled: true }, + }); + + cy.get(groupsSelector.cancelButton).click(); + + cy.ifEnv("Enterprise", () => { + cy.get('[data-cy="workflow-granular-access"]').realHover(); + cy.get('[data-cy="edit-workflow-granular-access"]').click(); + + verifyGranularPermissionModalStates("workflow", "custom"); + + cy.get(groupsSelector.executeWorkflowradio).check(); + verifyGranularPermissionModalStates("workflow", "custom", { + buildRadio: { checked: false, enabled: true }, + executeRadio: { checked: true, enabled: true }, + }); + cy.get(groupsSelector.cancelButton).click(); + + cy.get('[data-cy="datasource-granular-access"]').realHover(); + cy.get('[data-cy="edit-datasource-granular-access"]').click(); + verifyGranularPermissionModalStates("datasource", "custom"); + + cy.get(groupsSelector.buildWithDatasourceRadio).check(); + verifyGranularPermissionModalStates("datasource", "custom", { + configureRadio: { checked: false, enabled: true }, + buildWithRadio: { checked: true, enabled: true }, + }); + cy.get(groupsSelector.cancelButton).click(); + }); + + apiDeleteGroup(groupName2); + }); + + it("should create group via API, add permissions, duplicate group and verify all permissions are copied", () => { + cy.apiFullUserOnboarding(data.firstName, data.email, "builder"); + cy.apiLogout(); + + cy.apiLogin(); + cy.apiCreateApp(groupName3); + + cy.ifEnv("Enterprise", () => { + cy.apiCreateWorkflow(groupName3); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + groupName3, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); + }); + + apiCreateGroup(groupName3).then((groupId) => { + groupId3 = groupId; + apiAddUserToGroup(groupId3, data.email); + cy.apiCreateGranularPermission( + groupName3, + "Apps", + "app", + { canEdit: true, canView: false, hideFromDashboard: false }, + [] + ); + + cy.ifEnv("Enterprise", () => { + cy.apiCreateGranularPermission( + groupName3, + "Workflows", + "workflow", + { canEdit: true, canView: false, hideFromDashboard: false }, + [] + ); + cy.apiCreateGranularPermission( + groupName3, + "Data sources", + "datasource", + { canUse: false, canConfigure: true }, + [] + ); + }); + }); + + cy.apiUpdateGroupPermission( + groupName3, + getGroupPermissionInput(isEnterprise, true) + ); + + navigateToManageGroups(); + cy.wait(2000); + openGroupThreeDotMenu(groupName3); + cy.get(groupsSelector.duplicateOption).click(); + verifyDuplicateModal(groupName3); + + cy.get(groupsSelector.cancelButton).click(); + + openGroupThreeDotMenu(groupName3); + cy.get(groupsSelector.duplicateOption).click(); + cy.get(commonEeSelectors.confirmButton).click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + "Group duplicated successfully" + ); + + cy.wait(1000); + verifyUserRow(data.firstName, ` ${data.email}`); + cy.get(groupsSelector.groupLink(duplicatedGroupName)).click(); + cy.get(groupsSelector.permissionsLink).click(); + verifyCheckPermissionStates("builder"); + + cy.get(groupsSelector.granularLink).click(); + verifyGranularAccessByRole("builder"); + + apiDeleteGroup(duplicatedGroupName); + apiDeleteGroup(groupName3); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js deleted file mode 100644 index a22290df5d..0000000000 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js +++ /dev/null @@ -1,264 +0,0 @@ -import { fake } from "Fixtures/fake"; -import { commonSelectors } from "Selectors/common"; -import { groupsSelector } from "Selectors/manageGroups"; -import * as common from "Support/utils/common"; -import * as groups from "Support/utils/manageGroups"; -import { groupsText } from "Texts/manageGroups"; - -const verifyModalFields = (isEdit = false, groupName = '') => { - // Modal header verification - cy.get(groupsSelector.permissionNameLabel).verifyVisibleElement( - "have.text", - groupsText.permissionNameLabel - ); - cy.get(groupsSelector.permissionNameInput) - .should("be.visible") - .and(isEdit ? "have.value" : "have.attr", - isEdit ? groupName : "placeholder", - isEdit ? groupName : "Eg. Product analytics apps"); - cy.get(groupsSelector.permissionNameHelperText).verifyVisibleElement( - "have.text", - groupsText.permissionNameHelperText - ); - - // Permission section verification - const permissionChecks = [ - { selector: groupsSelector.permissionLabel, text: groupsText.permissionLabel }, - { selector: groupsSelector.editPermissionLabel, text: groupsText.editPermissionLabel }, - { selector: groupsSelector.editPermissionHelperText, text: groupsText.appEditHelperText }, - { selector: groupsSelector.viewPermissionLabel, text: groupsText.viewPermissionLabel }, - { selector: groupsSelector.viewPermissionHelperText, text: groupsText.appViewHelperText } - ]; - - permissionChecks.forEach(({ selector, text }) => { - cy.get(selector).verifyVisibleElement("have.text", text); - }); - - // Hide permission verification - cy.get(groupsSelector.hidePermissionInput).should("be.visible"); - cy.get(groupsSelector.appHidePermissionModalLabel).verifyVisibleElement( - "have.text", - groupsText.appHideLabel - ); - cy.get(groupsSelector.appHidePermissionModalHelperText).verifyVisibleElement( - "have.text", - groupsText.appHideHelperText - ); - - // Resource section verification - const resourceChecks = [ - { selector: groupsSelector.resourceLabel, text: groupsText.resourcesheader }, - { selector: groupsSelector.allAppsLabel, text: isEdit ? groupsText.allAppsLabel : groupsText.groupChipText }, - { selector: groupsSelector.allAppsHelperText, text: groupsText.allAppsHelperText } - ]; - - resourceChecks.forEach(({ selector, text }) => { - cy.get(selector).verifyVisibleElement("have.text", text); - }); - - // Button states - cy.get(groupsSelector.confimButton).verifyVisibleElement( - "have.text", - isEdit ? groupsText.updateButtonText : groupsText.addButtonText - ); - cy.get(groupsSelector.confimButton).should(isEdit ? "be.enabled" : "be.disabled"); - cy.get(groupsSelector.cancelButton).verifyVisibleElement("have.text", groupsText.cancelButton); - - if (isEdit) { - cy.get(groupsSelector.deletePermissionIcon).should("be.visible"); - } -}; - -const verifyPermissionSection = () => { - const permissionElements = [ - { - resource: groupsSelector.resourcesApps, - resourceText: groupsText.resourcesApps, - check: groupsSelector.appsCreateCheck, - label: groupsSelector.appsCreateLabel, - labelText: groupsText.createLabel, - helperText: groupsSelector.appCreateHelperText, - helperContent: groupsText.appCreateHelperText - }, - { - resource: groupsSelector.resourcesFolders, - resourceText: groupsText.resourcesFolders, - check: groupsSelector.foldersCreateCheck, - label: groupsSelector.foldersCreateLabel, - labelText: groupsText.folderCreateLabel, - helperText: groupsSelector.foldersHelperText, - helperContent: groupsText.folderHelperText - } - ]; - - permissionElements.forEach(({ resource, resourceText, check, label, labelText, helperText, helperContent }) => { - cy.get(resource).verifyVisibleElement("have.text", resourceText); - cy.get(check).should("be.visible"); - cy.get(check).check(); - cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.permissionUpdatedToast); - cy.get(check).uncheck(); - cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.permissionUpdatedToast); - cy.get(label).verifyVisibleElement("have.text", labelText); - cy.get(helperText).verifyVisibleElement("have.text", helperContent); - }); -}; - -const verifyEmptyStates = () => { - // Users empty state - cy.get(groupsSelector.userEmptyPageIcon).should("be.visible"); - cy.get(groupsSelector.userEmptyPageTitle).verifyVisibleElement("have.text", groupsText.userEmptyPageTitle); - cy.get(groupsSelector.userEmptyPageHelperText).verifyVisibleElement("have.text", groupsText.userEmptyPageHelperText); - - // Granular permissions empty state - cy.get(groupsSelector.granularLink).click(); - cy.get(groupsSelector.granularEmptyPageIcon).should("be.visible"); - cy.get(groupsSelector.emptyPagePermissionTitle).verifyVisibleElement("have.text", groupsText.emptyPagePermissionTitle); - cy.get(groupsSelector.emptyPagePermissionHelperText).verifyVisibleElement("have.text", groupsText.emptyPagePermissionHelperText); -}; - -const verifyGroupLinks = () => { - const links = [ - { selector: groupsSelector.usersLink, text: groupsText.usersLink }, - { selector: groupsSelector.permissionsLink, text: groupsText.permissionsLink }, - { selector: groupsSelector.granularLink, text: "Granular access" } - ]; - - links.forEach(({ selector, text }) => { - cy.get(selector).verifyVisibleElement("have.text", text); - }); -}; - -describe("Manage Groups", () => { - const groupName = fake.firstName.replaceAll("[^A-Za-z]", ""); - const newGroupname = `New ${groupName}`; - const data = { - firstName: fake.firstName, - email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", "") - }; - - beforeEach(() => { - cy.defaultWorkspaceLogin(); - }); - - it("Should verify the elements and functionalities on manage groups page", () => { - // Setup workspace - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.visit(`${data.workspaceSlug}`); - common.navigateToManageGroups(); - - // Verify page headers - cy.get(commonSelectors.breadcrumbTitle).should(($el) => { - expect($el.contents().first().text().trim()).to.eq("Workspace settings"); - }); - cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement("have.text", "Groups"); - - // Verify base group elements - groups.manageGroupsElements(); - - // Test group creation flow - const verifyGroupCreation = () => { - cy.get(groupsSelector.createNewGroupButton).should("be.visible").click(); - cy.get(groupsSelector.addNewGroupModalTitle).verifyVisibleElement("have.text", groupsText.cardTitle); - cy.get(groupsSelector.groupNameInput).should("be.visible"); - cy.get(groupsSelector.cancelButton).verifyVisibleElement("have.text", groupsText.cancelButton); - cy.get(groupsSelector.createGroupButton).verifyVisibleElement("have.text", groupsText.createGroupButton); - cy.get(groupsSelector.cancelButton).click(); - }; - - // Test duplicate group name - const testDuplicateGroup = () => { - cy.get(groupsSelector.createNewGroupButton).click(); - cy.clearAndType(groupsSelector.groupNameInput, groupsText.admin); - cy.get(groupsSelector.createGroupButton).click(); - cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.groupNameExistToast); - cy.get(groupsSelector.cancelButton).click(); - cy.get(groupsSelector.groupNameInput).should("not.exist"); - }; - - // Create and verify new group - const createNewGroup = () => { - cy.get(groupsSelector.createNewGroupButton).click(); - cy.clearAndType(groupsSelector.groupNameInput, groupName); - cy.get(groupsSelector.createGroupButton).click(); - cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.groupCreatedToast); - - // Verify group creation - cy.get(groupsSelector.groupLink(groupName)).verifyVisibleElement("have.text", groupName); - cy.get(groupsSelector.groupLink(groupName)).click(); - cy.get(groupsSelector.groupPageTitle(groupName)).verifyVisibleElement("have.text", `${groupName} (0)`); - cy.get(groupsSelector.groupNameUpdateLink).should("be.visible"); - - // Verify group links and tables - verifyGroupLinks(); - cy.get(groupsSelector.usersLink).click(); - cy.get(groupsSelector.nameTableHeader).verifyVisibleElement("have.text", groupsText.userNameTableHeader); - cy.get(groupsSelector.emailTableHeader).verifyVisibleElement("have.text", groupsText.emailTableHeader); - verifyEmptyStates(); - }; - - verifyGroupCreation(); - testDuplicateGroup(); - createNewGroup(); - - cy.get(groupsSelector.permissionsLink).click(); - verifyPermissionSection(); - - // Test granular access section - const verifyGranularAccess = () => { - cy.get(groupsSelector.granularLink).click(); - cy.get(groupsSelector.addAppButton).click(); - verifyModalFields(); - - // Add permission - cy.clearAndType(groupsSelector.permissionNameInput, groupName); - cy.get(groupsSelector.confimButton).click(); - - // Verify edit mode - cy.get(`[data-cy="${groupName.toLowerCase()}-text"]`).click(); - cy.get(`${groupsSelector.addEditPermissionModalTitle}:eq(2)`) - .verifyVisibleElement("have.text", groupsText.editPermissionModalTitle); - cy.get(groupsSelector.editPermissionRadio).check(); - verifyModalFields(true, groupName); - cy.get(groupsSelector.confimButton).click(); - }; - - verifyGranularAccess(); - - // Test group rename and delete - const verifyGroupRename = () => { - cy.get(groupsSelector.groupNameUpdateLink).click(); - cy.get(groupsSelector.updateGroupNameModalTitle) - .verifyVisibleElement("have.text", groupsText.updateGroupNameModalTitle); - cy.clearAndType(groupsSelector.groupNameInput, newGroupname); - cy.get(groupsSelector.createGroupButton).click(); - cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.groupNameUpdateSucessToast); - cy.get(groupsSelector.groupLink(newGroupname)) - .verifyVisibleElement("have.text", newGroupname); - }; - - const verifyGroupDelete = () => { - cy.get(groupsSelector.groupLink(newGroupname)).click(); - groups.OpenGroupCardOption(newGroupname); - cy.get(groupsSelector.deleteGroupOption).click(); - - // Verify delete confirmation - cy.get(groupsSelector.confirmText) - .verifyVisibleElement("have.text", groupsText.confirmText); - cy.get(commonSelectors.buttonSelector("Cancel")) - .verifyVisibleElement("have.text", groupsText.confirmCancelButton); - cy.get(commonSelectors.buttonSelector("Yes")) - .verifyVisibleElement("have.text", groupsText.confirmYesButton); - cy.get(commonSelectors.buttonSelector("Cancel")).click(); - - // Actual delete - groups.OpenGroupCardOption(newGroupname); - cy.get(groupsSelector.deleteGroupOption).click(); - cy.get(commonSelectors.buttonSelector("Yes")).click(); - }; - - verifyGroupRename(); - verifyGroupDelete(); - }); -}); \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js index 76eecab280..b6b8967b39 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js @@ -1,37 +1,30 @@ import { fake } from "Fixtures/fake"; -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; +import { commonSelectors } from "Selectors/common"; import { exportAppModalSelectors, importSelectors, } from "Selectors/exportImport"; import { groupsSelector } from "Selectors/manageGroups"; -import { workspaceConstantsSelectors } from "Selectors/workspaceConstants"; import { - createFolder, - deleteFolder, logout, navigateToManageGroups, navigateToManageUsers, - releaseApp, selectAppCardOption, } from "Support/utils/common"; import { - createGroupsAndAddUserInGroup, inviteUserBasedOnRole, setupAndUpdateRole, setupWorkspaceAndInviteUser, updateRole, - verifyBasicPermissions, - verifySettingsAccess, verifyUserPrivileges, } from "Support/utils/manageGroups"; -import { addAndVerifyConstants } from "Support/utils/workspaceConstants"; import { commonText } from "Texts/common"; import { exportAppModalText, importText } from "Texts/exportImport"; import { groupsText } from "Texts/manageGroups"; describe("Manage Groups", () => { let data = {}; + const isEnterprise = Cypress.env("environment") === "Enterprise"; before(() => { cy.exec("mkdir -p ./cypress/downloads/"); @@ -52,6 +45,7 @@ describe("Manage Groups", () => { cy.defaultWorkspaceLogin(); cy.intercept("DELETE", "/api/folders/*").as("folderDeleted"); cy.skipWalkthrough(); + cy.viewport(2400, 2000); }); it("should verify the last active admin role update protection", () => { @@ -89,215 +83,6 @@ describe("Manage Groups", () => { cy.get(commonSelectors.closeButton).click(); }); - it("should verify user privileges in custom groups", () => { - const groupName = fake.firstName.replace(/[^A-Za-z]/g, ""); - const appName2 = fake.companyName; - const appName3 = fake.companyName; - const appSlug = appName3.toLowerCase().replace(/\s+/g, "-"); - - setupWorkspaceAndInviteUser( - data.workspaceName, - data.workspaceSlug, - data.firstName, - data.email - ); - cy.apiLogout(); - - // Setup custom group - cy.apiLogin(); - cy.visit(data.workspaceSlug); - navigateToManageGroups(); - cy.get(groupsSelector.groupLink("Builder")).click(); - cy.get(groupsSelector.permissionsLink).click(); - cy.get(groupsSelector.appsCreateCheck).uncheck(); - cy.get(groupsSelector.appsDeleteCheck).uncheck(); - cy.get(groupsSelector.foldersCreateCheck).uncheck(); - cy.get(groupsSelector.workspaceVarCheckbox).uncheck(); - - createGroupsAndAddUserInGroup(groupName, data.email); - - // Permission configuration and verification - cy.get(groupsSelector.permissionsLink).click(); - - // App creation permission - cy.get(groupsSelector.appsCreateCheck).check(); - cy.get(commonSelectors.defaultModalTitle).contains( - groupsText.changeUserRoleHeader - ); - cy.get(groupsSelector.changeRoleModalMessage).contains( - groupsText.changeUserRoleMessage - ); - cy.get(".item-list").contains(data.email); - cy.get(groupsSelector.confimButton).should( - "have.text", - groupsText.continueButtonText - ); - cy.get(commonSelectors.cancelButton).click(); - - // Other permissions - const permissions = [ - groupsSelector.appsDeleteCheck, - groupsSelector.foldersCreateCheck, - groupsSelector.workspaceVarCheckbox, - ]; - - permissions.forEach((permission) => { - cy.get(permission).check(); - cy.get(".modal-content").should("be.visible"); - cy.get(commonSelectors.cancelButton).click(); - }); - - // Granular permissions - cy.get(groupsSelector.granularLink).click(); - cy.get(groupsSelector.addAppButton).click(); - cy.clearAndType(groupsSelector.permissionNameInput, data.firstName); - cy.get(groupsSelector.editPermissionRadio).click(); - cy.get(groupsSelector.confimButton).click(); - - // Verify modal - cy.get(".modal-content").should("be.visible"); - cy.get(groupsSelector.modalHeader).should( - "have.text", - groupsText.cantCreatePermissionModalHeader - ); - cy.get(groupsSelector.modalMessage).should( - "have.text", - groupsText.cantCreatePermissionModalMessage - ); - - cy.get(".item-list").contains(data.email); - cy.get(commonSelectors.closeButton).click(); - - // Role transition - cy.get(groupsSelector.permissionsLink).click(); - cy.get(groupsSelector.appsCreateCheck).check(); - cy.get(groupsSelector.confimButton).click(); - permissions.forEach((permission) => { - cy.get(permission).check(); - }); - cy.get(groupsSelector.groupLink("Builder")).click(); - cy.get(groupsSelector.usersLink).click(); - cy.get(`[data-cy="${data.email}-user-row"]`).should("be.visible"); - cy.apiLogout(); - cy.apiLogin(data.email); - cy.visit(data.workspaceSlug); - - // Verify builder permissions - verifyBasicPermissions(true); - - // App operations - cy.createApp(data.appName); - // cy.verifyToastMessage( - // commonSelectors.toastMessage, - // commonText.appCreatedToast - // ); - cy.backToApps(); - - cy.wait(2500); - cy.deleteApp(data.appName); - - - // Folder operations - createFolder(data.folderName); - deleteFolder(data.folderName); - - // Constants management - cy.get(commonSelectors.workspaceConstantsIcon).click(); - addAndVerifyConstants(data.firstName, data.appName); - cy.get( - workspaceConstantsSelectors.constDeleteButton(data.firstName) - ).click(); - cy.get(commonSelectors.yesButton).click(); - - verifySettingsAccess(false); - cy.get(commonSelectors.settingsIcon).click(); - cy.wait(500); - cy.apiLogout(); - - cy.apiLogin(); - cy.visit(data.workspaceSlug); - navigateToManageGroups(); - cy.get(groupsSelector.groupLink(groupName)).click(); - - cy.get(groupsSelector.permissionsLink).click(); - cy.get(groupsSelector.appsCreateCheck).uncheck(); - permissions.forEach((permission) => { - cy.get(permission).uncheck(); - }); - - cy.wait(2000); - cy.get(groupsSelector.groupLink("Builder")).click(); - cy.get(groupsSelector.granularLink).click(); - cy.wait(2000); - cy.get(groupsSelector.granularAccessPermission) - .trigger("mouseenter") - .click({ force: true }); - cy.get(groupsSelector.deletePermissionIcon).click(); - cy.get(groupsSelector.yesButton).click(); - - // Create test apps - cy.get(commonSelectors.homePageLogo).click(); - cy.apiCreateApp(data.appName); - cy.apiCreateApp(appName2); - - // App Hide from dashboard - cy.apiCreateApp(appName3); - cy.openApp(); - cy.apiAddComponentToApp(appName3, "text1"); - releaseApp(); - cy.get(commonWidgetSelector.shareAppButton).click(); - cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${appSlug}`); - cy.wait(500); - cy.get(commonWidgetSelector.modalCloseButton).click(); - cy.backToApps(); - - // Configure app permissions - navigateToManageGroups(); - cy.get(groupsSelector.groupLink(groupName)).click(); - cy.get(groupsSelector.granularLink).click(); - - // Setup permissions for both apps - [data.appName, appName2, appName3].forEach((app) => { - cy.get(groupsSelector.addAppButton).click(); - cy.clearAndType(groupsSelector.permissionNameInput, app); - cy.get(groupsSelector.customradio).check(); - cy.get(".css-1gfides").click({ force: true }).type(`${app}{enter}`); - cy.get(groupsSelector.confimButton).click({ force: true }); - cy.verifyToastMessage( - commonSelectors.toastMessage, - groupsText.createPermissionToast - ); - }); - cy.get(groupsSelector.groupChip).contains(data.appName).click(); - cy.get(groupsSelector.editPermissionRadio).click(); - cy.get(groupsSelector.confimButton).click(); - - //To hide app - cy.get(groupsSelector.groupChip).contains(appName3).click(); - cy.get(groupsSelector.hidePermissionInput).check(); - cy.get(groupsSelector.confimButton).click(); - - // Verify as end user - cy.wait(1000); - cy.apiLogout() - cy.apiLogin(data.email); - cy.visit(data.workspaceSlug); - - cy.get('.appcard-buttons-wrap [data-cy="edit-button"]').should( - "have.lengthOf", - 1 - ); - cy.get('.appcard-buttons-wrap [data-cy="launch-button"]').should( - "have.lengthOf", - 2 - ); - - //Visit hidden app url - cy.visitSlug({ - actualUrl: `${Cypress.config("baseUrl")}/applications/${appSlug}`, - }); - }); - it("should verify user role updating sequence", () => { const roleUpdateSequence = [ { @@ -407,7 +192,7 @@ describe("Manage Groups", () => { }); }); - it("should verify query creation and import access for Builders and Admin", () => { + it.skip("should verify query creation and import access for Builders and Admin", () => { const firstName2 = fake.firstName; const email2 = fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""); const workspaceName2 = fake.firstName @@ -454,7 +239,7 @@ describe("Manage Groups", () => { cy.wait(500); cy.apiCreateGDS( - `${Cypress.env('server_host')}/api/data-sources`, + `${Cypress.env("server_host")}/api/data-sources`, `cypress-${data.dsName}-qc-postgresql`, "postgresql", [ @@ -479,21 +264,21 @@ describe("Manage Groups", () => { //Create and run postgres query in the app // Need to enable once bug is fixed /* - - - addQuery( - "table_preview", - `SELECT * FROM persons;`, - `cypress-${data.dsName1}-postgresql` - ); - - cy.get('[data-cy="list-query-table_preview"]').verifyVisibleElement( - "have.text", - "table_preview " - ); - cy.get(dataSourceSelector.queryCreateAndRunButton).click(); - verifyValueOnInspector("table_preview", "7 items "); - */ + + + addQuery( + "table_preview", + `SELECT * FROM persons;`, + `cypress-${data.dsName1}-postgresql` + ); + + cy.get('[data-cy="list-query-table_preview"]').verifyVisibleElement( + "have.text", + "table_preview " + ); + cy.get(dataSourceSelector.queryCreateAndRunButton).click(); + verifyValueOnInspector("table_preview", "7 items "); + */ cy.backToApps(); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/userRoleUI.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/userRoleUI.cy.js new file mode 100644 index 0000000000..af06f1778c --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/userRoleUI.cy.js @@ -0,0 +1,202 @@ +import { fake } from "Fixtures/fake"; +import { commonSelectors } from "Selectors/common"; +import { groupsSelector } from "Selectors/manageGroups"; +import { navigateToManageGroups } from "Support/utils/common"; +import { + commonGroupVerification, + toggleAllPermissions, + verifyAdminHelperText, + verifyCheckPermissionStates, + verifyEditUserRoleModal, + verifyEmptyStates, + verifyEnduserHelperText, + verifyGranularAccessByRole, + verifyGranularAddModal, + verifyGranularEditModal, + verifyPermissionCheckBoxLabelsAndHelperTexts, + verifyUserRow, +} from "Support/utils/platform/groupsUI"; +import { groupsText } from "Texts/manageGroups"; + +describe("User Role UI and Functionality verification", () => { + const data = { + firstName: fake.firstName, + email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + workspaceName: fake.firstName, + workspaceSlug: fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""), + }; + + before(() => { + cy.apiLogin(); + cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); + }); + + beforeEach(() => { + cy.apiLogin(); + cy.visit(`${data.workspaceSlug}`); + navigateToManageGroups(); + cy.viewport(2000, 1900); + }); + + it("should verify admin role UI elements and interactions", () => { + // Verify page headers + cy.get(commonSelectors.breadcrumbTitle).should(($el) => { + expect($el.contents().first().text().trim()).to.eq("Workspace settings"); + }); + cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement( + "have.text", + "Groups" + ); + + cy.get('[data-cy="page-title"]').should(($el) => { + expect($el.contents().last().text().trim()).to.eq("Groups"); + }); + + cy.verifyElement( + groupsSelector.createNewGroupButton, + groupsText.createNewGroupButton + ); + + cy.get('[data-cy="user-role-title"]').verifyVisibleElement( + "have.text", + "USER ROLE" + ); + cy.verifyElement('[data-cy="custom-groups-title"]', "CUSTOM GROUPS"); + cy.get('[data-cy="create-group-button-icon"]').should("be.visible"); + cy.get('[data-cy="search-icon"]').should("be.visible"); + + // Admin List Item Verification + cy.verifyElement(groupsSelector.adminListItem, "Admin"); + cy.verifyElement(groupsSelector.adminTitle, "Admin (1)"); + cy.verifyElement( + groupsSelector.textDefaultGroup, + groupsText.textDefaultGroup + ); + + // Verify tabs visibility + cy.get(groupsSelector.usersLink).should("be.visible"); + cy.get(groupsSelector.permissionsLink).should("be.visible"); + cy.get(groupsSelector.granularLink).should("be.visible"); + + commonGroupVerification(); + + // Users Tab Verification + cy.get(groupsSelector.usersLink).click(); + cy.get('[data-cy="user-group-search-btn"]').should("be.visible"); + cy.verifyElement( + groupsSelector.nameTableHeader, + groupsText.userNameTableHeader + ); + cy.verifyElement( + groupsSelector.emailTableHeader, + groupsText.emailTableHeader + ); + + verifyUserRow("The Developer", "dev@tooljet.io"); + + cy.get('[data-cy="edit-role-button"]') + .should("be.visible") + .and("be.enabled"); + cy.get('[data-cy="edit-role-button"]').click(); + verifyEditUserRoleModal("dev@tooljet.io"); + cy.get(groupsSelector.cancelButton).click(); + + // Permissions Tab Verification + cy.get(groupsSelector.permissionsLink).click(); + verifyAdminHelperText(0); + + verifyCheckPermissionStates("admin"); + verifyPermissionCheckBoxLabelsAndHelperTexts(); + + // Granular Access Tab Verification + verifyGranularAccessByRole("admin"); + verifyAdminHelperText(1); + }); + + it("should verify builder role UI elements and interactions", () => { + // Builder Group Navigation and Title Verification + cy.get(groupsSelector.groupLink("Builder")).click(); + cy.verifyElement(groupsSelector.builderListItem, "Builder"); + cy.verifyElement(groupsSelector.builderTitle, "Builder (0)"); + cy.verifyElement( + groupsSelector.textDefaultGroup, + groupsText.textDefaultGroup + ); + + // Verify tabs visibility + cy.get(groupsSelector.usersLink).should("be.visible"); + cy.get(groupsSelector.permissionsLink).should("be.visible"); + cy.get(groupsSelector.granularLink).should("be.visible"); + + // Users Tab Verification - Empty State + verifyEmptyStates(); + + // Permissions Tab Verification + cy.get(groupsSelector.permissionsLink).click(); + verifyCheckPermissionStates("builder"); + verifyPermissionCheckBoxLabelsAndHelperTexts(); + toggleAllPermissions(); + + // Granular Access Tab Verification + verifyGranularAccessByRole("builder"); + cy.verifyElement( + groupsSelector.appHideLabel, + groupsText.appHideLabelPermissionModal + ); + + // Edit Modal Verification + verifyGranularEditModal("builder"); + + // Add Modal Verification + verifyGranularAddModal("builder"); + }); + + it("should verify enduser role UI and interaction", () => { + // End-user Group Navigation and Title Verification + cy.get(groupsSelector.groupLink("End-user")).click(); + cy.get(groupsSelector.groupLink("End-user")).verifyVisibleElement( + "have.text", + "End-user" + ); + cy.get(groupsSelector.enduserTitle).verifyVisibleElement( + "have.text", + "End-user (0)" + ); + cy.verifyElement( + groupsSelector.textDefaultGroup, + groupsText.textDefaultGroup + ); + + // Verify tabs visibility + cy.get(groupsSelector.usersLink).should("be.visible"); + cy.get(groupsSelector.permissionsLink).should("be.visible"); + cy.get(groupsSelector.granularLink).should("be.visible"); + + // Users Tab Verification - Empty State + verifyEmptyStates(); + + // Permissions Tab Verification + cy.get(groupsSelector.permissionsLink).click(); + verifyEnduserHelperText(0); + verifyCheckPermissionStates("enduser"); + verifyPermissionCheckBoxLabelsAndHelperTexts(); + + // Granular Access Tab Verification + cy.reload(); + cy.wait(2000); + cy.get(groupsSelector.groupLink("End-user")).click(); + + verifyGranularAccessByRole("enduser"); + verifyEnduserHelperText(1); + cy.verifyElement( + groupsSelector.appHideLabel, + groupsText.appHideLabelPermissionModal + ); + + // Edit Modal Verification + verifyGranularEditModal("enduser"); + + // Add Modal Verification + verifyGranularAddModal("enduser"); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/homePageDashboard.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/homePageDashboard.cy.js new file mode 100644 index 0000000000..b0ab5624e6 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/homePageDashboard.cy.js @@ -0,0 +1,113 @@ +import { fake } from "Fixtures/fake"; +import { commonSelectors } from "Selectors/common"; +import { dashboardSelector } from "Selectors/dashboard"; +import { commonText } from "Texts/common"; +import { dashboardText } from "Texts/dashboard"; + +describe("Home Page Dashboard Testcases", () => { + let data = {}; + const isEnterprise = Cypress.env("environment") === "Enterprise"; + + before(function () { + if (Cypress.env("environment") === "Community") { + this.skip(); + } + }); + beforeEach(() => { + + data = { + firstName: fake.firstName, + email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + }; + cy.intercept("GET", "/api/library_apps").as("appLibrary"); + cy.apiLogin(); + cy.visit("/"); + }); + + it("Should verify elements on home page dashboard", () => { + + cy.get(commonSelectors.homePageIcon).click(); + cy.get(commonSelectors.breadcrumbHeaderTitle).should(($el) => { + expect($el.contents().first().text().trim()).to.eq( + commonText.breadcrumbHome + ); + }); + cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement( + "have.text", + commonText.breadcrumbHome + ); + + cy.get(dashboardSelector.aiIcon).should("be.visible"); + cy.get(dashboardSelector.homePagePromptHeader).verifyVisibleElement( + "have.text", + dashboardText.homePagePromptHeader + ); + + cy.get(dashboardSelector.promptInput).should("be.visible"); + cy.get(dashboardSelector.homePagePromptTextArea).should("be.enabled").type("Build a task manager app"); + cy.get(dashboardSelector.promptEnterButton).should("have.attr", "class").and("include", "tw-opacity-100"); + + + cy.get(dashboardSelector.homePageDividerText).verifyVisibleElement( + "have.text", + dashboardText.homePageDividerText + ); + + // Define card types + const cardTypes = [ + { type: 'app', title: dashboardText.appCardTitle, description: dashboardText.appCardDescription, url: "my-workspace" }, + { type: 'datasource', title: dashboardText.datasourceCardTitle, description: dashboardText.datasourceCardDescription, url: 'data-sources' }, + { type: 'workflow', title: dashboardText.workflowCardTitle, description: dashboardText.workflowCardDescription, url: 'workflows' }, + { type: 'template', title: dashboardText.templateCardTitle, description: dashboardText.templateCardDescription, url: '#' } + ]; + + + const env = Cypress.env('environment'); + + // Filter cards based on environment + const cardsToTest = cardTypes.filter(cardType => { + if (env === 'Enterprise') return ['app', 'datasource', 'workflow'].includes(cardType.type); + if (env === 'Cloud') return ['add', 'datasource', 'template'].includes(cardType.type); + return false; + }); + + cardsToTest.forEach(cardType => { + cy.get(dashboardSelector.widgetCardName(cardType.type)).within(() => { + cy.get(dashboardSelector.homePageIcon(cardType.type)).should("be.visible"); + cy.get(dashboardSelector.widgetCardTitle).verifyVisibleElement( + "have.text", + cardType.title + ); + cy.get(dashboardSelector.widgetCardDescription).verifyVisibleElement( + "have.text", + cardType.description + ); + + }); + cy.get(dashboardSelector.widgetCardName(cardType.type)).should("have.attr", "href").and("include", cardType.url); + }); + + }); + + it("Should verify Home page accessibility for the specific role", () => { + //Invite End-user + cy.apiFullUserOnboarding(data.firstName, data.email); + cy.apiLogout(); + + cy.apiLogin(data.email); + cy.visit("/"); + cy.get(commonSelectors.homePageIcon).should("not.exist"); + cy.apiLogout(); + + cy.apiLogin(); + cy.visit("/"); + + //Update role to Builder + cy.apiUpdateUserRole(data.email, "builder"); + cy.apiLogout(); + + cy.apiLogin(data.email); + cy.visit("/"); + cy.get(commonSelectors.homePageIcon).should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/licensing/licensingHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/licensing/licensingHappyPath.cy.js new file mode 100644 index 0000000000..a4eab32ad9 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/licensing/licensingHappyPath.cy.js @@ -0,0 +1,219 @@ +import { commonSelectors } from "Selectors/common"; +import { commonText, settingsText, workspaceSettingsText } from "Texts/common"; +import { fake } from "Fixtures/fake"; +import { licenseText } from "Texts/license"; +import { licenseSelectors } from "Selectors/license"; +import { usersSelector } from "Selectors/manageUsers"; +import { + switchTabs, + verifylicenseTab, + verifySubTabs, + verifyAccessTab, + verifyDomainTab, + verifyResourceLimit, + verifyTooltip, +} from "Support/utils/license"; +import * as common from "Support/utils/common"; +import { dashboardSelector } from "../../../../../constants/selectors/dashboard"; +import { navigateToEditUser } from "Support/utils/manageUsers"; +import { + instanceSettingsSelector, + whiteLabellingSelectors, +} from "Selectors/eeCommon"; +import { workflowSelector } from "Selectors/workflows"; +import { commonEeSelectors } from "Selectors/eeCommon"; + +describe("License Page", () => { + const data = {}; + + beforeEach(() => { + cy.apiLogin(); + cy.visit("/"); + }); + + it("Should verify license page elements with the basic plan ", () => { + common.navigateToSettingPage(); + cy.get(licenseSelectors.listOfItems(licenseText.license)).click(); + + cy.contains(licenseText.licenseOverviewTitle).should("be.visible"); + cy.get(commonSelectors.breadcrumbPageTitle).should( + "have.text", + licenseText.license + ); + cy.get(licenseSelectors.comparePlansText) + .should("be.visible") + .and("contain.text", licenseText.comparePlansText); + + switchTabs(licenseText.licenseKeyLabel); + verifylicenseTab(); + + switchTabs(licenseText.limitsTabTitle); + verifySubTabs( + licenseText.limitsTab.aiCreditsSubTab, + licenseText.aiCreditsSubTab, + { + "Monthly recurring": "0/0", + "Add on credits": "0/0", + } + ); + verifySubTabs(licenseText.limitsTab.appsSubTab, licenseText.appsSubTab, { + "Number of Apps": "1/2", + }); + verifySubTabs( + licenseText.limitsTab.workspacesSubTab, + licenseText.workspacesSubTab, + { + "Number of Workspaces": "1/1", + } + ); + verifySubTabs(licenseText.limitsTab.usersSubTab, licenseText.usersSubTab, { + "Number of Total Users": "1/52", + "Number of Builders": "1/2", + "Number of End Users": "0/50", + "Number of Super Admins": "1/1", + }); + verifySubTabs( + licenseText.limitsTab.workflowsSubTab, + licenseText.workflowsSubTab, + { + "Number of Workflows": "0/2", + } + ); + verifySubTabs( + licenseText.limitsTab.tablesSubTab, + licenseText.tablesSubTab, + { + "Number of Tables": "Unlimited", + } + ); + + switchTabs(licenseText.accessTabTitle); + verifyAccessTab(); + + switchTabs(licenseText.domainTabTitle); + verifyDomainTab(); + }); + + it("Should verify banners and tooltips with the basic plan ", () => { + cy.get(commonSelectors.workspaceName).click(); + + cy.get('[data-cy="workspace-count"]').should("be.visible"); + verifyResourceLimit("workspaces", "basic"); + + cy.get(dashboardSelector.homePageContent).click(); + verifyResourceLimit("apps", "basic"); + + // cy.get(workflowSelector.globalWorkFlowsIcon).click(); + // verifyResourceLimit("workflow", "basic"); + + common.navigateToManageUsers(); + cy.get(usersSelector.buttonAddUsers).click(); + verifyResourceLimit("builders", "basic", "usage"); + + cy.reload(); + + common.navigateToManageGroups(); + cy.get('[data-cy="create-new-group-button"]').should("be.disabled"); + verifyTooltip( + '[data-cy="create-new-group-button"]', + "Custom groups are available only in paid plans", + true + ); + + verifyResourceLimit( + "custom-groups", + "basic", + "custom-groups", + "Custom groups & permissions are paid features" + ); + + cy.get( + licenseSelectors.listOfItems(workspaceSettingsText.customStylesListItem) + ).click(); + cy.get('[data-cy="modal-close"]').click(); + cy.get(licenseSelectors.paidFeatureButton).should("be.visible"); + + cy.get( + licenseSelectors.listOfItems( + workspaceSettingsText.configureGitSyncListItem + ) + ).click(); + cy.get(licenseSelectors.paidFeatureButton).should("be.visible"); + + cy.get( + licenseSelectors.listOfItems(workspaceSettingsText.themesListItem) + ).click(); + verifyResourceLimit( + "custom-themes", + "basic", + "custom-themes", + "Custom themes is our paid feature. Upgrade to a paid plan to add and customize themes." + ); + + common.navigateToSettingPage(); + cy.get(licenseSelectors.listOfItems(settingsText.allUsersListItem)).click({ + force: true, + }); + navigateToEditUser(commonText.email); + verifyResourceLimit( + "edit-user", + "basic", + "edit-user", + "Edit user details with our paid plans. For more," + ); + + cy.reload(); + cy.get( + licenseSelectors.listOfItems(settingsText.manageInstanceSettingsListItem) + ).click(); + cy.get(licenseSelectors.paidFeatureButton).should("be.visible"); + cy.get(instanceSettingsSelector.allowWorkspaceToggle).should("be.disabled"); + cy.get( + licenseSelectors.listOfItems(settingsText.whiteLabellingListItem) + ).click(); + cy.get(licenseSelectors.paidFeatureButton).should("be.visible"); + cy.get(whiteLabellingSelectors.appLogoInput).should("be.disabled"); + + cy.get(commonSelectors.workspaceConstantsIcon).click(); + verifyTooltip( + licenseSelectors.listOfItems("staging"), + "Multi-environments are available only in paid plans", + true + ); + verifyTooltip( + licenseSelectors.listOfItems("production"), + "Multi-environments are available only in paid plans", + true + ); + + cy.get(commonSelectors.dashboardIcon).click(); + cy.get(commonSelectors.settingsIcon).click(); + verifyTooltip( + commonEeSelectors.auditLogIcon, + "Audit logs are available only in paid plans", + true + ); + + // cy.apiCreateApp(`${fake.companyName}-license-App`); + // cy.openApp(); + + // cy.get('[data-cy="list-current-env-name"]').click(); + // cy.get('[data-cy="env-name-list"]') + // .eq(1) + // .within(() => { + // verifyTooltip( + // '[data-cy="env-name-dropdown"]', + // "Multi-environments are available only in paid plans" + // ); + // }); + + // cy.get('[data-cy="env-name-list"]') + // .eq(2) + // .within(() => { + // verifyTooltip( + // '[data-cy="env-name-dropdown"]', + // "Multi-environments are available only in paid plans" + // ); + // }); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js index 3cdda33212..df4e5d0b50 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js @@ -1,15 +1,15 @@ -import { commonSelectors } from "Selectors/common"; -import { commonText } from "Texts/common"; +import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { onboardingSelectors } from "Selectors/onboarding"; -import { onboardingText } from "Texts/onboarding"; import { logout } from "Support/utils/common"; import { bannerElementsVerification, onboardingStepOne, - onboardingStepTwo, onboardingStepThree, + onboardingStepTwo, } from "Support/utils/onboarding"; import { updateLicense } from "Support/utils/platform/eeCommon"; +import { commonText } from "Texts/common"; +import { onboardingText } from "Texts/onboarding"; describe("Self host onboarding", () => { const envVar = Cypress.env("environment"); @@ -82,21 +82,10 @@ describe("Self host onboarding", () => { cy.get(check.selector).verifyVisibleElement("have.text", check.text); }); - cy.ifEnv("Community", () => { - cy.get(commonSelectors.signUpTermsHelperText).should(($el) => { - expect($el.contents().first().text().trim()).to.eq( - // commonText.selfHostSignUpTermsHelperText - "By signing up you are agreeing to the" - ); - }); - }); - - cy.ifEnv("Enterprise", () => { - cy.get(commonSelectors.signUpTermsHelperText).should(($el) => { - expect($el.contents().first().text().trim()).to.eq( - "By signing up you are agreeing to the" - ); - }); + cy.get(commonSelectors.signUpTermsHelperText).should(($el) => { + expect($el.contents().first().text().trim()).to.eq( + "By signing up you are agreeing to the" + ); }); const links = [ @@ -272,8 +261,9 @@ describe("Self host onboarding", () => { onboardingStepThree(); }); - // cy.get(commonSelectors.skipbutton).click(); - cy.backToApps(); + cy.wait(2000); + cy.get(commonWidgetSelector.previewButton).eq(1).should("be.visible"); + cy.go("back"); logout(); cy.appUILogin(); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js index ca014ffb69..a573bd0e26 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js @@ -2,15 +2,15 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; -import { deleteDatasource } from "Support/utils/dataSource"; +import { deleteDatasource} from "Support/utils/dataSource"; import { dataSourceSelector } from "Selectors/dataSource"; import { workflowsText } from "Texts/workflows"; import { workflowSelector } from "Selectors/workflows"; - import { enterJsonInputInStartNode, - deleteAppandWorkflowAfterExecution, + verifyPreviewOutputText, verifyTextInResponseOutputLimited, + navigateBackToWorkflowsDashboard, } from "Support/utils/workFlows"; const data = {}; @@ -19,15 +19,15 @@ describe("Workflows in apps", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); - data.appName = `${data.wfName}-wf-app`; + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.appName = `${data.workflowName}-wf-app`; data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("Creating workflows with runjs and validating execution in apps", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -51,7 +51,7 @@ describe("Workflows in apps", () => { cy.apiCreateApp(data.appName); cy.openApp(); - cy.addWorkflowInApp(data.wfName); + cy.addWorkflowInApp(data.workflowName); cy.get(dataSourceSelector.queryPreviewButton).click(); @@ -59,19 +59,19 @@ describe("Workflows in apps", () => { // cy.verifyToastMessage( // commonSelectors.toastMessage, - // `Query (${data.dsName}) completed.` + // `Query (${data.dataSourceName}) completed.` // ); - - deleteAppandWorkflowAfterExecution(data.wfName, data.appName); + cy.apiDeleteApp(); + cy.apiDeleteWorkflow(data.workflowName); }); it("Creating workflows with postgres and validating execution in apps", () => { - const dsName = `cypress-${data.dataSourceName}-manual-pgsql`; + const dataSourceName = `cypress-${data.dataSourceName}-manual-pgsql`; cy.get(commonSelectors.globalDataSourceIcon).click(); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "postgresql", [ { key: "connection_type", value: "manual", encrypted: false }, @@ -94,7 +94,7 @@ describe("Workflows in apps", () => { ] ); - cy.get(dataSourceSelector.dataSourceNameButton(dsName)) + cy.get(dataSourceSelector.dataSourceNameButton(dataSourceName)) .should("be.visible") .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); @@ -103,9 +103,10 @@ describe("Workflows in apps", () => { }).should("have.text", postgreSqlText.labelConnectionVerified); cy.reload(); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.postgresqlNodeName)).click({ force: true, @@ -127,7 +128,7 @@ describe("Workflows in apps", () => { cy.apiCreateApp(data.appName); cy.openApp(); - cy.addWorkflowInApp(data.wfName); + cy.addWorkflowInApp(data.workflowName); cy.get(dataSourceSelector.queryPreviewButton).click(); @@ -135,10 +136,10 @@ describe("Workflows in apps", () => { // cy.verifyToastMessage( // commonSelectors.toastMessage, - // `Query (${data.dsName}) completed.` + // `Query (${data.dataSourceName}) completed.` // ); - deleteAppandWorkflowAfterExecution(data.wfName, data.appName); - - deleteDatasource(`cypress-${data.dataSourceName}-manual-pgsql`); + cy.apiDeleteApp(); + cy.apiDeleteWorkflow(data.workflowName); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-manual-pgsql`); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js index 24fe84ebc9..a9a14de1b6 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js @@ -2,7 +2,7 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; -import { deleteWorkflowAndDS } from "Support/utils/dataSource"; +import { deleteWorkflowAndDS, deleteDatasource } from "Support/utils/dataSource"; import { dataSourceSelector } from "Selectors/dataSource"; import { harperDbText } from "Texts/harperDb"; import { workflowsText } from "Texts/workflows"; @@ -14,6 +14,7 @@ import { import { enterJsonInputInStartNode, verifyTextInResponseOutputLimited, + navigateBackToWorkflowsDashboard, } from "Support/utils/workFlows"; const data = {}; @@ -22,14 +23,14 @@ describe("Workflows with Datasource", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("RunJS workflow - execute and validate", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -50,16 +51,16 @@ describe("Workflows with Datasource", () => { ); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - cy.deleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); it("Postgres workflow - execute and validate", () => { - const dsName = `cypress-${data.dataSourceName}-manual-pgsql`; + const dataSourceName = `cypress-${data.dataSourceName}-manual-pgsql`; cy.get(commonSelectors.globalDataSourceIcon).click(); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "postgresql", [ { key: "connection_type", value: "manual", encrypted: false }, @@ -82,7 +83,7 @@ describe("Workflows with Datasource", () => { ] ); - cy.get(dataSourceSelector.dataSourceNameButton(dsName)) + cy.get(dataSourceSelector.dataSourceNameButton(dataSourceName)) .should("be.visible") .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); @@ -91,9 +92,10 @@ describe("Workflows with Datasource", () => { }).should("have.text", postgreSqlText.labelConnectionVerified); cy.reload(); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.postgresqlNodeName)).click({ force: true, @@ -112,15 +114,17 @@ describe("Workflows with Datasource", () => { ); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); - deleteWorkflowAndDS(data.wfName, dsName); + cy.apiDeleteWorkflow(data.workflowName); + + cy.apiDeleteGDS(dataSourceName); }); it("REST API workflow - execute and validate", () => { - const dsName = `cypress-${data.dataSourceName}-restapi`; + const dataSourceName = `cypress-${data.dataSourceName}-restapi`; cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "restapi", [ { key: "url", value: "https://jsonplaceholder.typicode.com" }, @@ -152,9 +156,10 @@ describe("Workflows with Datasource", () => { ] ); cy.reload(); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.restapiNodeName)).click({ force: true, @@ -174,40 +179,41 @@ describe("Workflows with Datasource", () => { ); cy.verifyTextInResponseOutput(workflowsText.restApiExpectedValue); - deleteWorkflowAndDS(data.wfName, dsName); + cy.apiDeleteWorkflow(data.workflowName); + + cy.apiDeleteGDS(dataSourceName); }); it("HarperDB workflow - execute and validate", () => { - const dsName = `cypress-${data.dataSourceName}-harperdb`; + const dataSourceName = `cypress-${data.dataSourceName}-harperdb`; const Host = Cypress.env("harperdb_host"); const Port = Cypress.env("harperdb_port"); const Username = Cypress.env("harperdb_username"); const Password = Cypress.env("harperdb_password"); cy.get(commonSelectors.globalDataSourceIcon).click(); - cy.installMarketplacePlugin(workflowsText.harperDbPluginName); + cy.installMarketplacePlugin("HarperDB"); - selectAndAddDataSource( - "databases", - harperDbText.harperDb, - data.dataSourceName - ); + selectAndAddDataSource("databases", harperDbText.harperDb, data.dataSourceName); fillDataSourceTextField( harperDbText.hostLabel, harperDbText.hostInputPlaceholder, Host ); + fillDataSourceTextField( harperDbText.portLabel, harperDbText.portPlaceholder, Port ); + fillDataSourceTextField( harperDbText.userNameLabel, harperDbText.userNamePlaceholder, Username ); + fillDataSourceTextField( harperDbText.passwordlabel, harperDbText.passwordPlaceholder, @@ -228,9 +234,10 @@ describe("Workflows with Datasource", () => { postgreSqlText.toastDSSaved ); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.harperdbNodeName)).click({ force: true, @@ -256,7 +263,8 @@ describe("Workflows with Datasource", () => { workflowsText.harperDbResponseNodeQuery ); cy.verifyTextInResponseOutput(workflowsText.harperDbExpectedValue); - - deleteWorkflowAndDS(data.wfName, dsName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.workflowName); + deleteDatasource(dataSourceName); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js index 7294f97c63..404283a44b 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js @@ -2,7 +2,7 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; -import { deleteWorkflowAndDS } from "Support/utils/dataSource"; +import { deleteWorkflowAndDS,deleteDatasource } from "Support/utils/dataSource"; import { dataSourceSelector } from "Selectors/dataSource"; import { workflowsText } from "Texts/workflows"; import { workflowSelector } from "Selectors/workflows"; @@ -11,6 +11,7 @@ import { enterJsonInputInStartNode, importWorkflowApp, verifyTextInResponseOutputLimited, + navigateBackToWorkflowsDashboard } from "Support/utils/workFlows"; const data = {}; @@ -19,16 +20,16 @@ describe("Workflows Export/Import Sanity", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("RunJS workflow - execute, export/import, re-execute", () => { - const wfName = `${data.wfName}-runjs`; + const workflowName = `${data.workflowName}-runjs`; - cy.createWorkflowApp(wfName); + cy.createWorkflowApp(workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -49,23 +50,22 @@ describe("Workflows Export/Import Sanity", () => { ); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - cy.exportWorkflowApp(wfName); - - importWorkflowApp(wfName, workflowsText.exportFixturePath); + cy.exportWorkflowApp(workflowName); + cy.apiDeleteWorkflow(workflowName); + importWorkflowApp(workflowName, workflowsText.exportFixturePath); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - - cy.deleteWorkflow(wfName); + cy.apiDeleteWorkflow(workflowName); cy.task("deleteFile", workflowsText.exportFixturePath); }); it("Postgres workflow - execute, export/import, re-execute", () => { - const wfName = `${data.wfName}-pg`; - const dsName = `cypress-${data.dataSourceName}-manual-pgsql`; + const workflowName = `${data.workflowName}-pg`; + const dataSourceName = `cypress-${data.dataSourceName}-manual-pgsql`; cy.get(commonSelectors.globalDataSourceIcon).click(); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "postgresql", [ { key: "connection_type", value: "manual", encrypted: false }, @@ -87,7 +87,7 @@ describe("Workflows Export/Import Sanity", () => { ] ); - cy.get(dataSourceSelector.dataSourceNameButton(dsName)) + cy.get(dataSourceSelector.dataSourceNameButton(dataSourceName)) .should("be.visible") .click(); @@ -98,9 +98,10 @@ describe("Workflows Export/Import Sanity", () => { cy.reload(); - cy.createWorkflowApp(wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.postgresqlNodeName)).click({ force: true, @@ -120,12 +121,14 @@ describe("Workflows Export/Import Sanity", () => { ); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); - cy.exportWorkflowApp(wfName); - - importWorkflowApp(wfName, workflowsText.exportFixturePath); + cy.exportWorkflowApp(workflowName); + cy.apiDeleteWorkflow(workflowName); + importWorkflowApp(workflowName, workflowsText.exportFixturePath); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); + + cy.apiDeleteWorkflow(workflowName); - deleteWorkflowAndDS(wfName, dsName); + cy.apiDeleteGDS(dataSourceName); cy.task("deleteFile", workflowsText.exportFixturePath); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js index d6fda50df5..82fce2e134 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js @@ -5,6 +5,7 @@ import { workflowSelector } from "Selectors/workflows"; import { enterJsonInputInStartNode, revealWorkflowToken, + navigateBackToWorkflowsDashboard } from "Support/utils/workFlows"; const data = {}; @@ -13,14 +14,14 @@ describe("Workflows with Webhooks", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("Creating workflows with runjs, triggering via webhook, and validating execution", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -64,6 +65,6 @@ describe("Workflows with Webhooks", () => { }); }); }); - cy.deleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js index 4aba89e079..3bb762db9d 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js @@ -15,17 +15,17 @@ describe("Workflows features", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); - data.appName = `${data.wfName}-wf-app`; - data.childWFName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); - data.parentWFName = `${data.wfName}-wf-app`; + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.appName = `${data.workflowName}-wf-app`; + data.childWorkflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.parentWorkflowName = `${data.workflowName}-wf-app`; data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("Creating workflow with long string input and validating execution", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(workflowsText.longStringJsonText); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -45,11 +45,13 @@ describe("Workflows features", () => { workflowsText.responseNodeQuery ); cy.verifyTextInResponseOutput(workflowsText.longStringJsonText); - cy.deleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); it("Creating workflow with Node Preview Validation and execution", () => { - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); + enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -71,12 +73,14 @@ describe("Workflows features", () => { workflowsText.responseNodeQuery ); cy.verifyTextInResponseOutput(workflowsText.jsonValuePlaceholder); - cy.deleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); // Need to run after bug fixes it("Creating workflow inside Workflow and validating execution", () => { - cy.createWorkflowApp(data.childWFName); + cy.apiCreateWorkflow(data.childWorkflowName) + cy.openWorkflow(); + enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -97,9 +101,8 @@ describe("Workflows features", () => { ); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - navigateBackToWorkflowsDashboard(); - - cy.createWorkflowApp(data.parentWFName); + cy.apiCreateWorkflow(data.parentWorkflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.workflowNodeLabel); @@ -109,9 +112,9 @@ describe("Workflows features", () => { cy.get('input[id^="react-select-"]') .eq(1) - .type(data.childWFName, { force: true }); + .type(data.childWorkflowName, { force: true }); cy.get(".react-select__option") - .contains(data.childWFName) + .contains(data.childWorkflowName) .click({ force: true }); cy.get("body").click(50, 50); @@ -122,12 +125,13 @@ describe("Workflows features", () => { workflowsText.workflowResponseNodeQuery ); // cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - cy.deleteWorkflow(data.childWFName); - cy.deleteWorkflowfromDashboard(data.parentWFName); + cy.apiDeleteWorkflow(data.childWorkflowName); + cy.apiDeleteWorkflow(data.parentWorkflowName); }); it("Creating workflow with large datasets and validating execution", () => { - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.workflowName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -152,7 +156,6 @@ describe("Workflows features", () => { verifyTextInResponseOutputLimited( workflowsText.responseNodeExpectedValueTextForLargeDataset ); - - cy.deleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); }); diff --git a/cypress-tests/cypress/fixtures/license/license.json b/cypress-tests/cypress/fixtures/license/license.json new file mode 100644 index 0000000000..0b53c42c4f --- /dev/null +++ b/cypress-tests/cypress/fixtures/license/license.json @@ -0,0 +1,24 @@ +{ + "basic": { + "planName": "Basic", + "Apps": 2, + "Workspaces": 1, + "Total Users": 52, + "Builders": 2, + "End Users": 50, + "Super Admins": 1, + "Workflows": 2, + "Tables": "Unlimited" + }, + "enterprise": { + "planName": "Enterprise", + "Apps": 3, + "Workspaces": 2, + "Total Users": 3, + "Builders": 2, + "End Users": 1, + "Super Admins": 1, + "Workflows": 2, + "Tables": 2 + } +} diff --git a/cypress-tests/cypress/support/e2e.js b/cypress-tests/cypress/support/e2e.js index 157e580be7..fc2894d12e 100644 --- a/cypress-tests/cypress/support/e2e.js +++ b/cypress-tests/cypress/support/e2e.js @@ -14,11 +14,14 @@ // *********************************************************** // Import commands.js using ES2015 syntax: +import "cypress-real-events/support"; import "../commands/commands"; import "../commands/apiCommands"; +import "../commands/workflowsApiCommands"; import '../commands/workflowCommands'; import '../commands/platform/platformApiCommands'; import "@cypress/code-coverage/support"; +import "cypress-real-events"; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/cypress-tests/cypress/support/utils/apps.js b/cypress-tests/cypress/support/utils/apps.js index 4041c3ce46..5f2848025c 100644 --- a/cypress-tests/cypress/support/utils/apps.js +++ b/cypress-tests/cypress/support/utils/apps.js @@ -1,4 +1,4 @@ -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; +import { commonWidgetSelector } from "Selectors/common"; import { appPromote } from "Support/utils/platform/multiEnv"; const slugValidations = [ @@ -76,16 +76,20 @@ export const setUpSlug = (slug) => { cy.get(commonWidgetSelector.modalCloseButton).click(); }; -export const setupAppWithSlug = (appName, slug) => { +export const setupAppWithSlug = (appName, slug, appType = 'private') => { + const defaultLayout = { + desktop: { top: 90, left: 9, width: 6, height: 40 }, + mobile: { top: 90, left: 9, width: 6, height: 40 }, + }; cy.apiCreateApp(appName); - cy.apiAddComponentToApp(appName, "text1"); + cy.apiAddComponentToApp(appName, appType, defaultLayout, "Text", appType); cy.ifEnv("Enterprise", () => { cy.openApp( "", Cypress.env("workspaceId"), Cypress.env("appId"), - commonWidgetSelector.draggableWidget("text1") + commonWidgetSelector.draggableWidget(appType) ); appPromote("development", "production"); }); diff --git a/cypress-tests/cypress/support/utils/common.js b/cypress-tests/cypress/support/utils/common.js index bd461ec006..a15c7ee5cf 100644 --- a/cypress-tests/cypress/support/utils/common.js +++ b/cypress-tests/cypress/support/utils/common.js @@ -7,6 +7,7 @@ import { import { profileSelector } from "Selectors/profile"; import { appPromote } from "Support/utils/platform/multiEnv"; import { commonText, path } from "Texts/common"; +import { commonEeSelectors } from "Selectors/eeCommon"; export const navigateToProfile = () => { cy.get(commonSelectors.settingsIcon).click(); @@ -101,15 +102,17 @@ export const navigateToAppEditor = (appName) => { }; export const viewAppCardOptions = (appName) => { - cy.get(".homepage-app-card .home-app-card-header .menu-ico").then(($el) => { - $el[0].style.setProperty("visibility", "visible", "important"); - }); + cy.contains('.homepage-app-card', appName).within(() => { + cy.get('.home-app-card-header .menu-ico') + .then(($el) => { + $el[0].style.setProperty('visibility', 'visible', 'important'); + }); - cy.get('[data-cy="app-card-menu-icon"]').click(); + cy.get('[data-cy="app-card-menu-icon"]').click(); + }); }; export const viewFolderCardOptions = (folderName) => { - cy.reloadAppForTheElement(folderName); cy.get(commonSelectors.folderListcard(folderName)) .parent() .within(() => { @@ -128,7 +131,7 @@ export const verifyModal = (title, buttonText, inputFiledSelector) => { cy.get(commonSelectors.buttonSelector(commonText.cancelButton)) .should("be.visible") .and("have.text", commonText.cancelButton); - cy.get(commonSelectors.buttonSelector(buttonText)) + cy.get(commonSelectors.buttonSelector(buttonText)).first() .should("be.visible") .and("have.text", buttonText); @@ -184,7 +187,6 @@ export const searchUser = (email) => { }; export const selectAppCardOption = (appName, appCardOption) => { - cy.wait(1000); viewAppCardOptions(appName); cy.get(appCardOption).should("be.visible").click(); }; @@ -251,3 +253,9 @@ export const fillInputField = (data) => { cy.get(inputSelector).type(`{selectall}{backspace}${value}`); }); }; + +export const navigateToSettingPage = () => { + cy.get(commonSelectors.settingsIcon).click(); + cy.get(commonEeSelectors.instanceSettingIcon).click(); + cy.get(commonSelectors.pageSectionHeader).should("be.visible"); +}; diff --git a/cypress-tests/cypress/support/utils/dashboard.js b/cypress-tests/cypress/support/utils/dashboard.js index dbc090735b..0133ca2c3a 100644 --- a/cypress-tests/cypress/support/utils/dashboard.js +++ b/cypress-tests/cypress/support/utils/dashboard.js @@ -1,13 +1,13 @@ import { commonSelectors } from "Selectors/common"; import { dashboardSelector } from "Selectors/dashboard"; -import { dashboardText } from "Texts/dashboard"; -import { commonText } from "Texts/common"; import { - viewAppCardOptions, - verifyModal, - closeModal, cancelModal, + closeModal, + verifyModal, + viewAppCardOptions, } from "Support/utils/common"; +import { commonText } from "Texts/common"; +import { dashboardText } from "Texts/dashboard"; export const modifyAndVerifyAppCardIcon = (appName) => { var random = function (obj) { diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js index 3238661dd0..020e46a735 100644 --- a/cypress-tests/cypress/support/utils/dataSource.js +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -1,10 +1,9 @@ -import { postgreSqlSelector } from "Selectors/postgreSql"; -import { postgreSqlText } from "Texts/postgreSql"; -import { cyParamName } from "Selectors/common"; -import { commonSelectors } from "Selectors/common"; +import { commonSelectors, cyParamName } from "Selectors/common"; import { dataSourceSelector } from "Selectors/dataSource"; +import { postgreSqlSelector } from "Selectors/postgreSql"; import { navigateToAppEditor } from "Support/utils/common"; import { verifyAppDelete } from "Support/utils/dashboard"; +import { postgreSqlText } from "Texts/postgreSql"; export const verifyCouldnotConnectWithAlert = (alertText) => { cy.get(postgreSqlSelector.connectionFailedText, { diff --git a/cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js b/cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js new file mode 100644 index 0000000000..c8ee8fc7f0 --- /dev/null +++ b/cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js @@ -0,0 +1,38 @@ +export const renameApp = (name) => { + cy.get('[data-cy="edit-app-name-button"]').click(); + cy.get("[data-cy='app-name-input']").type(`{selectAll}{backspace}${name}`, { force: true }); + cy.get("[data-cy='rename-app']").click(); +}; + +export const verifyAppName = (name) => { + cy.get('[data-cy="edit-app-name-button"]').should("have.text", name); +} + +export const verifyCurrentEnvironment = (envName) => { + cy.get('[data-cy="list-current-env-name"]').should("have.text", envName); +} + +export const verifyCurrentVersion = (version) => { + cy.get('[data-cy*="-current-version-text"]').should("have.text", version); +} + +export const addNewVersion = (newVersion, fromVersion) => { + cy.get('[data-cy*="-current-version-text"]').click(); + cy.get('[data-cy="create-new-version-button"]').click(); + if (fromVersion) { + cy.get('[data-cy="create-version-from-input-field"]').click(); + cy.contains('[id*="react-select"]', fromVersion).click(); + } + cy.get('[data-cy="version-name-input-field"]').type(newVersion, { force: true }); + cy.get('[data-cy="create-new-version-button"]').click(); + cy.verifyToastMessage("Version Created"); +}; + +export const promoteEnv = () => { + cy.get('[data-cy="promote-button"]').first().click(); + cy.get('[data-cy="promote-button"]').last().click(); +} + + + +// import { renameApp, verifyAppName, verifyCurrentEnvironment, verifyCurrentVersion, addNewVersion, promoteEnv } from 'cypress-tests/cypress/support/utils/editor/editorHeaderOperations'; \ No newline at end of file diff --git a/cypress-tests/cypress/support/utils/exportImport.js b/cypress-tests/cypress/support/utils/exportImport.js index 552cebdf1d..57b393d6ce 100644 --- a/cypress-tests/cypress/support/utils/exportImport.js +++ b/cypress-tests/cypress/support/utils/exportImport.js @@ -1,12 +1,11 @@ +import { commonSelectors } from "Selectors/common"; import { appVersionSelectors, exportAppModalSelectors, importSelectors, } from "Selectors/exportImport"; -import { exportAppModalText, appVersionText } from "Texts/exportImport"; -import { commonSelectors } from "Selectors/common"; -import { verifyModal, commonWidgetSelector } from "Support/utils/common"; -import { importText } from "Texts/exportImport"; +import { verifyModal } from "Support/utils/common"; +import { appVersionText, exportAppModalText, importText } from "Texts/exportImport"; export const verifyElementsOfExportModal = ( currentVersionName, @@ -137,3 +136,122 @@ export const importAndVerifyApp = (filePath, expectedToast) => { } }; +export const verifyImportModalElements = (expectedAppName) => { + cy.get(importSelectors.importAppTitle).verifyVisibleElement("have.text", "Import app"); + cy.get(commonSelectors.appNameLabel).verifyVisibleElement("have.text", "App name"); + cy.get(commonSelectors.appNameInput) + .should("be.visible") + .and("have.value", expectedAppName); + cy.get(commonSelectors.appNameInfoLabel).verifyVisibleElement( + "have.text", + "App name must be unique and max 50 characters" + ); + cy.get(commonSelectors.cancelButton) + .should("be.visible") + .and("have.text", "Cancel"); + cy.get(commonSelectors.importAppButton).verifyVisibleElement("have.text", "Import app"); +}; + +export const setupDataSourceWithConstants = (dsEnv, password = Cypress.env("pg_password")) => { + cy.apiUpdateDataSource("postgresql", dsEnv, { + options: [{ + key: "password", + value: password, + encrypted: true, + }], + }); +}; + +export const validateExportedAppStructure = ( + appData, + expectedAppName, + options = {} +) => { + const { + validateComponents = true, + validateQueries = true, + validateVersions = true, + validateTooljetDB = true, + expectedVersions = ["v1", "v2", "v3"], + } = options; + + // Validate the app name + const appNameFromFile = appData.app[0].definition.appV2.name; + expect(appNameFromFile).to.equal(expectedAppName); + + // Validate the schema for the student table in tooljetdb + if (validateTooljetDB) { + const tooljetDatabase = appData.tooljet_database.find( + (db) => db.table_name === "student" + ); + expect(tooljetDatabase).to.exist; + expect(tooljetDatabase.schema).to.exist; + } + + // Validate components + if (validateComponents) { + const components = appData.app[0].definition.appV2.components; + + const text2Component = components.find( + (component) => component.name === "text2" + ); + expect(text2Component).to.exist; + expect(text2Component.properties.text.value).to.equal( + "{{constants.pageHeader}}" + ); + + const textinput1 = components.find( + (component) => component.name === "textinput1" + ); + expect(textinput1).to.exist; + expect(textinput1.properties.value.value).to.include("queries"); + + const textinput2 = components.find( + (component) => component.name === "textinput2" + ); + expect(textinput2).to.exist; + expect(textinput2.properties.value.value).to.include("queries"); + + const textinput3 = components.find( + (component) => component.name === "textinput3" + ); + cy.log(JSON.stringify(appData.app[0].definition.appV2.appVersions)); + expect(textinput3).to.exist; + expect(textinput3.properties.value.value).to.include("queries"); + } + + // Validate the data queries + if (validateQueries) { + const dataQueries = appData.app[0].definition.appV2.dataQueries; + + const postgresqlQuery = dataQueries.find( + (query) => query.name === "postgresql1" + ); + expect(postgresqlQuery).to.exist; + expect(postgresqlQuery.options.query).to.include( + "Select * from {{secrets.db_name}}" + ); + + const restapiQuery = dataQueries.find((query) => query.name === "restapi1"); + expect(restapiQuery).to.exist; + expect(restapiQuery.options.url).to.equal( + "https://jsonplaceholder.typicode.com/users/1" + ); + + const tooljetdbQuery = dataQueries.find( + (query) => query.name === "tooljetdb1" + ); + expect(tooljetdbQuery).to.exist; + expect(tooljetdbQuery.options.operation).to.equal("list_rows"); + } + + // Validate app versions + if (validateVersions) { + const appVersions = appData.app[0].definition.appV2.appVersions; + expect(appVersions).to.exist; + + const versionNames = appVersions.map((version) => version.name); + expect(versionNames).to.include.members(expectedVersions); + } +}; + diff --git a/cypress-tests/cypress/support/utils/license.js b/cypress-tests/cypress/support/utils/license.js new file mode 100644 index 0000000000..d7cda09ed2 --- /dev/null +++ b/cypress-tests/cypress/support/utils/license.js @@ -0,0 +1,224 @@ +import { licenseSelectors } from "Selectors/license"; +import { licenseText } from "Texts/license"; + +export const switchTabs = (tabTitle) => { + cy.get(licenseSelectors.listOfItems(tabTitle)).should("be.visible").click(); + cy.get(licenseSelectors.tabTitle(tabTitle)).should("have.text", tabTitle); +}; + +export const verifylicenseTab = () => { + cy.get(licenseSelectors.label(licenseText.licenseKeyTab.licenseLabel)).should( + "be.visible" + ); + cy.get(licenseSelectors.licenseTextArea).should( + "have.attr", + "placeholder", + licenseText.licenseKeyTab.enterLicenseKeyPlaceholder + ); +}; + +export const verifySubTabs = (subTabName, subTabDataObj, valuesObj) => { + const subTabData = Object.values(subTabDataObj); + + cy.get(licenseSelectors.subTab(subTabName)) + .verifyVisibleElement("have.text", subTabName) + .click(); + + subTabData.forEach((label) => { + const displayLabel = /^Number of\s+/i.test(label) + ? label.replace(/^Number of\s+/i, "") + : label; + + cy.get( + licenseSelectors.numberOfTextLabel(displayLabel) + ).verifyVisibleElement("have.text", label); + + if (valuesObj && valuesObj[displayLabel] !== undefined) { + cy.get(licenseSelectors.inputField(displayLabel)).verifyVisibleElement( + "have.value", + valuesObj[displayLabel] + ); + } + }); +}; + +export const verifyAccessTab = () => { + const accessTabLabels = Object.values(licenseText.accessTab); + accessTabLabels.forEach((accessTabLabel) => { + cy.get(licenseSelectors.label(accessTabLabel)).verifyVisibleElement( + "have.text", + accessTabLabel + ); + cy.get(licenseSelectors.label(accessTabLabel)) + .next(licenseSelectors.circularToggleDisabledIcon) + .should("be.visible"); + }); +}; + +export const verifyDomainTab = () => { + cy.get(licenseSelectors.warningIcon).should("be.visible"); + cy.get(licenseSelectors.noDomainLinkedLabel).verifyVisibleElement( + "have.text", + licenseText.domainTab.noDomainLinkedLabel + ); + cy.get(licenseSelectors.noDomainInfoText).verifyVisibleElement( + "have.text", + licenseText.domainTab.noDomainInfoText + ); +}; + +export const verifyTooltip = ( + selector, + expectedTooltip, + isDisabled = false +) => { + const hoverTarget = isDisabled + ? cy.get(selector).parent().trigger("mouseover", { force: true }) + : cy.get(selector).trigger("mouseover", { force: true }); + + cy.get(".tooltip", { timeout: 3000 }) + .should("be.visible") + .and("contain.text", expectedTooltip); + + hoverTarget.trigger("mouseout", { force: true }); +}; + +const normalizeText = (text) => + text + .trim() + .replace(/[\u00A0\s]+/g, " ") + .replace(/[\u2018\u2019]/g, "'") + .replace(/[\u201C\u201D]/g, '"'); + +const verifyLimitUI = ( + type, + baseLabel, + headingSelector, + infoSelector, + limit +) => { + cy.get(headingSelector) + .invoke("text") + .then((headingText) => { + cy.log(`Heading: ${headingText}`); + cy.get(infoSelector) + .invoke("text") + .then((infoText) => { + cy.log(`Info: ${infoText}`); + + const ratioMatch = headingText.match(/(\d+)\/(\d+)/); + + if (ratioMatch) { + const [_, currentCount, headingLimit] = ratioMatch.map(Number); + cy.log(`Current ${type}: ${currentCount}`); + + expect(headingLimit).to.equal(limit); + expect(headingText).to.match( + new RegExp(`${baseLabel}\\s+limit\\s+nearing`, "i") + ); + expect(infoText).to.match(/nearing/i); + } else { + expect(headingText).to.match( + new RegExp(`${baseLabel}\\s+limit\\s+reached`, "i") + ); + expect(infoText).to.contain( + `reached your limit for number of ${type}` + ); + } + }); + }); +}; + +const verifyFeatureBanner = (cyPrefix, expectedHeading = null) => { + const isSmallBanner = cyPrefix === "edit-user"; + const headingSelector = isSmallBanner + ? licenseSelectors.licenseBannerHeading + : licenseSelectors.limitHeading(cyPrefix); + const infoSelector = isSmallBanner + ? licenseSelectors.licenseBannerInfo + : licenseSelectors.limitInfo(cyPrefix); + + cy.get(headingSelector) + .should("be.visible") + .invoke("text") + .then((headingText) => { + const actual = normalizeText(headingText); + if (expectedHeading) { + const expected = normalizeText(expectedHeading); + expect(actual).to.equal(expected); + } + }); + cy.get("body").then(($body) => { + if ($body.find(infoSelector).length > 0) { + cy.get(infoSelector).then(($el) => { + const text = $el.text().trim(); + if ($el.is(":visible") && text) cy.log(`Feature banner info: ${text}`); + }); + } + }); +}; + +const getLimitFromPlan = (planName, type) => { + return cy.fixture("license/license.json").then((licenseData) => { + const planKey = planName.toLowerCase(); + const plan = licenseData[planKey]; + expect(plan, `Plan "${planName}" should exist in license.json`).to.not.be + .undefined; + + const resourceKeyMap = { + workspaces: "Workspaces", + apps: "Apps", + tables: "Tables", + workflows: "Workflows", + users: "Total Users", + builders: "Builders", + endusers: "End Users", + superadmins: "Super Admins", + }; + + const resourceKey = resourceKeyMap[type]; + if (!resourceKey) return cy.wrap(null); + + const limit = plan[resourceKey]; + expect(limit, `Limit for "${resourceKey}" should exist in ${planName} plan`) + .to.not.be.undefined; + + cy.log(`Using limit from ${planName} plan: ${resourceKey} = ${limit}`); + return cy.wrap(limit); + }); +}; + +export const verifyResourceLimit = ( + resourceType, + limitOrPlan = null, + dataCyPrefix = null, + expectedHeading = null +) => { + const type = resourceType.toLowerCase().trim(); + const cyPrefix = dataCyPrefix || type; + const typeCapitalized = type.charAt(0).toUpperCase() + type.slice(1); + const baseLabel = typeCapitalized.slice(0, -1); + const headingSelector = licenseSelectors.limitHeading(cyPrefix); + const infoSelector = licenseSelectors.limitInfo(cyPrefix); + + const isNumber = typeof limitOrPlan === "number"; + const planName = isNumber ? null : limitOrPlan; + + if (isNumber) { + return verifyLimitUI( + type, + baseLabel, + headingSelector, + infoSelector, + limitOrPlan + ); + } + + getLimitFromPlan(planName, type).then((limit) => { + if (limit === null) { + verifyFeatureBanner(cyPrefix, expectedHeading); + } else { + verifyLimitUI(type, baseLabel, headingSelector, infoSelector, limit); + } + }); +}; diff --git a/cypress-tests/cypress/support/utils/manageGroups.js b/cypress-tests/cypress/support/utils/manageGroups.js index ff93409568..df8c2d921b 100644 --- a/cypress-tests/cypress/support/utils/manageGroups.js +++ b/cypress-tests/cypress/support/utils/manageGroups.js @@ -10,631 +10,7 @@ import { } from "Support/utils/manageUsers"; import { groupsText } from "Texts/manageGroups"; -export const manageGroupsElements = () => { - cy.get('[data-cy="page-title"]').should(($el) => { - expect($el.contents().last().text().trim()).to.eq("Groups"); - }); - - cy.get('[data-cy="user-role-title"]').verifyVisibleElement( - "have.text", - "USER ROLE" - ); - - // Admin Permissions - // Admin List Item Verification - cy.verifyElement(groupsSelector.adminListItem, "Admin"); - cy.verifyElement(groupsSelector.adminTitle, "Admin (1)"); - - // Group Permission Elements Verification - cy.verifyElement( - groupsSelector.createNewGroupButton, - groupsText.createNewGroupButton - ); - cy.verifyElement(groupsSelector.usersLink, groupsText.usersLink); - cy.verifyElement(groupsSelector.permissionsLink, groupsText.permissionsLink); - cy.verifyElement(groupsSelector.granularLink, "Granular access"); - - // Resource Verification - cy.verifyElement( - groupsSelector.textDefaultGroup, - groupsText.textDefaultGroup - ); - cy.verifyElement( - groupsSelector.nameTableHeader, - groupsText.userNameTableHeader - ); - cy.verifyElement( - groupsSelector.emailTableHeader, - groupsText.emailTableHeader - ); - - // Permissions Page Navigation and Verifications - cy.get(groupsSelector.permissionsLink).click(); - cy.get(groupsSelector.helperTextAdminAppAccess) - .eq(0) - .verifyVisibleElement("have.text", groupsText.adminAccessHelperText); - - // Granular Access Verifications - cy.verifyElement(groupsSelector.resourcesApps, groupsText.resourcesApps); - cy.verifyElement( - groupsSelector.permissionsTableHeader, - groupsText.permissionsTableHeader - ); - cy.get(groupsSelector.appsCreateCheck) - .should("be.visible") - .and("be.checked") - .and("be.disabled"); - cy.verifyElement(groupsSelector.appsCreateLabel, groupsText.createLabel); - cy.verifyElement( - groupsSelector.appCreateHelperText, - groupsText.appCreateHelperText - ); - cy.get(groupsSelector.appsDeleteCheck) - .should("be.visible") - .and("be.checked") - .and("be.disabled"); - cy.verifyElement(groupsSelector.appsDeleteLabel, groupsText.deleteLabel); - cy.verifyElement( - groupsSelector.appDeleteHelperText, - groupsText.appDeleteHelperText - ); - - // Folder Permissions - cy.verifyElement( - groupsSelector.resourcesFolders, - groupsText.resourcesFolders - ); - cy.verifyElement( - groupsSelector.foldersCreateLabel, - groupsText.folderCreateLabel - ); - cy.verifyElement( - groupsSelector.foldersHelperText, - groupsText.folderHelperText - ); - cy.get(groupsSelector.foldersCreateCheck) - .should("be.visible") - .and("be.checked") - .and("be.disabled"); - - // Workspace Variable Permissions - cy.verifyElement( - groupsSelector.resourcesWorkspaceVar, - groupsText.resourcesWorkspaceVar - ); - cy.verifyElement( - groupsSelector.workspaceCreateLabel, - groupsText.workspaceCreateLabel - ); - cy.verifyElement( - groupsSelector.workspaceHelperText, - groupsText.workspaceHelperText - ); - cy.get(groupsSelector.workspaceVarCheckbox) - .should("be.visible") - .and("be.checked") - .and("be.disabled"); - // Granular Permissions - cy.get(groupsSelector.granularLink).click(); - cy.verifyElement(groupsSelector.nameTableHeader, groupsText.nameTableHeader); - cy.verifyElement( - groupsSelector.permissionsTableHeader, - groupsText.granularAccessPermissionHeader - ); - cy.verifyElement( - `${groupsSelector.resourceHeader}:eq(1)`, - groupsText.resourcesTableHeader - ); - cy.verifyElement(groupsSelector.appsText, " Apps"); - cy.get(groupsSelector.appEditRadio) - .should("be.visible") - .and("be.checked") - .and("have.attr", "disabled"); - cy.verifyElement(groupsSelector.appEditLabel, groupsText.appEditLabelText); - cy.verifyElement( - groupsSelector.appEditHelperText, - groupsText.appEditHelperText - ); - cy.get(groupsSelector.appViewRadio) - .should("be.visible") - .and("have.attr", "disabled"); - cy.verifyElement(groupsSelector.appViewLabel, groupsText.appViewLabel); - cy.verifyElement( - groupsSelector.appViewHelperText, - groupsText.appViewHelperText - ); - cy.get(groupsSelector.appHideCheckbox).should("be.visible"); - cy.verifyElement( - groupsSelector.appHideHelperText, - groupsText.appHideHelperText - ); - cy.verifyElement(groupsSelector.addAppButton, groupsText.addButton); - cy.get(groupsSelector.addAppButton).should("be.disabled"); - - //Builder - cy.get(groupsSelector.groupLink("Builder")).click(); - cy.verifyElement(groupsSelector.builderListItem, "Builder"); - cy.verifyElement(groupsSelector.builderTitle, "Builder (0)"); - - // Group Permission Elements Verification - cy.verifyElement( - groupsSelector.createNewGroupButton, - groupsText.createNewGroupButton - ); - cy.verifyElement(groupsSelector.usersLink, groupsText.usersLink); - cy.verifyElement(groupsSelector.permissionsLink, groupsText.permissionsLink); - cy.verifyElement(groupsSelector.granularLink, "Granular access"); - - // Resource Verification - cy.verifyElement( - groupsSelector.textDefaultGroup, - groupsText.textDefaultGroup - ); - cy.get(groupsSelector.usersLink).click(); - cy.verifyElement( - groupsSelector.nameTableHeader, - groupsText.userNameTableHeader - ); - cy.verifyElement( - groupsSelector.emailTableHeader, - groupsText.emailTableHeader - ); - cy.get(groupsSelector.userEmptyPageIcon).should("be.visible"); - cy.get(groupsSelector.userEmptyPageTitle).verifyVisibleElement( - "have.text", - groupsText.userEmptyPageTitle - ); - cy.get(groupsSelector.userEmptyPageHelperText).verifyVisibleElement( - "have.text", - groupsText.userEmptyPageHelperText - ); - - // Granular Access Verifications - cy.get(groupsSelector.permissionsLink).click(); - cy.verifyElement(groupsSelector.resourcesApps, groupsText.resourcesApps); - cy.verifyElement( - groupsSelector.permissionsTableHeader, - groupsText.permissionsTableHeader - ); - - cy.get(groupsSelector.appsCreateCheck).should("be.visible").and("be.checked"); - cy.verifyElement(groupsSelector.appsCreateLabel, groupsText.createLabel); - cy.verifyElement( - groupsSelector.appCreateHelperText, - groupsText.appCreateHelperText - ); - toggleCheckbox( - groupsSelector.appsCreateCheck, - commonSelectors.toastMessage, - groupsText.permissionUpdatedToast - ); - - cy.get(groupsSelector.appsDeleteCheck).should("be.visible").and("be.checked"); - cy.verifyElement(groupsSelector.appsDeleteLabel, groupsText.deleteLabel); - cy.verifyElement( - groupsSelector.appDeleteHelperText, - groupsText.appDeleteHelperText - ); - toggleCheckbox( - groupsSelector.appsDeleteCheck, - commonSelectors.toastMessage, - groupsText.permissionUpdatedToast - ); - - // Folder Permissions - cy.verifyElement( - groupsSelector.resourcesFolders, - groupsText.resourcesFolders - ); - cy.get(groupsSelector.foldersCreateCheck) - .should("be.visible") - .and("be.checked"); - cy.verifyElement( - groupsSelector.foldersCreateLabel, - groupsText.folderCreateLabel - ); - cy.verifyElement( - groupsSelector.foldersHelperText, - groupsText.folderHelperText - ); - toggleCheckbox( - groupsSelector.foldersCreateCheck, - commonSelectors.toastMessage, - groupsText.permissionUpdatedToast - ); - - // Workspace Variable Permissions - cy.verifyElement( - groupsSelector.resourcesWorkspaceVar, - groupsText.resourcesWorkspaceVar - ); - cy.get(groupsSelector.workspaceVarCheckbox) - .should("be.visible") - .and("be.checked"); - cy.verifyElement( - groupsSelector.workspaceCreateLabel, - groupsText.workspaceCreateLabel - ); - cy.verifyElement( - groupsSelector.workspaceHelperText, - groupsText.workspaceHelperText - ); - toggleCheckbox( - groupsSelector.workspaceVarCheckbox, - commonSelectors.toastMessage, - groupsText.permissionUpdatedToast - ); - - // Granular Permissions - cy.get(groupsSelector.granularLink).click(); - - cy.verifyElement(groupsSelector.nameTableHeader, groupsText.nameTableHeader); - cy.verifyElement( - groupsSelector.permissionsTableHeader, - groupsText.granularAccessPermissionHeader - ); - cy.verifyElement( - `${groupsSelector.resourceHeader}:eq(1)`, - groupsText.resourcesTableHeader - ); - cy.verifyElement(groupsSelector.appsText, groupsText.appsLink); - cy.get(groupsSelector.appEditRadio) - .should("be.visible") - .and("be.checked") - .and("be.enabled"); - cy.verifyElement(groupsSelector.appEditLabel, groupsText.appEditLabelText); - cy.verifyElement( - groupsSelector.appEditHelperText, - groupsText.appEditHelperText - ); - cy.get(groupsSelector.appViewRadio).should("be.visible").and("be.enabled"); - cy.verifyElement(groupsSelector.appViewLabel, groupsText.appViewLabel); - cy.verifyElement( - groupsSelector.appViewHelperText, - groupsText.appViewHelperText - ); - cy.get(groupsSelector.appHideCheckbox) - .should("be.visible") - .and("be.disabled"); - cy.verifyElement( - groupsSelector.appHideLabel, - groupsText.appHideLabelPermissionModal - ); - cy.verifyElement( - groupsSelector.appHideHelperText, - groupsText.appHideHelperText - ); - - cy.get(groupsSelector.granularAccessPermission) - .trigger("mouseenter") - .click({ force: true }); - cy.get(".modal-base").should("be.visible"); - cy.get(groupsSelector.deletePermissionIcon) - .should("be.visible") - .and("be.enabled"); - cy.get(groupsSelector.deletePermissionIcon).click(); - cy.get(".confirm-dialogue-modal").should("be.visible"); - cy.verifyElement(groupsSelector.deleteMessage, groupsText.deleteMessage); - cy.get(groupsSelector.yesButton).should("be.visible").and("be.enabled"); - cy.get(groupsSelector.cancelButton).should("be.visible").and("be.enabled"); - cy.contains("Cancel").click(); - cy.get(groupsSelector.granularAccessPermission) - .trigger("mouseenter") - .click({ force: true }); - cy.verifyElement( - `${groupsSelector.addEditPermissionModalTitle}:eq(2)`, - groupsText.editPermissionModalTitle - ); - permissionModal(); - - cy.get(groupsSelector.customradio).should("be.visible").should("be.disabled"); - cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); - cy.verifyElement( - groupsSelector.customHelperText, - groupsText.customHelperText - ); - - cy.verifyElement(groupsSelector.confimButton, groupsText.updateButtonText); - cy.get(groupsSelector.confimButton).should("be.disabled"); - cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); - cy.get(groupsSelector.cancelButton).click(); - - //Add modal - cy.verifyElement(groupsSelector.addAppButton, groupsText.addButton); - cy.get(groupsSelector.addAppButton) - .should("be.visible") - .and("be.enabled") - .click(); - cy.verifyElement( - `${groupsSelector.addEditPermissionModalTitle}:eq(2)`, - groupsText.addPermissionModalTitle - ); - permissionModal(); - cy.get(groupsSelector.customradio).should("be.visible").should("be.disabled"); - cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); - cy.verifyElement( - groupsSelector.customHelperText, - groupsText.customHelperText - ); - cy.verifyElement(groupsSelector.confimButton, groupsText.addButtonText); - cy.get(groupsSelector.confimButton).should("be.disabled"); - cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); - cy.get(groupsSelector.cancelButton).click(); - - //End User - - cy.get(groupsSelector.groupLink("End-user")).click(); - cy.get(groupsSelector.groupLink("End-user")).verifyVisibleElement( - "have.text", - "End-user" - ); - - cy.get(groupsSelector.enduserTitle).verifyVisibleElement( - "have.text", - "End-user (0)" - ); - - cy.verifyElement( - groupsSelector.createNewGroupButton, - groupsText.createNewGroupButton - ); - cy.verifyElement(groupsSelector.usersLink, groupsText.usersLink); - cy.verifyElement(groupsSelector.permissionsLink, groupsText.permissionsLink); - cy.verifyElement(groupsSelector.granularLink, "Granular access"); - - // Resource Verification - cy.verifyElement( - groupsSelector.textDefaultGroup, - groupsText.textDefaultGroup - ); - cy.get(groupsSelector.usersLink).click(); - cy.verifyElement( - groupsSelector.nameTableHeader, - groupsText.userNameTableHeader - ); - cy.verifyElement( - groupsSelector.emailTableHeader, - groupsText.emailTableHeader - ); - cy.get(groupsSelector.userEmptyPageIcon).should("be.visible"); - cy.get(groupsSelector.userEmptyPageTitle).verifyVisibleElement( - "have.text", - groupsText.userEmptyPageTitle - ); - cy.get(groupsSelector.userEmptyPageHelperText).verifyVisibleElement( - "have.text", - groupsText.userEmptyPageHelperText - ); - - // Granular Access Verifications - cy.get(groupsSelector.permissionsLink).click(); - cy.get(groupsSelector.helperTextAdminAppAccess) - .eq(0) - .verifyVisibleElement("have.text", groupsText.enduserAccessHelperText); - cy.verifyElement(groupsSelector.resourcesApps, groupsText.resourcesApps); - cy.verifyElement( - groupsSelector.permissionsTableHeader, - groupsText.permissionsTableHeader - ); - - cy.get(groupsSelector.appsCreateCheck) - .should("be.visible") - .and("not.be.checked") - .and("be.disabled"); - cy.verifyElement(groupsSelector.appsCreateLabel, groupsText.createLabel); - cy.verifyElement( - groupsSelector.appCreateHelperText, - groupsText.appCreateHelperText - ); - cy.get(groupsSelector.appsDeleteCheck) - .should("be.visible") - .and("not.be.checked") - .and("be.disabled"); - cy.verifyElement(groupsSelector.appsDeleteLabel, groupsText.deleteLabel); - cy.verifyElement( - groupsSelector.appDeleteHelperText, - groupsText.appDeleteHelperText - ); - - // Folder Permissions - cy.verifyElement( - groupsSelector.resourcesFolders, - groupsText.resourcesFolders - ); - cy.get(groupsSelector.foldersCreateCheck) - .should("be.visible") - .and("not.be.checked") - .and("be.disabled"); - cy.verifyElement( - groupsSelector.foldersCreateLabel, - groupsText.folderCreateLabel - ); - cy.verifyElement( - groupsSelector.foldersHelperText, - groupsText.folderHelperText - ); - - // Workspace Variable Permissions - cy.verifyElement( - groupsSelector.resourcesWorkspaceVar, - groupsText.resourcesWorkspaceVar - ); - cy.get(groupsSelector.workspaceVarCheckbox) - .should("be.visible") - .and("not.be.checked") - .and("be.disabled"); - cy.verifyElement( - groupsSelector.workspaceCreateLabel, - groupsText.workspaceCreateLabel - ); - cy.verifyElement( - groupsSelector.workspaceHelperText, - groupsText.workspaceHelperText - ); - - // Granular Permissions - cy.get(groupsSelector.granularLink).click(); - cy.verifyElement(groupsSelector.nameTableHeader, groupsText.nameTableHeader); - cy.verifyElement( - groupsSelector.permissionsTableHeader, - groupsText.granularAccessPermissionHeader - ); - cy.verifyElement( - `${groupsSelector.resourceHeader}:eq(1)`, - groupsText.resourcesTableHeader - ); - cy.verifyElement(groupsSelector.appsText, groupsText.appsLink); - cy.get(groupsSelector.appEditRadio) - .should("be.visible") - .and("not.be.checked") - .and("be.disabled"); - cy.verifyElement(groupsSelector.appEditLabel, groupsText.appEditLabelText); - cy.verifyElement( - groupsSelector.appEditHelperText, - groupsText.appEditHelperText - ); - cy.get(groupsSelector.appViewRadio) - .should("be.visible") - .and("be.disabled") - .and("be.checked"); - cy.verifyElement(groupsSelector.appViewLabel, groupsText.appViewLabel); - cy.verifyElement( - groupsSelector.appViewHelperText, - groupsText.appViewHelperText - ); - cy.get(groupsSelector.appHideCheckbox).should("be.visible").and("be.enabled"); - cy.verifyElement( - groupsSelector.appHideLabel, - groupsText.appHideLabelPermissionModal - ); - cy.verifyElement( - groupsSelector.appHideHelperText, - groupsText.appHideHelperText - ); - - cy.get(groupsSelector.granularAccessPermission) - .trigger("mouseenter") - .click({ force: true }); - cy.get(".modal-base").should("be.visible"); - cy.get(groupsSelector.deletePermissionIcon) - .should("be.visible") - .and("be.enabled"); - cy.get(groupsSelector.deletePermissionIcon).click(); - cy.get(".confirm-dialogue-modal").should("be.visible"); - cy.verifyElement(groupsSelector.deleteMessage, groupsText.deleteMessage); - cy.get(groupsSelector.yesButton).should("be.visible").and("be.enabled"); - cy.get(groupsSelector.cancelButton).should("be.visible").and("be.enabled"); - cy.contains("Cancel").click(); - cy.get(groupsSelector.granularAccessPermission) - .trigger("mouseenter") - .click({ force: true }); - cy.verifyElement( - `${groupsSelector.addEditPermissionModalTitle}:eq(2)`, - groupsText.editPermissionModalTitle - ); - permissionModal(); - - cy.get(groupsSelector.customradio).should("be.visible").should("be.disabled"); - cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); - cy.verifyElement( - groupsSelector.customHelperText, - groupsText.customHelperText - ); - - cy.verifyElement(groupsSelector.confimButton, groupsText.updateButtonText); - cy.get(groupsSelector.confimButton).should("be.disabled"); - cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); - cy.get(groupsSelector.cancelButton).click(); - //Add Modal - cy.verifyElement(groupsSelector.addAppButton, groupsText.addButton); - cy.get(groupsSelector.addAppButton) - .should("be.visible") - .and("be.enabled") - .click(); - cy.verifyElement( - `${groupsSelector.addEditPermissionModalTitle}:eq(2)`, - groupsText.addPermissionModalTitle - ); - permissionModal(); - cy.get(groupsSelector.customradio).should("be.visible").should("be.disabled"); - cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); - cy.verifyElement( - groupsSelector.customHelperText, - groupsText.customHelperText - ); - cy.verifyElement(groupsSelector.confimButton, groupsText.addButtonText); - cy.get(groupsSelector.confimButton).should("be.disabled"); - cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); - cy.get(groupsSelector.cancelButton).click(); -}; - -const toggleCheckbox = (selector, toastSelector, toastMessage) => { - cy.get(selector).should("be.visible").uncheck(); - cy.verifyToastMessage(toastSelector, toastMessage); - cy.get(selector).check(); - cy.verifyToastMessage(toastSelector, toastMessage); -}; - -// Permission Modal Verification -export const permissionModal = () => { - cy.verifyElement( - groupsSelector.permissionNameLabel, - groupsText.permissionNameLabel - ); - cy.verifyElement( - groupsSelector.permissionNameHelperText, - groupsText.permissionNameHelperText - ); - - cy.verifyElement(groupsSelector.permissionLabel, groupsText.permissionLabel); - cy.verifyElement( - groupsSelector.editPermissionLabel, - groupsText.editPermissionLabel - ); - cy.verifyElement( - groupsSelector.editPermissionHelperText, - groupsText.editPermissionHelperText - ); - - cy.verifyElement( - groupsSelector.viewPermissionLabel, - groupsText.viewPermissionLabel - ); - cy.verifyElement( - groupsSelector.viewPermissionHelperText, - groupsText.viewPermissionHelperText - ); - - cy.get(groupsSelector.hidePermissionInput).should("be.visible"); - cy.verifyElement(groupsSelector.resourceLabel, groupsText.resourcesheader); - cy.get(groupsSelector.resourceContainer).should("be.visible"); - cy.get(groupsSelector.allAppsRadio).should("be.visible").and("be.checked"); - cy.verifyElement(groupsSelector.allAppsLabel, groupsText.allAppsLabel); - cy.verifyElement( - groupsSelector.allAppsHelperText, - groupsText.allAppsHelperText - ); -}; - -export const addAppToGroup = (appName) => { - cy.get(groupsSelector.appsLink).click(); - cy.wait(500); - cy.get(groupsSelector.appSearchBox).click(); - cy.wait(500); - cy.get(groupsSelector.searchBoxOptions).contains(appName).click(); - cy.get(groupsSelector.selectAddButton).click(); - cy.contains("tr", appName) - .parent() - .within(() => { - cy.get("td input").eq(1).check(); - }); - cy.verifyToastMessage( - commonSelectors.toastMessage, - "App permissions updated" - ); -}; - -export const createGroup = (groupName) => { +export const apiCreateGroup = (groupName) => { return cy.getAuthHeaders().then((headers) => { return cy .request({ @@ -650,14 +26,16 @@ export const createGroup = (groupName) => { }); }; -export const apiDeleteGroup = (groupId) => { - cy.getAuthHeaders().then((headers) => { - cy.request({ - method: "DELETE", - url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}`, - headers: headers, - }).then((response) => { - expect(response.status).to.equal(200); +export const apiDeleteGroup = (groupName) => { + cy.apiGetGroupId(groupName).then((groupId) => { + cy.getAuthHeaders().then((headers) => { + cy.request({ + method: "DELETE", + url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}`, + headers: headers, + }).then((response) => { + expect(response.status).to.equal(200); + }); }); }); }; @@ -669,52 +47,6 @@ export const deleteGroup = (groupName, workspaceId) => { }); }; -export const createGroupAddAppAndUserToGroup = (groupName, email) => { - cy.getAuthHeaders().then((headers) => { - createGroup(groupName).then((groupId) => { - // Add app to group - cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions/app`, - headers: headers, - body: { - name: "Apps", - type: "app", - groupId: groupId, - isAll: false, - createResourcePermissionObject: { - canEdit: true, - canView: false, - hideFromDashboard: false, - resourcesToAdd: [{ appId: Cypress.env("appId") }], - }, - }, - }).then((response) => { - expect(response.status).to.equal(201); - }); - cy.wait(2000); - cy.task("dbConnection", { - dbconfig: Cypress.env("app_db"), - sql: `select id from users where email='${email}';`, - }).then((resp) => { - const userId = resp.rows[0].id; - // Add user to group - cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/users`, - headers: headers, - body: { - userIds: [userId], - groupId: groupId, - }, - }).then((response) => { - expect(response.status).to.equal(201); - }); - }); - }); - }); -}; - export const OpenGroupCardOption = (groupName) => { cy.get(groupsSelector.groupLink(groupName)) .trigger("mouseenter") @@ -775,61 +107,6 @@ export const groupPermission = ( }); }; -export const duplicateGroup = () => { - OpenGroupCardOption(groupName); - cy.get(groupsSelector.duplicateOption).click(); -}; - -export const updateRoleUI = (user, role, email, message) => { - cy.get(groupsSelector.groupLink(user)).click(); - cy.get(groupsSelector.usersLink).click(); - cy.get(`[data-cy="${email}-user-row"] > :nth-child(3)`).click(); - cy.get('[data-cy="modal-title"] > .tj-text-md').should( - "have.text", - "Edit user role" - ); - cy.get('[data-cy="user-email"]').should("have.text", email); - cy.get(groupsSelector.userRoleLabel).should("have.text", groupsText.userRole); - cy.get(groupsSelector.warningText).should( - "have.text", - groupsText.warningText - ); - cy.get(groupsSelector.cancelButton) - .should("have.text", groupsText.cancelButton) - .and("be.enabled"); - cy.get(groupsSelector.confimButton).should("be.disabled"); - cy.get( - ".css-nwhe5y-container > .react-select__control > .react-select__value-container" - ) - .click() - .type(`${role}{enter}`); - cy.get(groupsSelector.confimButton) - .should("be.enabled") - .and("have.text", groupsText.continueButtonText) - .click(); - cy.get('[data-cy="modal-body"]').should("have.text", message); - cy.get(groupsSelector.cancelButton).click(); - cy.get(`[data-cy="${email}-user-row"] > :nth-child(3)`).click(); - cy.get( - ".css-nwhe5y-container > .react-select__control > .react-select__value-container" - ) - .click() - .type(`${role}{enter}`); - cy.get(groupsSelector.confimButton) - .should("be.enabled") - .and("have.text", groupsText.continueButtonText) - .click(); - cy.get(groupsSelector.confimButton).click(); - if (user != "Admin") { - cy.verifyToastMessage( - commonSelectors.toastMessage, - groupsText.roleUpdateToastMessage - ); - } - cy.get(groupsSelector.groupLink(role)).click(); - cy.get(`[data-cy="${email}-user-row"]`).should("exist"); -}; - export const updateRole = (user, role, email, message = null) => { cy.get(groupsSelector.groupLink(user)).click(); cy.get(groupsSelector.usersLink).click(); @@ -897,25 +174,6 @@ export const inviteUserBasedOnRole = (firstName, email, role = "end-user") => { cy.get(commonSelectors.dashboardIcon).click(); }; -export const verifyBasicPermissions = (canCreate = true) => { - cy.get(commonSelectors.dashboardAppCreateButton).should( - canCreate ? "be.enabled" : "be.disabled" - ); - cy.get(commonSelectors.createNewFolderButton).should( - canCreate ? "exist" : "not.exist" - ); - cy.get('[data-cy="database-icon"]').should(canCreate ? "exist" : "not.exist"); - cy.get(commonSelectors.workspaceConstantsIcon).should( - canCreate ? "exist" : "not.exist" - ); - - cy.ifEnv("Enterprise", () => { - cy.get(commonSelectors.globalDataSourceIcon).should( - canCreate ? "exist" : "not.exist" - ); - }); -}; - export const setupWorkspaceAndInviteUser = ( workspaceName, workspaceSlug, @@ -934,13 +192,6 @@ export const setupWorkspaceAndInviteUser = ( cy.wait(2000); }; -export const verifySettingsAccess = (shouldExist = true) => { - cy.get(commonSelectors.settingsIcon).click(); - cy.get(commonSelectors.workspaceSettings).should( - shouldExist ? "exist" : "not.exist" - ); -}; - export const verifyUserPrivileges = ( expectedButtonState, shouldHaveWorkspaceSettings @@ -974,3 +225,35 @@ export const verifyUserRole = (userIdAlias, expectedRole, expectedGroups) => { }); }); }; + +export const apiAddUserToGroup = (groupId, email) => { + return cy.getAuthHeaders().then((headers) => { + return cy + .request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/organization-users`, + headers: headers, + log: false, + }) + .then((response) => { + expect(response.status).to.equal(200); + const user = response.body.users.find((u) => u.email === email); + const userId = user.user_id; + return cy + .request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/users`, + headers: headers, + body: { + userIds: [userId], + groupId: groupId, + }, + log: false, + }) + .then((addResponse) => { + expect(addResponse.status).to.equal(201); + return userId; + }); + }); + }); +}; diff --git a/cypress-tests/cypress/support/utils/manageSSO.js b/cypress-tests/cypress/support/utils/manageSSO.js index 3a793f16db..9270d1293f 100644 --- a/cypress-tests/cypress/support/utils/manageSSO.js +++ b/cypress-tests/cypress/support/utils/manageSSO.js @@ -1,8 +1,8 @@ import { commonSelectors, cyParamName } from "Selectors/common"; import { ssoSelector } from "Selectors/manageSSO"; -import { ssoText } from "Texts/manageSSO"; import * as common from "Support/utils/common"; import { commonText } from "Texts/common"; +import { ssoText } from "Texts/manageSSO"; export const generalSettings = () => { cy.get(ssoSelector.enableSignUpToggle).check(); diff --git a/cypress-tests/cypress/support/utils/manageUsers.js b/cypress-tests/cypress/support/utils/manageUsers.js index 4212891e90..26d8ad7a18 100644 --- a/cypress-tests/cypress/support/utils/manageUsers.js +++ b/cypress-tests/cypress/support/utils/manageUsers.js @@ -1,13 +1,13 @@ import { commonSelectors, cyParamName } from "Selectors/common"; -import { usersText } from "Texts/manageUsers"; -import { usersSelector } from "Selectors/manageUsers"; import { ssoSelector } from "Selectors/manageSSO"; -import { ssoText } from "Texts/manageSSO"; -import * as common from "Support/utils/common"; -import { commonText } from "Texts/common"; +import { usersSelector } from "Selectors/manageUsers"; import { onboardingSelectors } from "Selectors/onboarding"; -const envVar = Cypress.env("environment"); +import * as common from "Support/utils/common"; import { fillInputField } from "Support/utils/common"; +import { commonText } from "Texts/common"; +import { ssoText } from "Texts/manageSSO"; +import { usersText } from "Texts/manageUsers"; +const envVar = Cypress.env("environment"); export const manageUsersElements = () => { cy.get( @@ -480,7 +480,11 @@ export const openEditUserDetails = ( verifyUserStatusAndMetadata(email, activeStatusText, expectedMetadata); - cy.contains("td", email) + navigateToEditUser(email); +}; + +export const navigateToEditUser = (email) => { + cy.contains("td", email) .parent() .within(() => { cy.get('[data-cy="user-actions-button"]').click(); @@ -489,6 +493,3 @@ export const openEditUserDetails = ( .verifyVisibleElement("have.text", "Edit user details") .click(); }; - - - diff --git a/cypress-tests/cypress/support/utils/onboarding.js b/cypress-tests/cypress/support/utils/onboarding.js index 05ad350b6a..d8599c7166 100644 --- a/cypress-tests/cypress/support/utils/onboarding.js +++ b/cypress-tests/cypress/support/utils/onboarding.js @@ -1,15 +1,10 @@ import { commonSelectors } from "Selectors/common"; -import { commonText } from "Texts/common"; -import { dashboardText } from "Texts/dashboard"; -import { - verifyandModifyUserRole, - verifyandModifySizeOftheCompany, -} from "Support/utils/selfHostSignUp"; -import { navigateToManageUsers, logout } from "Support/utils/common"; import { ssoSelector } from "Selectors/manageSSO"; -import { ssoText } from "Texts/manageSSO"; import { onboardingSelectors } from "Selectors/onboarding"; +import { logout, navigateToManageUsers } from "Support/utils/common"; import { fetchAndVisitInviteLink } from "Support/utils/manageUsers"; +import { commonText } from "Texts/common"; +import { ssoText } from "Texts/manageSSO"; import { onboardingText } from "Texts/onboarding"; export const verifyConfirmEmailPage = (email) => { diff --git a/cypress-tests/cypress/support/utils/platform/customGroups.js b/cypress-tests/cypress/support/utils/platform/customGroups.js new file mode 100644 index 0000000000..cdae98c6fd --- /dev/null +++ b/cypress-tests/cypress/support/utils/platform/customGroups.js @@ -0,0 +1,173 @@ +import { commonSelectors } from "Selectors/common"; +import { commonEeSelectors } from "Selectors/eeCommon"; +import { cyParamName, groupsSelector } from "Selectors/manageGroups"; +import { groupsText } from "Texts/manageGroups"; + +export const createGroupViaUI = (groupName) => { + cy.get(groupsSelector.createNewGroupButton).click(); + cy.get(groupsSelector.addNewGroupModalTitle).verifyVisibleElement( + "have.text", + groupsText.cardTitle + ); + cy.clearAndType(groupsSelector.groupNameInput, groupName); + cy.get(groupsSelector.createGroupButton).should("be.enabled").click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.groupCreatedToast + ); +}; + +export const verifyGroupCreatedInSidebar = (groupName) => { + cy.get(groupsSelector.groupLink(groupName)) + .should("be.visible") + .and("contain.text", groupName); +}; + +export const renameGroupViaUI = (oldName, newName) => { + cy.get(groupsSelector.groupLink(oldName)).click(); + cy.get(groupsSelector.groupNameUpdateLink).should("be.visible").click(); + cy.clearAndType(groupsSelector.groupNameInput, newName); + cy.get(groupsSelector.createGroupButton).click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.groupNameUpdateSucessToast + ); +}; + +export const deleteGroupViaUI = (groupName) => { + cy.get(groupsSelector.groupLink(groupName)).click(); + cy.get(groupsSelector.groupLink(groupName)).realHover(); + cy.wait(2000).then(() => { + cy.get( + `[data-cy="${cyParamName(groupName)}-list-item"] > :nth-child(2) > .tj-base-btn` + ).click({ force: true }); + }); + cy.get(groupsSelector.deleteGroupOption).click(); + cy.get(commonSelectors.buttonSelector("Yes")).click(); +}; + +export const verifyGroupRemovedFromSidebar = (groupName) => { + cy.get(groupsSelector.groupLink(groupName)).should("not.exist"); +}; + +export const addGranularPermissionViaUI = (permissionName, options = {}) => { + const { + resourceType = "app", + permission = "edit", + scope = "all", + resources = [], + } = options; + + cy.ifEnv("Community", () => { + cy.get(groupsSelector.addAppsButton).click(); + }); + cy.ifEnv("Enterprise", () => { + cy.get(groupsSelector.addPermissionButton).click(); + if (resourceType === "app") { + cy.get(groupsSelector.addAppButton).click(); + } else if (resourceType === "workflow") { + cy.get(groupsSelector.addWorkflowButton).click(); + } else if (resourceType === "datasource") { + cy.get(groupsSelector.addDatasourceButton).click(); + } + }); + + cy.clearAndType(groupsSelector.permissionNameInput, permissionName); + + if (resourceType === "app") { + if (permission === "view") { + cy.get(groupsSelector.viewPermissionRadio).check(); + } else if (permission === "edit") { + cy.get(groupsSelector.editPermissionRadio).check(); + } + } else if (resourceType === "workflow") { + if (permission === "execute") { + cy.get(groupsSelector.executeWorkflowradio).check(); + } else if (permission === "build") { + cy.get(groupsSelector.buildWorkflowradio).check(); + } + } else if (resourceType === "datasource") { + if (permission === "buildWith") { + cy.get(groupsSelector.buildWithDatasourceRadio).check(); + } else if (permission === "configure") { + cy.get(groupsSelector.configureDatasourceradio).check(); + } + } + + if (scope === "custom") { + cy.get(groupsSelector.customRadio).check(); + if (resources.length > 0) { + resources.forEach((resource) => { + cy.get(groupsSelector.resourceSelector).click(); + cy.get(groupsSelector.searchBoxOptions).contains(resource).click(); + }); + } + } else { + cy.get(groupsSelector.allAppsRadio).check(); + } + + cy.get(groupsSelector.confimButton).click(); +}; + +export const switchBetweenAllAndCustom = (targetScope) => { + if (targetScope === "all") { + cy.get(groupsSelector.allAppsRadio).check(); + cy.get(groupsSelector.allAppsRadio).should("be.checked"); + cy.get(groupsSelector.customRadio).should("not.be.checked"); + } else if (targetScope === "custom") { + cy.get(groupsSelector.customRadio).check(); + cy.get(groupsSelector.customRadio).should("be.checked"); + cy.get(groupsSelector.allAppsRadio).should("not.be.checked"); + cy.get(".css-b62m3t-container").should("be.visible"); + } +}; + +export const openGroupThreeDotMenu = (groupName) => { + cy.get(groupsSelector.groupLink(groupName)).realHover() + cy.get(groupsSelector.groupLink(groupName)).then(() => { + cy.get('[datacy="groups-list-option-button"]').click(); + }); +}; + +export const verifyDuplicateModal = (originalGroupName) => { + cy.get('[data-cy="modal-title"]') + .should("be.visible") + .and("contain.text", "Duplicate group"); + + cy.verifyElement( + '[data-cy="modal-message"]', + "Duplicate the following parts of the group" + ); + cy.verifyElement('[data-cy="users-label"]', "Users"); + cy.get('[data-cy="users-check-input"]') + .should("be.visible") + .and("be.checked"); + + cy.verifyElement('[data-cy="permissions-label"]', "Permissions"); + cy.get('[data-cy="permissions-check-input"]') + .should("be.visible") + .and("be.checked"); + + cy.verifyElement('[data-cy="apps-label"]', "Apps"); + cy.get('[data-cy="apps-check-input"]').should("be.visible").and("be.checked"); + + cy.ifEnv("Enterprise", () => { + cy.verifyElement('[data-cy="workflows-label"]', "Workflows"); + cy.get('[data-cy="workflows-check-input"]') + .should("be.visible") + .and("be.checked"); + + cy.verifyElement('[data-cy="datasources-label"]', "Datasources"); + cy.get('[data-cy="datasources-check-input"]') + .should("be.visible") + .and("be.checked"); + }); + + cy.verifyElement(groupsSelector.cancelButton, "Cancel"); + cy.get(groupsSelector.cancelButton).should("be.visible").and("be.enabled"); + + cy.verifyElement(commonEeSelectors.confirmButton, "Duplicate"); + cy.get(commonEeSelectors.confirmButton) + .should("be.visible") + .and("be.enabled"); +}; diff --git a/cypress-tests/cypress/support/utils/platform/groupsUI.js b/cypress-tests/cypress/support/utils/platform/groupsUI.js new file mode 100644 index 0000000000..5d5e32408e --- /dev/null +++ b/cypress-tests/cypress/support/utils/platform/groupsUI.js @@ -0,0 +1,855 @@ +import { commonSelectors } from "Selectors/common"; +import { groupsSelector } from "Selectors/manageGroups"; +import { groupsText } from "Texts/manageGroups"; + +export const verifyAdminHelperText = (index = 0) => { + cy.get(groupsSelector.helperTextAdminAppAccess) + .eq(index) + .should("be.visible") + .and("contain.text", "Admin has all permissions. This is not editable"); + + cy.get(groupsSelector.helperTextAdminAppAccess) + .eq(index) + .find("a") + .should("be.visible") + .and("have.text", "read documentation") + .and("have.attr", "href") + .and("include", "docs.tooljet.ai/docs/tutorial/manage-users-groups"); +}; + +export const verifyEditUserRoleModal = (userEmail) => { + cy.get('[data-cy="modal-title"]') + .last() + .within(() => { + cy.get("span").should("be.visible").and("contain.text", "Edit user role"); + cy.get('[data-cy="user-email"]') + .should("be.visible") + .and("have.text", userEmail); + }); + + cy.get(groupsSelector.userRoleLabel) + .should("be.visible") + .and("have.text", groupsText.userRole); + cy.get(groupsSelector.warningText) + .should("be.visible") + .and("have.text", groupsText.warningText); + + cy.get(".react-select__control").should("be.visible"); + cy.get(".react-select__placeholder") + .should("be.visible") + .and("contain.text", "Select new role of user"); + + cy.get(groupsSelector.cancelButton) + .should("be.visible") + .and("have.text", groupsText.cancelButton) + .and("be.enabled"); + + cy.get(groupsSelector.confimButton) + .should("be.visible") + .and("have.text", groupsText.continueButtonText) + .and("be.disabled"); + + cy.get('[data-cy="modal-close-button"]').should("be.visible"); +}; + +export const toggleAllPermissions = (status = ["uncheck", "check"]) => { + permissions.forEach((permissionSelector) => { + cy.get(permissionSelector).should("be.visible")[status[0]](); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.permissionUpdatedToast + ); + cy.get(permissionSelector).should("be.visible")[status[1]](); + cy.verifyToastMessage( + commonSelectors.toastMessage, + groupsText.permissionUpdatedToast + ); + }); +}; + +export const verifyDeleteConfirmationModal = () => { + cy.get(".confirm-dialogue-modal").should("be.visible"); + cy.verifyElement(groupsSelector.deleteMessage, groupsText.deleteMessage); + cy.get(groupsSelector.yesButton).should("be.visible").and("be.enabled"); + cy.get(groupsSelector.cancelButton).should("be.visible").and("be.enabled"); +}; + +export const verifyGranularEditModal = (role) => { + cy.get(groupsSelector.granularAccessPermission).realHover(); + cy.get('[data-cy="edit-apps-granular-access"]').click(); + + cy.get(".modal-base").should("be.visible"); + + cy.get(groupsSelector.deletePermissionIcon) + .should("be.visible") + .and("be.enabled"); + cy.get(groupsSelector.deletePermissionIcon).click(); + + verifyDeleteConfirmationModal(); + cy.contains("Cancel").click(); + + cy.get(groupsSelector.granularAccessPermission) + .realHover() + .click({ force: true }); + cy.verifyElement( + `${groupsSelector.addEditPermissionModalTitle}:eq(2)`, + groupsText.editPermissionModalTitle + ); + permissionModal(); + + if (role === "builder" || role === "enduser") { + cy.get(groupsSelector.customRadio).should("be.disabled"); + } else { + cy.get(groupsSelector.customRadio).should("be.enabled"); + } + cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); + cy.verifyElement( + groupsSelector.customHelperText, + groupsText.customHelperText + ); + + cy.verifyElement(groupsSelector.confimButton, groupsText.updateButtonText); + cy.get(groupsSelector.confimButton).should("be.disabled"); + cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); + cy.get(groupsSelector.cancelButton).click(); +}; + +export const verifyGranularAddModal = (role) => { + cy.ifEnv("Community", () => { + cy.get(groupsSelector.addAppsButton) + .should("be.visible") + .and("be.enabled") + .click(); + }); + + cy.ifEnv("Enterprise", () => { + cy.get(groupsSelector.addPermissionButton) + .should("be.visible") + .and("be.enabled") + .click(); + cy.get(groupsSelector.addAppButton).click(); + }); + + cy.verifyElement( + `${groupsSelector.addEditPermissionModalTitle}:eq(2)`, + groupsText.addPermissionModalTitle + ); + permissionModal(); + + if (role === "builder" || role === "enduser") { + cy.get(groupsSelector.customRadio).should("be.disabled"); + } else { + cy.get(groupsSelector.customRadio).should("be.enabled"); + } + cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); + cy.verifyElement( + groupsSelector.customHelperText, + groupsText.customHelperText + ); + + cy.verifyElement(groupsSelector.confimButton, groupsText.addButtonText); + cy.get(groupsSelector.confimButton).should("be.disabled"); + cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); + cy.get(groupsSelector.cancelButton).click(); +}; + +export const verifyEnduserHelperText = (index = 0) => { + cy.get(groupsSelector.helperTextAdminAppAccess) + .eq(index) + .should("be.visible") + .and("contain.text", "End-user can only have permission to view apps"); + + cy.get(groupsSelector.helperTextAdminAppAccess) + .eq(index) + .find("a") + .should("be.visible") + .and("have.text", "read documentation") + .and("have.attr", "href") + .and("include", "docs.tooljet.ai/docs/tutorial/manage-users-groups"); +}; + +export const verifyGranularPermissionModalUI = ( + resourceType, + isEdit = false, + permissionName = "" +) => { + // Permission name section + cy.get(groupsSelector.permissionNameLabel).verifyVisibleElement( + "have.text", + groupsText.permissionNameLabel + ); + cy.get(groupsSelector.permissionNameInput).should("be.visible"); + + if (isEdit) { + cy.get(groupsSelector.permissionNameInput).should( + "have.value", + permissionName + ); + } else { + cy.get(groupsSelector.permissionNameInput).should( + "have.attr", + "placeholder" + ); + } + + cy.get(groupsSelector.permissionNameHelperText).verifyVisibleElement( + "have.text", + groupsText.permissionNameHelperText + ); + + // Permission section + cy.get(groupsSelector.permissionLabel).verifyVisibleElement( + "have.text", + groupsText.permissionLabel + ); + + if (resourceType === "app") { + cy.verifyElement( + groupsSelector.editPermissionLabel, + groupsText.editPermissionLabel + ); + cy.verifyElement( + groupsSelector.editPermissionHelperText, + groupsText.editPermissionHelperText + ); + cy.verifyElement( + groupsSelector.viewPermissionLabel, + groupsText.viewPermissionLabel + ); + cy.verifyElement( + groupsSelector.viewPermissionHelperText, + groupsText.viewPermissionHelperText + ); + + cy.get(groupsSelector.hidePermissionInput).should("be.visible"); + cy.verifyElement( + groupsSelector.appHidePermissionModalLabel, + groupsText.appHideLabel + ); + cy.verifyElement( + groupsSelector.appHidePermissionModalHelperText, + groupsText.appHideHelperText + ); + } + + if (resourceType === "workflow") { + cy.verifyElement(groupsSelector.workflowsBuildLabel, "Build"); + cy.verifyElement( + groupsSelector.workflowsBuildHelperText, + "Access to workflow builder" + ); + cy.verifyElement(groupsSelector.workflowsExecuteLabel, "Execute"); + cy.verifyElement( + groupsSelector.workflowsExecuteHelperText, + "Only able to execute the workflow" + ); + } + + if (resourceType === "datasource") { + cy.verifyElement(groupsSelector.datasourcesConfigureLabel, "Configure"); + cy.verifyElement( + groupsSelector.datasourcesConfigureHelperText, + "Access and edit connection detail" + ); + cy.verifyElement(groupsSelector.datasourcesBuildWithLabel, "Build with"); + cy.verifyElement( + groupsSelector.datasourcesBuildWithHelperText, + "Use in apps & workflows" + ); + } + + // Resources section + cy.get(groupsSelector.resourceLabel).verifyVisibleElement( + "have.text", + groupsText.resourcesheader + ); + cy.get(groupsSelector.allAppsRadio).should("be.visible"); + + if (isEdit) { + cy.verifyElement(groupsSelector.allAppsLabel, groupsText.allAppsLabel); + } else { + cy.verifyElement(groupsSelector.allAppsLabel, groupsText.groupChipText); + } + + cy.verifyElement( + groupsSelector.allAppsHelperText, + groupsText.allAppsHelperText + ); + cy.get(groupsSelector.customRadio).should("be.visible"); + cy.verifyElement(groupsSelector.customLabel, groupsText.customLabel); + cy.verifyElement( + groupsSelector.customHelperText, + groupsText.customHelperText + ); + + cy.verifyElement( + groupsSelector.confimButton, + isEdit ? groupsText.updateButtonText : groupsText.addButtonText + ); + cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton); + + if (isEdit) { + cy.get(groupsSelector.deletePermissionIcon).should("be.visible"); + } +}; + +export const verifyGranularPermissionModalStates = ( + resourceType, + role, + customStateOverride = null +) => { + const stateConfig = { + app: { + builder: { + editRadio: { checked: true, enabled: true }, + viewRadio: { checked: false, enabled: true }, + hideCheckbox: { enabled: false }, + allAppsRadio: { checked: true, enabled: false }, + customRadio: { checked: false, enabled: false }, + }, + enduser: { + editRadio: { checked: false, enabled: false }, + viewRadio: { checked: true, enabled: false }, + hideCheckbox: { enabled: true }, + allAppsRadio: { checked: true, enabled: false }, + customRadio: { checked: false, enabled: false }, + }, + custom: { + editRadio: { checked: true, enabled: true }, + viewRadio: { checked: false, enabled: true }, + hideCheckbox: { enabled: false }, + allAppsRadio: { checked: true, enabled: true }, + customRadio: { checked: false, enabled: true }, + }, + }, + workflow: { + builder: { + buildRadio: { checked: true, enabled: true }, + executeRadio: { checked: false, enabled: true }, + allAppsRadio: { checked: true, enabled: false }, + customRadio: { checked: false, enabled: false }, + }, + enduser: { + buildRadio: { checked: false, enabled: false }, + executeRadio: { checked: true, enabled: false }, + allAppsRadio: { checked: true, enabled: false }, + customRadio: { checked: false, enabled: false }, + }, + custom: { + buildRadio: { checked: true, enabled: true }, + executeRadio: { checked: false, enabled: true }, + allAppsRadio: { checked: true, enabled: true }, + customRadio: { checked: false, enabled: true }, + }, + }, + datasource: { + builder: { + configureRadio: { checked: true, enabled: true }, + buildWithRadio: { checked: false, enabled: true }, + allAppsRadio: { checked: true, enabled: false }, + customRadio: { checked: false, enabled: false }, + }, + custom: { + configureRadio: { checked: true, enabled: true }, + buildWithRadio: { checked: false, enabled: true }, + allAppsRadio: { checked: true, enabled: true }, + customRadio: { checked: false, enabled: true }, + }, + }, + }; + + // Get the base config + let config = stateConfig[resourceType][role]; + + // If customStateOverride is provided and role is 'custom', merge it with the default + if (customStateOverride && role === "custom") { + config = { ...config, ...customStateOverride }; + } + + if (resourceType === "app") { + cy.get(groupsSelector.editPermissionRadio) + .should("be.visible") + .and(config.editRadio.checked ? "be.checked" : "not.be.checked") + .and(config.editRadio.enabled ? "be.enabled" : "be.disabled"); + + cy.get(groupsSelector.viewPermissionRadio) + .should("be.visible") + .and(config.viewRadio.checked ? "be.checked" : "not.be.checked") + .and(config.viewRadio.enabled ? "be.enabled" : "be.disabled"); + + cy.get(groupsSelector.hidePermissionInput) + .should("be.visible") + .and(config.hideCheckbox.enabled ? "be.enabled" : "be.disabled"); + } + + if (resourceType === "workflow") { + cy.get(groupsSelector.buildWorkflowradio) + .should("be.visible") + .and(config.buildRadio.checked ? "be.checked" : "not.be.checked") + .and(config.buildRadio.enabled ? "be.enabled" : "be.disabled"); + + cy.get(groupsSelector.executeWorkflowradio) + .should("be.visible") + .and(config.executeRadio.checked ? "be.checked" : "not.be.checked") + .and(config.executeRadio.enabled ? "be.enabled" : "be.disabled"); + } + + if (resourceType === "datasource") { + cy.get(groupsSelector.configureDatasourceradio) + .should("be.visible") + .and(config.configureRadio.checked ? "be.checked" : "not.be.checked") + .and(config.configureRadio.enabled ? "be.enabled" : "be.disabled"); + + cy.get(groupsSelector.buildWithDatasourceRadio) + .should("be.visible") + .and(config.buildWithRadio.checked ? "be.checked" : "not.be.checked") + .and(config.buildWithRadio.enabled ? "be.enabled" : "be.disabled"); + } + + cy.get(groupsSelector.allAppsRadio) + .should("be.visible") + .and(config.allAppsRadio.checked ? "be.checked" : "not.be.checked") + .and(config.allAppsRadio.enabled ? "be.enabled" : "be.disabled"); + + cy.get(groupsSelector.customRadio) + .should("be.visible") + .and(config.customRadio.checked ? "be.checked" : "not.be.checked") + .and(config.customRadio.enabled ? "be.enabled" : "be.disabled"); +}; + +export const verifyEmptyStates = (customGroup = false) => { + // Users empty state + cy.get(groupsSelector.usersLink).click(); + cy.get('[data-cy="user-group-search-btn"]').should("be.visible"); + cy.verifyElement( + groupsSelector.nameTableHeader, + groupsText.userNameTableHeader + ); + cy.verifyElement( + groupsSelector.emailTableHeader, + groupsText.emailTableHeader + ); + + cy.get(groupsSelector.userEmptyPageIcon).should("be.visible"); + cy.get(groupsSelector.userEmptyPageTitle).verifyVisibleElement( + "have.text", + groupsText.userEmptyPageTitle + ); + cy.get(groupsSelector.userEmptyPageHelperText).verifyVisibleElement( + "have.text", + groupsText.userEmptyPageHelperText + ); + + if (customGroup) { + // Granular permissions empty state + cy.get(groupsSelector.granularLink).click(); + cy.get(groupsSelector.granularEmptyPageIcon).should("be.visible"); + cy.get(groupsSelector.emptyPagePermissionTitle).verifyVisibleElement( + "have.text", + groupsText.emptyPagePermissionTitle + ); + cy.get(groupsSelector.emptyPagePermissionHelperText).verifyVisibleElement( + "have.text", + groupsText.emptyPagePermissionHelperText + ); + } +}; + +export const verifyGroupLinks = () => { + const links = [ + { selector: groupsSelector.usersLink, text: groupsText.usersLink }, + { + selector: groupsSelector.permissionsLink, + text: groupsText.permissionsLink, + }, + { selector: groupsSelector.granularLink, text: "Granular access" }, + ]; + + links.forEach(({ selector, text }) => { + cy.get(selector).verifyVisibleElement("have.text", text); + }); +}; + +export const commonGroupVerification = () => { + cy.verifyElement( + groupsSelector.textDefaultGroup, + groupsText.textDefaultGroup + ); + cy.verifyElement(groupsSelector.usersLink, groupsText.usersLink); + cy.verifyElement(groupsSelector.permissionsLink, groupsText.permissionsLink); + cy.verifyElement(groupsSelector.granularLink, "Granular access"); + + cy.get(groupsSelector.usersLink).click(); + cy.verifyElement( + groupsSelector.nameTableHeader, + groupsText.userNameTableHeader + ); + cy.verifyElement( + groupsSelector.emailTableHeader, + groupsText.emailTableHeader + ); +}; + +export const permissions = + Cypress.env("environment") === "Community" + ? [ + groupsSelector.appsCreateCheck, + groupsSelector.appsDeleteCheck, + groupsSelector.foldersCreateCheck, + groupsSelector.workspaceVarCheckbox, + ] + : [ + groupsSelector.appsCreateCheck, + groupsSelector.appsDeleteCheck, + groupsSelector.appPromoteCheck, + groupsSelector.appReleaseCheck, + groupsSelector.workflowsCreateCheck, + groupsSelector.workflowsDeleteCheck, + groupsSelector.datasourcesCreateCheck, + groupsSelector.datasourcesDeleteCheck, + groupsSelector.foldersCreateCheck, + groupsSelector.workspaceVarCheckbox, + ]; + +export const verifyCheckPermissionStates = (roleType, action = null) => { + const roleConfig = { + admin: { checked: true, enabled: false }, + enduser: { checked: false, enabled: false }, + builder: { checked: true, enabled: true }, + custom: { checked: false, enabled: true }, + }; + + const config = roleConfig[roleType]; + + permissions.forEach((permissionSelector) => { + cy.get(permissionSelector) + .should("be.visible") + .and(config.checked ? "be.checked" : "not.be.checked") + .and(config.enabled ? "be.enabled" : "be.disabled"); + }); +}; + +export const verifyPermissionCheckBoxLabelsAndHelperTexts = () => { + const commonPermissions = [ + { selector: groupsSelector.resourcesApps, text: groupsText.resourcesApps }, + { + selector: groupsSelector.permissionsTableHeader, + text: groupsText.permissionsTableHeader, + }, + { selector: groupsSelector.appsCreateLabel, text: groupsText.createLabel }, + { + selector: groupsSelector.appCreateHelperText, + text: groupsText.appCreateHelperText, + }, + { selector: groupsSelector.appsDeleteLabel, text: groupsText.deleteLabel }, + { + selector: groupsSelector.appDeleteHelperText, + text: groupsText.appDeleteHelperText, + }, + { + selector: groupsSelector.resourcesFolders, + text: groupsText.resourcesFolders, + }, + { + selector: groupsSelector.foldersCreateLabel, + text: groupsText.folderCreateLabel, + }, + { + selector: groupsSelector.foldersHelperText, + text: groupsText.folderHelperText, + }, + { + selector: groupsSelector.resourcesWorkspaceVar, + text: groupsText.resourcesWorkspaceVar, + }, + { + selector: groupsSelector.workspaceCreateLabel, + text: groupsText.workspaceCreateLabel, + }, + { + selector: groupsSelector.workspaceHelperText, + text: groupsText.workspaceHelperText, + }, + ]; + + commonPermissions.forEach(({ selector, text }) => { + cy.verifyElement(selector, text); + }); + + cy.ifEnv("Enterprise", () => { + const enterprisePermissions = [ + { selector: groupsSelector.appPromoteLabel, text: "Promote" }, + { + selector: groupsSelector.appPromoteHelperText, + text: "Promote any app in this workspace", + }, + { selector: groupsSelector.appReleaseLabel, text: "Release" }, + { + selector: groupsSelector.appReleaseHelperText, + text: "Release any app in this workspace", + }, + { selector: groupsSelector.workflowsCreateLabel, text: "Create" }, + { + selector: groupsSelector.workflowsCreateHelperText, + text: "Create workflow in this workspace", + }, + { selector: groupsSelector.workflowsDeleteLabel, text: "Delete" }, + { + selector: groupsSelector.workflowsDeleteHelperText, + text: "Delete any workflow in this workspace", + }, + { selector: groupsSelector.datasourcesCreateLabel, text: "Create" }, + { + selector: groupsSelector.datasourcesCreateHelperText, + text: "Create data source connections in this workspace", + }, + { selector: groupsSelector.datasourcesDeleteLabel, text: "Delete" }, + { + selector: groupsSelector.datasourcesDeleteHelperText, + text: "Delete any data source in this workspace", + }, + ]; + + enterprisePermissions.forEach(({ selector, text }) => { + cy.verifyElement(selector, text); + }); + }); +}; + +export const verifyGranularAccessByRole = (role) => { + const roleConfig = { + admin: { + appEditRadio: { checked: true, enabled: false }, + appViewRadio: { checked: false, enabled: false }, + appHideCheckbox: { enabled: false }, + workflowBuildRadio: { checked: true, enabled: false }, + workflowExecuteRadio: { checked: false, enabled: false }, + datasourceConfigureRadio: { checked: true, enabled: false }, + datasourceBuildWithRadio: { checked: false, enabled: false }, + addButtonEnabled: false, + verifyHelperTexts: true, + hasDatasource: true, + }, + builder: { + appEditRadio: { checked: true, enabled: true }, + appViewRadio: { checked: false, enabled: true }, + appHideCheckbox: { enabled: false }, + workflowBuildRadio: { checked: true, enabled: true }, + workflowExecuteRadio: { checked: false, enabled: true }, + datasourceConfigureRadio: { checked: true, enabled: true }, + datasourceBuildWithRadio: { checked: false, enabled: true }, + addButtonEnabled: true, + verifyHelperTexts: false, + hasDatasource: true, + }, + enduser: { + appEditRadio: { checked: false, enabled: false }, + appViewRadio: { checked: true, enabled: false }, + appHideCheckbox: { enabled: true }, + workflowBuildRadio: { checked: false, enabled: false }, + workflowExecuteRadio: { checked: true, enabled: false }, + addButtonEnabled: true, + verifyHelperTexts: false, + hasDatasource: false, + }, + }; + + const config = roleConfig[role]; + + cy.get(groupsSelector.granularLink).click(); + + if (role === "admin") { + cy.verifyElement( + groupsSelector.nameTableHeader, + groupsText.nameTableHeader + ); + cy.verifyElement( + groupsSelector.permissionsTableHeader, + groupsText.granularAccessPermissionHeader + ); + cy.verifyElement( + `${groupsSelector.resourceHeader}:eq(1)`, + groupsText.resourcesTableHeader + ); + } + + cy.verifyElement(groupsSelector.appsText, " Apps"); + + cy.get(groupsSelector.appEditRadio) + .should("be.visible") + .and(config.appEditRadio.checked ? "be.checked" : "not.be.checked") + .and( + config.appEditRadio.enabled ? "be.enabled" : "have.attr", + config.appEditRadio.enabled ? "" : "disabled" + ); + + cy.get(groupsSelector.appViewRadio) + .should("be.visible") + .and( + config.appViewRadio.enabled ? "be.enabled" : "have.attr", + config.appViewRadio.enabled ? "" : "disabled" + ); + + cy.get(groupsSelector.appHideCheckbox) + .should("be.visible") + .and(config.appHideCheckbox.enabled ? "be.enabled" : "be.disabled"); + + if (config.verifyHelperTexts) { + cy.verifyElement(groupsSelector.appEditLabel, groupsText.appEditLabelText); + cy.verifyElement( + groupsSelector.appEditHelperText, + groupsText.appEditHelperText + ); + cy.verifyElement(groupsSelector.appViewLabel, groupsText.appViewLabel); + cy.verifyElement( + groupsSelector.appViewHelperText, + groupsText.appViewHelperText + ); + cy.verifyElement( + groupsSelector.appHideHelperText, + groupsText.appHideHelperText + ); + } + + cy.verifyElement(groupsSelector.groupChip("All apps"), "All apps"); + + cy.ifEnv("Community", () => { + cy.get(groupsSelector.addAppButton).should( + config.addButtonEnabled ? "be.enabled" : "be.disabled" + ); + }); + + cy.ifEnv("Enterprise", () => { + cy.verifyElement(groupsSelector.workflowsText, "Workflows"); + + cy.get(groupsSelector.workflowsBuildRadio) + .should("be.visible") + .and(config.workflowBuildRadio.checked ? "be.checked" : "not.be.checked") + .and( + config.workflowBuildRadio.enabled ? "be.enabled" : "have.attr", + config.workflowBuildRadio.enabled ? "" : "disabled" + ); + + cy.get(groupsSelector.workflowsExecuteRadio) + .should("be.visible") + .and( + config.workflowExecuteRadio.enabled ? "be.enabled" : "have.attr", + config.workflowExecuteRadio.enabled ? "" : "disabled" + ); + + if (config.verifyHelperTexts) { + cy.verifyElement(groupsSelector.workflowsBuildLabel, "Build"); + cy.verifyElement( + groupsSelector.workflowsBuildHelperText, + "Access to workflow builder" + ); + cy.verifyElement(groupsSelector.workflowsExecuteLabel, "Execute"); + cy.verifyElement( + groupsSelector.workflowsExecuteHelperText, + "Only able to execute the workflow" + ); + } + + cy.verifyElement( + groupsSelector.groupChip("All workflows"), + "All workflows" + ); + + if (config.hasDatasource) { + cy.verifyElement(groupsSelector.datasourcesText, " Data sources"); + + cy.get(groupsSelector.datasourcesConfigureRadio) + .should("be.visible") + .and( + config.datasourceConfigureRadio.checked + ? "be.checked" + : "not.be.checked" + ) + .and( + config.datasourceConfigureRadio.enabled ? "be.enabled" : "have.attr", + config.datasourceConfigureRadio.enabled ? "" : "disabled" + ); + + cy.get(groupsSelector.datasourcesBuildWithRadio) + .should("be.visible") + .and( + config.datasourceBuildWithRadio.enabled ? "be.enabled" : "have.attr", + config.datasourceBuildWithRadio.enabled ? "" : "disabled" + ); + + if (config.verifyHelperTexts) { + cy.verifyElement(groupsSelector.datasourcesConfigureLabel, "Configure"); + cy.verifyElement( + groupsSelector.datasourcesConfigureHelperText, + "Access & edit connection details" + ); + cy.verifyElement( + groupsSelector.datasourcesBuildWithLabel, + "Build with" + ); + cy.verifyElement( + groupsSelector.datasourcesBuildWithHelperText, + "Use in apps & workflows" + ); + } + + cy.verifyElement( + groupsSelector.groupChip("All data source"), + "All data sources" + ); + } + + cy.verifyElement(groupsSelector.addPermissionButton, "Add permission"); + cy.get(groupsSelector.addPermissionButton).should( + config.addButtonEnabled ? "be.enabled" : "be.disabled" + ); + }); +}; + +export const permissionModal = () => { + cy.verifyElement( + groupsSelector.permissionNameLabel, + groupsText.permissionNameLabel + ); + cy.verifyElement( + groupsSelector.permissionNameHelperText, + groupsText.permissionNameHelperText + ); + + cy.verifyElement(groupsSelector.permissionLabel, groupsText.permissionLabel); + cy.verifyElement( + groupsSelector.editPermissionLabel, + groupsText.editPermissionLabel + ); + cy.verifyElement( + groupsSelector.editPermissionHelperText, + groupsText.editPermissionHelperText + ); + + cy.verifyElement( + groupsSelector.viewPermissionLabel, + groupsText.viewPermissionLabel + ); + cy.verifyElement( + groupsSelector.viewPermissionHelperText, + groupsText.viewPermissionHelperText + ); + + cy.get(groupsSelector.hidePermissionInput).should("be.visible"); + cy.verifyElement(groupsSelector.resourceLabel, groupsText.resourcesheader); + cy.get(groupsSelector.resourceContainer).should("be.visible"); + cy.get(groupsSelector.allAppsRadio).should("be.visible").and("be.checked"); + cy.verifyElement(groupsSelector.allAppsLabel, groupsText.allAppsLabel); + cy.verifyElement( + groupsSelector.allAppsHelperText, + groupsText.allAppsHelperText + ); +}; + +export const verifyUserRow = (name, email) => { + cy.get('[data-cy="avatar-image"]').should("be.visible"); + cy.get('[data-cy="user-name"]') + .should("be.visible") + .and("contain.text", name); + cy.get('[data-cy="user-email"]').should("be.visible").and("have.text", email); +}; diff --git a/cypress-tests/cypress/support/utils/platform/multiEnv.js b/cypress-tests/cypress/support/utils/platform/multiEnv.js index be72c3ae53..10207eea6e 100644 --- a/cypress-tests/cypress/support/utils/platform/multiEnv.js +++ b/cypress-tests/cypress/support/utils/platform/multiEnv.js @@ -109,6 +109,7 @@ export const selectEnv = (envName) => { }; if (isValidEnvName(envName)) { + cy.wait(1000) cy.get('[data-cy="list-current-env-name"]').click(); cy.wait(500) const envSelector = `${multiEnvSelector.envNameList}:eq(${envIndex})`; diff --git a/cypress-tests/cypress/support/utils/uiPermissions.js b/cypress-tests/cypress/support/utils/uiPermissions.js new file mode 100644 index 0000000000..4e5a61dcf8 --- /dev/null +++ b/cypress-tests/cypress/support/utils/uiPermissions.js @@ -0,0 +1,206 @@ +import { commonSelectors } from "Selectors/common"; +import { workflowSelector } from "Selectors/workflows"; +import { deleteFolder } from "Support/utils/common"; +import { + addAndVerifyConstants, + deleteConstant, +} from "Support/utils/workspaceConstants"; +import { commonText } from "Texts/common"; + +export const uiCreateApp = (appName) => { + cy.createApp(appName); + cy.wait(2000); + cy.go("back"); +}; + +export const uiVerifyAppCreated = (appName, shouldExist = true) => { + const assertion = shouldExist ? "exist" : "not.exist"; + cy.get(commonSelectors.appCard(appName)).should(assertion); +}; + +export const uiDeleteApp = (appName) => { + cy.deleteApp(appName); +}; + +export const uiVerifyAppDeleted = (appName) => { + cy.get(commonSelectors.appCard(appName)).should("not.exist"); +}; + +export const uiVerifyAppCreatePrivilege = (hasPrivilege = true) => { + const assertion = hasPrivilege ? "be.enabled" : "be.disabled"; + cy.get(commonSelectors.dashboardAppCreateButton).should(assertion); +}; + +export const uiCreateFolder = (folderName) => { + cy.get(commonSelectors.createNewFolderButton).click(); + cy.clearAndType(commonSelectors.folderNameInput, folderName); + cy.get(commonSelectors.createFolderButton).click(); +}; + +export const uiVerifyFolderCreated = (folderName) => { + cy.get(commonSelectors.folderListcard(folderName)).should("exist"); +}; + +export const uiVerifyFolderDeleted = (folderName) => { + cy.verifyToastMessage( + commonSelectors.toastMessage, + commonText.folderDeletedToast + ); + cy.get(commonSelectors.folderListcard(folderName)).should("not.exist"); +}; + +export const uiVerifyFolderCreatePrivilege = (hasPrivilege = true) => { + const assertion = hasPrivilege ? "exist" : "not.exist"; + cy.get(commonSelectors.createNewFolderButton).should(assertion); +}; + +export const uiVerifyWorkspaceConstantCreatePrivilege = ( + hasPrivilege = true +) => { + const assertion = hasPrivilege ? "exist" : "not.exist"; + cy.get(commonSelectors.workspaceConstantsIcon).should(assertion); +}; + +export const uiCreateDataSource = ( + datasourceName, + datasourceType = "restapi" +) => { + cy.get(commonSelectors.globalDataSourceIcon).click(); + // cy.get(commonSelectors.addNewDataSourceButton).click(); + cy.get('[data-cy="rest-api-add-button"]').eq(0).click({ force: true }); +}; + +export const uiVerifyDataSourceCreated = (datasourceName) => { + cy.verifyToastMessage(commonSelectors.toastMessage, "Data Source Added"); + cy.get('[data-cy="restapi-button"]').should("exist"); +}; + +export const uiDeleteDataSource = (datasourceName) => { + cy.get('[data-cy="restapi-delete-button"]').click({ force: true }); + cy.get(commonSelectors.yesButton).click(); +}; + +export const uiVerifyDataSourceDeleted = (datasourceName) => { + cy.verifyToastMessage(commonSelectors.toastMessage, "Data Source Deleted"); + cy.get('[data-cy="restapi-button"]').should("not.exist"); +}; + +export const uiVerifyDataSourceCreatePrivilege = (hasPrivilege = true) => { + const assertion = hasPrivilege ? "exist" : "not.exist"; + cy.get(commonSelectors.globalDataSourceIcon).should(assertion); +}; + +export const uiCreateWorkflow = (workflowName) => { + cy.get(workflowSelector.globalWorkFlowsIcon).click(); + + cy.get('[data-cy="button-new-workflow-from-scratch"]').click(); + cy.get(workflowSelector.workFlowNameInputField).type(workflowName); + cy.get(workflowSelector.createWorkFlowsButton).click(); + cy.wait(2000); + cy.go("back"); +}; + +export const uiVerifyWorkflowCreated = (workflowName) => { + cy.get(commonSelectors.globalWorkFlowsIcon).click(); + cy.get(`[data-cy="${workflowName.toLowerCase()}-card"]`) + .contains(workflowName) + .should("exist"); +}; + +export const uiDeleteWorkflow = () => { + cy.get(".homepage-app-card .home-app-card-header .menu-ico").then(($el) => { + $el[0].style.setProperty("visibility", "visible", "important"); + }); + + cy.get('[data-cy="app-card-menu-icon"]').click(); + cy.get(workflowSelector.deleteWorkFlowOption).click(); + cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); +}; + +export const uiVerifyWorkflowDeleted = (workflowName) => { + cy.get(`[data-cy="${workflowName.toLowerCase()}-card"]`).should("not.exist"); +}; + +export const uiVerifyWorkflowCreatePrivilege = (hasPrivilege = true) => { + const assertion = hasPrivilege ? "exist" : "not.exist"; + cy.get(commonSelectors.globalWorkFlowsIcon).should(assertion); +}; + +export const uiVerifyAllCreatePrivileges = ( + hasAppCreate = true, + hasFolderCreate = true, + hasConstantCreate = true, + hasDataSourceCreate = true, + hasWorkflowCreate = true +) => { + uiVerifyAppCreatePrivilege(hasAppCreate); + uiVerifyFolderCreatePrivilege(hasFolderCreate); + uiVerifyWorkspaceConstantCreatePrivilege(hasConstantCreate); + + cy.ifEnv("Enterprise", () => { + uiVerifyDataSourceCreatePrivilege(hasDataSourceCreate); + uiVerifyWorkflowCreatePrivilege(hasWorkflowCreate); + }); +}; + +export const uiVerifyBuilderPrivileges = () => { + uiVerifyAllCreatePrivileges(true, true, true, true, true); +}; + +export const uiVerifyAdminPrivileges = () => { + uiVerifyAllCreatePrivileges(true, true, true, true, true); + cy.get(commonSelectors.settingsIcon).click(); + cy.get(commonSelectors.workspaceSettings).should("exist"); + cy.get(commonSelectors.dashboardIcon).click(); +}; + +export const uiAppCRUDWorkflow = (appName) => { + uiCreateApp(appName); + uiVerifyAppCreated(appName, true); + + uiDeleteApp(appName); + uiVerifyAppDeleted(appName); +}; + +export const uiFolderCRUDWorkflow = (folderName) => { + uiCreateFolder(folderName); + uiVerifyFolderCreated(folderName); + + deleteFolder(folderName); + uiVerifyFolderDeleted(folderName); +}; + +export const uiWorkspaceConstantCRUDWorkflow = ( + constantName, + constantValue +) => { + cy.get(commonSelectors.workspaceConstantsIcon).click(); + + addAndVerifyConstants(constantName, constantValue); + deleteConstant(constantName); + cy.get(commonSelectors.dashboardIcon).click(); +}; + +export const uiDataSourceCRUDWorkflow = ( + datasourceName, + datasourceType = "restapi" +) => { + cy.ifEnv("Enterprise", () => { + uiCreateDataSource(datasourceName, datasourceType); + uiVerifyDataSourceCreated(datasourceName); + + uiDeleteDataSource(datasourceName); + uiVerifyDataSourceDeleted(datasourceName); + cy.get(commonSelectors.dashboardIcon).click(); + }); +}; + +export const uiWorkflowCRUDWorkflow = (workflowName) => { + cy.ifEnv("Enterprise", () => { + uiCreateWorkflow(workflowName); + uiVerifyWorkflowCreated(workflowName); + + uiDeleteWorkflow(); + uiVerifyWorkflowDeleted(workflowName); + }); +}; diff --git a/cypress-tests/cypress/support/utils/userPermissions.js b/cypress-tests/cypress/support/utils/userPermissions.js index 0058312120..0d2e585b80 100644 --- a/cypress-tests/cypress/support/utils/userPermissions.js +++ b/cypress-tests/cypress/support/utils/userPermissions.js @@ -1,42 +1,8 @@ import { commonSelectors } from "Selectors/common"; -import { commonText } from "Texts/common"; import { workspaceConstantsSelectors } from "Selectors/workspaceConstants"; -import { createFolder, deleteFolder } from "Support/utils/common"; import { addAndVerifyConstants } from "Support/utils/workspaceConstants"; -const appOperations = { - createApp: (appName) => { - cy.createApp(appName); - cy.backToApps(); - }, - - deleteApp: (appName) => { - cy.deleteApp(appName); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appDeletedToast - ); - }, - - cloneApp: (appName) => { - cy.get(commonSelectors.appCard(appName)) - .trigger("mouseover") - .find(commonSelectors.cloneButton) - .click(); - }, -}; - -const folderOperations = { - createFolder: (folderName) => { - createFolder(folderName); - }, - - deleteFolder: (folderName) => { - deleteFolder(folderName); - }, -}; - -const constantsOperations = { +export const constantsOperations = { createConstant: (name, value) => { cy.get(commonSelectors.workspaceConstantsIcon).click(); addAndVerifyConstants(name, value); @@ -49,7 +15,7 @@ const constantsOperations = { }; // Permission verification helpers -const verifyPermissions = { +export const verifyPermissions = { checkAppPermissions: (shouldExist = true) => { const assertion = shouldExist ? "exist" : "not.exist"; cy.get(commonSelectors.appCreateButton).should(assertion); @@ -73,41 +39,89 @@ const verifyPermissions = { }, }; -// Helper function to perform all verifications -const verifyAllPermissions = (shouldHaveAccess = true) => { - verifyPermissions.checkAppPermissions(shouldHaveAccess); - verifyPermissions.checkFolderPermissions(shouldHaveAccess); - verifyPermissions.checkConstantsPermissions(shouldHaveAccess); - verifyPermissions.checkSettingsAccess(shouldHaveAccess); +export const getGroupPermissionInput = (isEnterprise, flag) => { + return isEnterprise + ? { + appCreate: flag, + appDelete: flag, + appPromote: flag, + appRelease: flag, + workflowCreate: flag, + workflowDelete: flag, + dataSourceCreate: flag, + dataSourceDelete: flag, + folderCRUD: flag, + orgConstantCRUD: flag, + } + : { + appCreate: flag, + appDelete: flag, + folderCRUD: flag, + orgConstantCRUD: flag, + }; }; -// Role-based permission sets -const rolePermissions = { - admin: { - name: "Admin", - hasFullAccess: true, - canManageWorkspace: true, - canManageUsers: true, - }, - builder: { - name: "Builder", - hasFullAccess: true, - canManageWorkspace: false, - canManageUsers: false, - }, - endUser: { - name: "End User", - hasFullAccess: false, - canManageWorkspace: false, - canManageUsers: false, - }, +export const verifyBuilderPermissions = ( + appName, + folderName, + constName, + constValue, + isAdmin = false +) => { + verifyBasicPermissions(true); + + // App operations + cy.apiCreateApp(appName); + cy.apiDeleteApp(); + + // Folder operations + cy.apiCreateFolder(folderName); + cy.apiDeleteFolder(); + + // Constants management + cy.get(commonSelectors.workspaceConstantsIcon).click(); + addAndVerifyConstants(constName, constValue); + cy.get(workspaceConstantsSelectors.constDeleteButton(constName)).click(); + cy.get(commonSelectors.yesButton).click(); + + cy.ifEnv("Enterprise", () => { + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + appName, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); + cy.apiDeleteGDS(appName); + + cy.apiCreateWorkflow(appName); + cy.apiDeleteWorkflow(appName); + }); + + verifySettingsAccess(isAdmin); }; -export { - appOperations, - folderOperations, - constantsOperations, - verifyPermissions, - verifyAllPermissions, - rolePermissions, +export const verifyBasicPermissions = (canCreate = true) => { + cy.get(commonSelectors.dashboardAppCreateButton).should( + canCreate ? "be.enabled" : "be.disabled" + ); + cy.get(commonSelectors.createNewFolderButton).should( + canCreate ? "exist" : "not.exist" + ); + cy.get('[data-cy="database-icon"]').should(canCreate ? "exist" : "not.exist"); + + cy.ifEnv("Enterprise", () => { + cy.get(commonSelectors.globalDataSourceIcon).should( + canCreate ? "exist" : "not.exist" + ); + cy.get(commonSelectors.workspaceConstantsIcon).should( + canCreate ? "exist" : "not.exist" + ); + }); +}; + +export const verifySettingsAccess = (shouldExist = true) => { + cy.get(commonSelectors.settingsIcon).click(); + cy.get(commonSelectors.workspaceSettings).should( + shouldExist ? "exist" : "not.exist" + ); }; diff --git a/cypress-tests/cypress/support/utils/version.js b/cypress-tests/cypress/support/utils/version.js index ffb1ad32a0..a4c37312bd 100644 --- a/cypress-tests/cypress/support/utils/version.js +++ b/cypress-tests/cypress/support/utils/version.js @@ -1,20 +1,19 @@ -import { appVersionText } from "Texts/exportImport"; -import { appVersionSelectors } from "Selectors/exportImport"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; -import { commonText } from "Texts/common"; -import { verifyModal, closeModal } from "Support/utils/common"; +import { appVersionSelectors } from "Selectors/exportImport"; import { confirmVersionModalSelectors, editVersionSelectors, } from "Selectors/version"; +import { closeModal } from "Support/utils/common"; +import { commonText } from "Texts/common"; +import { appVersionText } from "Texts/exportImport"; import { deleteVersionText, releasedVersionText } from "Texts/version"; -import { verifyComponent } from "Support/utils/basicComponents"; import { appPromote } from "./platform/multiEnv"; export const navigateToCreateNewVersionModal = (value) => { cy.get(appVersionSelectors.appVersionLabel).click(); - cy.contains(appVersionText.createNewVersion).should("be.visible"); - cy.contains(appVersionText.createNewVersion).click(); + cy.contains(appVersionText.createNewVersion).first().should("be.visible"); + cy.contains(appVersionText.createNewVersion).first().click(); }; export const navigateToEditVersionModal = (value) => { @@ -48,7 +47,7 @@ export const verifyElementsOfCreateNewVersionModal = (version = []) => { ); cy.get( commonSelectors.buttonSelector(appVersionText.createNewVersion) - ).verifyVisibleElement("have.text", appVersionText.createNewVersion); + ).first().verifyVisibleElement("have.text", appVersionText.createNewVersion); cy.get(commonSelectors.buttonSelector(commonText.cancelButton)) .should("be.visible") .and("have.text", commonText.cancelButton); @@ -113,7 +112,7 @@ export const verifyDuplicateVersion = (newVersion = [], version) => { cy.get(appVersionSelectors.createVersionInputField).click(); cy.contains(`[id*="react-select-"]`, version).click(); cy.clearAndType(appVersionSelectors.versionNameInputField, newVersion[0]); - cy.get(appVersionSelectors.createNewVersionButton).click(); + cy.get(appVersionSelectors.createNewVersionButton).first().click(); cy.verifyToastMessage( commonSelectors.toastMessage, appVersionText.versionNameAlreadyExists @@ -160,4 +159,10 @@ export const switchVersionAndVerify = (currentVersion, newVersion) => { .should("be.visible") .click(); cy.get(".app-version-name").contains(newVersion).click(); + cy.wait('@appDs') + +}; + +export const openPreviewSettings = () => { + cy.get(commonSelectors.previewSettings).should("be.visible").click(); }; diff --git a/cypress-tests/cypress/support/utils/workFlows.js b/cypress-tests/cypress/support/utils/workFlows.js index e415e48e84..711a19da46 100644 --- a/cypress-tests/cypress/support/utils/workFlows.js +++ b/cypress-tests/cypress/support/utils/workFlows.js @@ -40,23 +40,23 @@ export const revealWorkflowToken = (selectors) => { }; export const importWorkflowApp = ( - wfName, + workflowName, fixturePath = "cypress/fixtures/exportedApp.json" ) => { cy.get(workflowSelector.importWorkFlowsOption).click(); cy.get(workflowSelector.importWorkFlowsLabel).click(); cy.get('input[type="file"]').first().selectFile(fixturePath, { force: true }); cy.wait(2000); - cy.get(workflowSelector.workFlowNameInputField).clear().type(wfName); + cy.get(workflowSelector.workFlowNameInputField).clear().type(workflowName); cy.get(workflowSelector.importWorkFlowsButton).click(); }; -export const deleteAppandWorkflowAfterExecution = (wfName, appName) => { +export const deleteAppandWorkflowAfterExecution = (workflowName, appName) => { cy.backToApps(); cy.deleteApp(appName); cy.get(workflowSelector.globalWorkFlowsIcon).click(); cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); - cy.get(commonSelectors.appCard(wfName)) + cy.get(commonSelectors.appCard(workflowName)) .realHover() .find(commonSelectors.appCardOptionsButton) .realHover() diff --git a/cypress-tests/package-lock.json b/cypress-tests/package-lock.json index e869477d52..2eb3c29240 100644 --- a/cypress-tests/package-lock.json +++ b/cypress-tests/package-lock.json @@ -8,6 +8,7 @@ "name": "cypress-tests", "version": "1.0.0", "dependencies": { + "cypress-real-events": "^1.15.0", "moment": "^2.29.4", "node-xlsx": "^0.4.0", "pdf-parse": "^1.1.1", @@ -17,7 +18,7 @@ "@cypress/code-coverage": "^3.12.12", "@cypress/webpack-preprocessor": "^5.12.0", "@faker-js/faker": "^7.3.0", - "cypress": "^15.0.0", + "cypress": "^15.4.0", "cypress-mailhog": "^2.5.0" } }, @@ -1591,7 +1592,6 @@ "version": "3.0.9", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", @@ -1638,7 +1638,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" @@ -1648,7 +1647,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -1844,25 +1842,28 @@ "version": "16.18.119", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.119.tgz", "integrity": "sha512-ia7V9a2FnhUFfetng4/sRPBMTwHZUkPFY736rb1cg9AgG7MZdR97q7/nLR9om+sq5f1la9C857E0l/nrI0RiFQ==", - "dev": true + "devOptional": true }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==" }, "node_modules/@types/sizzle": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", - "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", - "dev": true + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==" + }, + "node_modules/@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", + "license": "MIT" }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, "optional": true, "dependencies": { "@types/node": "*" @@ -2060,7 +2061,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -2121,7 +2121,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, "engines": { "node": ">=6" } @@ -2130,7 +2129,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -2145,7 +2143,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2154,7 +2151,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2181,7 +2177,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, "funding": [ { "type": "github", @@ -2222,7 +2217,6 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" @@ -2232,7 +2226,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -2242,29 +2235,20 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, "engines": { "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, "engines": { "node": ">= 4.0.0" } @@ -2273,7 +2257,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "*" @@ -2283,7 +2266,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, "license": "MIT" }, "node_modules/babel-loader": { @@ -2356,7 +2338,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2376,7 +2357,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" @@ -2385,8 +2365,7 @@ "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==" }, "node_modules/bluebird": { "version": "3.7.1", @@ -2452,7 +2431,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -2476,7 +2454,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "engines": { "node": "*" } @@ -2492,7 +2469,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, "engines": { "node": ">=6" } @@ -2516,7 +2492,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2530,7 +2505,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2576,14 +2550,12 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, "license": "Apache-2.0" }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2595,15 +2567,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -2618,7 +2581,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", - "dev": true, "funding": [ { "type": "github", @@ -2633,7 +2595,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, "engines": { "node": ">=6" } @@ -2642,7 +2603,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -2654,7 +2614,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", - "dev": true, "license": "MIT", "dependencies": { "string-width": "^4.2.0" @@ -2670,7 +2629,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" @@ -2711,7 +2669,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2722,20 +2679,17 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2746,7 +2700,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -2759,7 +2712,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, "engines": { "node": ">= 6" } @@ -2775,7 +2727,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, "engines": { "node": ">=4.0.0" } @@ -2816,14 +2767,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2834,10 +2783,9 @@ } }, "node_modules/cypress": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.0.0.tgz", - "integrity": "sha512-OH5Srk10qTzHYYt3BsP9V1DPYIAzms55s3xQn4mGmYO4k6pi25MCajDyPbiULfNDhNcthNQ2xmYvu1JdeEw1Hw==", - "dev": true, + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.4.0.tgz", + "integrity": "sha512-+GC/Y/LXAcaMCzfuM7vRx5okRmonceZbr0ORUAoOrZt/5n2eGK8yh04bok1bWSjZ32wRHrZESqkswQ6biArN5w==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -2845,13 +2793,13 @@ "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", + "@types/tmp": "^0.2.3", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", - "check-more-types": "^2.24.0", "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", @@ -2866,10 +2814,8 @@ "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", - "getos": "^3.2.1", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", @@ -2881,6 +2827,7 @@ "request-progress": "^3.0.0", "semver": "^7.7.1", "supports-color": "^8.1.1", + "systeminformation": "5.27.7", "tmp": "~0.2.4", "tree-kill": "1.2.2", "untildify": "^4.0.0", @@ -2890,7 +2837,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.1.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/cypress-mailhog": { @@ -2900,17 +2847,24 @@ "dev": true, "license": "MIT" }, + "node_modules/cypress-real-events": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.15.0.tgz", + "integrity": "sha512-in98xxTnnM9Z7lZBvvVozm99PBT2eEOjXRG5LKWyYvQnj9mGWXMiPNpfw7e7WiraBFh7XlXIxnE9Cu5o+52kQQ==", + "license": "MIT", + "peerDependencies": { + "cypress": "^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x || ^14.x || ^15.x" + } + }, "node_modules/cypress/node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/cypress/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2923,7 +2877,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2938,7 +2891,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" @@ -2950,8 +2902,7 @@ "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, "node_modules/debug": { "version": "4.3.7", @@ -2997,7 +2948,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3019,7 +2969,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3034,7 +2983,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, "license": "MIT", "dependencies": { "jsbn": "~0.1.0", @@ -3050,14 +2998,12 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -3080,7 +3026,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -3093,7 +3038,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3103,7 +3047,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3120,7 +3063,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3133,7 +3075,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3164,7 +3105,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -3242,8 +3182,7 @@ "node_modules/eventemitter2": { "version": "6.4.7", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==" }, "node_modules/events": { "version": "3.3.0", @@ -3259,7 +3198,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -3282,7 +3220,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, "dependencies": { "pify": "^2.2.0" }, @@ -3294,14 +3231,12 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, "license": "MIT" }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -3321,7 +3256,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, "engines": [ "node >=0.6.0" ], @@ -3377,7 +3311,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, "dependencies": { "pend": "~1.2.0" } @@ -3386,7 +3319,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -3456,7 +3388,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "*" @@ -3466,7 +3397,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -3503,7 +3433,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -3524,7 +3453,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3551,7 +3479,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3585,7 +3512,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3599,7 +3525,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -3610,20 +3535,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "dependencies": { - "async": "^3.2.0" - } - }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" @@ -3673,7 +3588,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, "dependencies": { "ini": "2.0.0" }, @@ -3717,7 +3631,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3729,14 +3642,12 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3745,7 +3656,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3758,7 +3668,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3774,7 +3683,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, "dependencies": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" @@ -3790,7 +3698,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, "engines": { "node": ">=8" } @@ -3799,7 +3706,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -3817,7 +3723,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", @@ -3832,7 +3737,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, "engines": { "node": ">=8.12.0" } @@ -3841,7 +3745,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -3879,7 +3782,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, "engines": { "node": ">=8" } @@ -3905,7 +3807,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, "engines": { "node": ">=10" } @@ -3939,7 +3840,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3960,7 +3860,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" @@ -3985,7 +3884,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3994,7 +3892,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -4005,14 +3902,12 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, "engines": { "node": ">=10" }, @@ -4032,14 +3927,12 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, "license": "MIT" }, "node_modules/istanbul-lib-coverage": { @@ -4228,7 +4121,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { @@ -4254,7 +4146,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { @@ -4268,7 +4159,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, "license": "ISC" }, "node_modules/json5": { @@ -4287,7 +4177,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -4299,7 +4188,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, "engines": [ "node >=0.6.0" ], @@ -4311,20 +4199,10 @@ "verror": "1.10.0" } }, - "node_modules/lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, - "engines": { - "node": "> 0.8" - } - }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", @@ -4372,8 +4250,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -4391,14 +4268,12 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -4414,7 +4289,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -4432,7 +4306,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -4449,7 +4322,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4487,7 +4359,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4496,8 +4367,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -4525,7 +4395,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4534,7 +4403,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -4546,7 +4414,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -4567,7 +4434,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4639,7 +4505,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "dependencies": { "path-key": "^3.0.0" }, @@ -4739,7 +4604,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4752,7 +4616,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -4761,7 +4624,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -4775,8 +4637,7 @@ "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==" }, "node_modules/p-limit": { "version": "2.3.0", @@ -4809,7 +4670,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -4866,7 +4726,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -4910,14 +4769,12 @@ "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, "license": "MIT" }, "node_modules/pg": { @@ -5023,7 +4880,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5158,7 +5014,6 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, "engines": { "node": ">=6" }, @@ -5170,7 +5025,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, "engines": { "node": ">= 0.6.0" } @@ -5190,14 +5044,12 @@ "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==" }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -5217,7 +5069,6 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -5350,7 +5201,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, "dependencies": { "throttleit": "^1.0.0" } @@ -5411,7 +5261,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -5433,8 +5282,7 @@ "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, "node_modules/rimraf": { "version": "3.0.2", @@ -5479,7 +5327,6 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -5488,7 +5335,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -5508,7 +5354,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/schema-utils": { @@ -5560,7 +5405,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5572,7 +5416,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -5581,7 +5424,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5601,7 +5443,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5618,7 +5459,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5637,7 +5477,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5656,8 +5495,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/slash": { "version": "3.0.0", @@ -5672,7 +5510,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -5737,7 +5574,6 @@ "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, "license": "MIT", "dependencies": { "asn1": "~0.2.3", @@ -5763,7 +5599,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5777,7 +5612,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5798,7 +5632,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, "engines": { "node": ">=6" } @@ -5807,7 +5640,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5828,6 +5660,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/systeminformation": { + "version": "5.27.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.7.tgz", + "integrity": "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==", + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -5970,7 +5828,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -5978,14 +5835,12 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, "license": "MIT", "dependencies": { "tldts-core": "^6.1.86" @@ -5998,14 +5853,12 @@ "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, "license": "MIT" }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.14" @@ -6027,7 +5880,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "tldts": "^6.1.32" @@ -6040,7 +5892,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, "bin": { "tree-kill": "cli.js" } @@ -6048,14 +5899,12 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -6068,14 +5917,12 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, "license": "Unlicense" }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, "engines": { "node": ">=10" }, @@ -6140,7 +5987,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, "engines": { "node": ">= 10.0.0" } @@ -6149,7 +5995,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, "engines": { "node": ">=8" } @@ -6198,7 +6043,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -6207,7 +6051,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, "engines": [ "node >=0.6.0" ], @@ -6346,7 +6189,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6367,7 +6209,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6383,8 +6224,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -6457,7 +6297,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/cypress-tests/package.json b/cypress-tests/package.json index ff8c991918..da92a4f936 100644 --- a/cypress-tests/package.json +++ b/cypress-tests/package.json @@ -10,13 +10,14 @@ "@cypress/code-coverage": "^3.12.12", "@cypress/webpack-preprocessor": "^5.12.0", "@faker-js/faker": "^7.3.0", - "cypress": "^15.0.0", + "cypress": "^15.4.0", "cypress-mailhog": "^2.5.0" }, "dependencies": { + "cypress-real-events": "^1.15.0", "moment": "^2.29.4", "node-xlsx": "^0.4.0", "pdf-parse": "^1.1.1", "pg": "^8.8.0" } -} \ No newline at end of file +} diff --git a/deploy/ec2/ee/setup_app b/deploy/ec2/ee/setup_app index 2c3233000d..2defef26c8 100755 --- a/deploy/ec2/ee/setup_app +++ b/deploy/ec2/ee/setup_app @@ -170,7 +170,7 @@ fi TOOLJET_EDTION=ee npm --prefix server run db:setup -if sudo -E systemctl start nest && && sudo journalctl -u nest -f +if sudo -E systemctl start nest && sudo journalctl -u nest -f then echo "The app will be served at ${TOOLJET_HOST}" else diff --git a/docker/LTS/ee/ee-production.Dockerfile b/docker/LTS/ee/ee-production.Dockerfile index 034d8e5ea5..eb1fa3c058 100644 --- a/docker/LTS/ee/ee-production.Dockerfile +++ b/docker/LTS/ee/ee-production.Dockerfile @@ -168,6 +168,12 @@ RUN mkdir -p /home/appuser \ && chmod -R g=u /home/appuser \ && npm cache clean --force +# Create rsyslog directory for audit logs with proper permissions +RUN mkdir -p /home/appuser/rsyslog \ + && chown -R appuser:0 /home/appuser/rsyslog \ + && chmod g+s /home/appuser/rsyslog \ + && chmod -R g=u /home/appuser/rsyslog + # Create directory /tmp/.npm/npm-cache/ and set ownership to appuser RUN mkdir -p /tmp/.npm/npm-cache/ \ && chown -R appuser:0 /tmp/.npm/npm-cache/ \ diff --git a/docker/pre-release/ee/ee-production.Dockerfile b/docker/pre-release/ee/ee-production.Dockerfile index 7dc300436a..fb39eaa921 100644 --- a/docker/pre-release/ee/ee-production.Dockerfile +++ b/docker/pre-release/ee/ee-production.Dockerfile @@ -164,6 +164,12 @@ RUN mkdir -p /home/appuser \ && chmod -R g=u /home/appuser \ && npm cache clean --force +# Create rsyslog directory for audit logs with proper permissions +RUN mkdir -p /home/appuser/rsyslog \ + && chown -R appuser:0 /home/appuser/rsyslog \ + && chmod g+s /home/appuser/rsyslog \ + && chmod -R g=u /home/appuser/rsyslog + # Create directory /tmp/.npm/npm-cache/ and set ownership to appuser RUN mkdir -p /tmp/.npm/npm-cache/ \ && chown -R appuser:0 /tmp/.npm/npm-cache/ \ diff --git a/frontend/.storybook/decorators.jsx b/frontend/.storybook/decorators.jsx index eee828eccf..e72f6af4f7 100644 --- a/frontend/.storybook/decorators.jsx +++ b/frontend/.storybook/decorators.jsx @@ -1,14 +1,19 @@ // storybookDecorators.js -import React from 'react'; +import React from "react"; +import { MemoryRouter } from "react-router-dom"; export function withColorScheme(story, context) { - const darkMode = context?.globals?.backgrounds?.value === '#333333'; // Access theme mode from globals - const className = darkMode ? 'dark-theme' : ''; + const darkMode = context?.globals?.backgrounds?.value === "#333333"; // Access theme mode from globals + const className = darkMode ? "dark-theme" : ""; return ( -
OR START WITH
++ OR START WITH +
{title}
-+
{description}
); } -function GetStartedWidget({ type, to, onClick }) { +function GetStartedWidget({ type, to, onClick}) { const { title, description } = WIDGET_TYPES[type]; return ( -+ Mixed variants, sizes, and icon-only cases using Lucide's official DynamicIcon component. Notice how outline + and ghost variants use different icon colors for icon-only vs text buttons. +
++ Lucide icons use Tailwind CSS classes for colors. Compare the outline/ghost buttons with text vs icon-only + to see the color difference. +
+
User name
@@ -783,14 +784,13 @@ class BaseManageGroupPermissionResources extends React.Component {
- {user.email} + {user.email}