diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a12a2827d9..9177fdfefd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,6 +33,7 @@ env: server-plugins templates tests + qms-tests rush.json .prettierrc tools @@ -282,6 +283,99 @@ jobs: # with: # name: db-snapshot # path: ./tests/db_dump + uitest-qms: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + - name: Cache node modules + uses: actions/cache@v4 + env: + cache-name: cache-node-platform + with: + path: | + common/temp + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Checking for mis-matching dependencies... + run: node common/scripts/install-run-rush.js check + + - name: Installing... + run: node common/scripts/install-run-rush.js install + + - name: Docker Build + run: node common/scripts/install-run-rush.js docker:build -p 20 + env: + DOCKER_CLI_HINTS: false + - name: Prepare server + run: | + cd ./qms-tests + ./prepare.sh + - name: Install Playwright + run: | + cd ./qms-tests/sanity + node ../../common/scripts/install-run-rushx.js ci + - name: Run UI tests + run: | + cd ./qms-tests/sanity + node ../../common/scripts/install-run-rushx.js uitest + - name: "Store docker logs" + if: always() + run: | + cd ./qms-tests/sanity + mkdir logs + docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log + docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log + docker logs $(docker ps | grep front | cut -f 1 -d ' ') > logs/front.log + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-results-qms + path: ./qms-tests/sanity/playwright-report/ + # - name: Get Allure history + # uses: actions/checkout@v4 + # if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} + # continue-on-error: true + # with: + # ref: gh-pages + # path: gh-pages + # - name: Generates Allure Report + # uses: simple-elf/allure-report-action@master + # if: always() + # id: allure-report + # with: + # allure_results: ./qms-tests/sanity/allure-results/ + # gh_pages: gh-pages + # allure_report: allure-report + # allure_history: allure-history + # - name: Upload allure test results + # if: always() + # uses: actions/upload-artifact@v4 + # with: + # name: allure-report-qms + # path: ./allure-report/ + # - name: Deploy report to Github Pages + # if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} + # uses: peaceiris/actions-gh-pages@v4 + # with: + # PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # PUBLISH_BRANCH: gh-pages + # PUBLISH_DIR: allure-history + - name: Upload Logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: docker-logs-qms + path: ./qms-tests/sanity/logs uitest-uweb: runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index c36f96af5e..f0d8be300c 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -539,6 +539,9 @@ dependencies: '@rush-temp/products-resources': specifier: file:./projects/products-resources.tgz version: file:projects/products-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2) + '@rush-temp/qms-tests-sanity': + specifier: file:./projects/qms-tests-sanity.tgz + version: file:projects/qms-tests-sanity.tgz '@rush-temp/query': specifier: file:./projects/query.tgz version: file:projects/query.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2) @@ -23274,6 +23277,30 @@ packages: - ts-node dev: false + file:projects/qms-tests-sanity.tgz: + resolution: {integrity: sha512-ntTsOcgNEYuQVnuTv8IDdeGHDmCqEJpcQijo2H/OJJlOfHBj52ijRQCFB8Jnhv6L2RLEV9euSHAk+VGxvWsqyw==, tarball: file:projects/qms-tests-sanity.tgz} + name: '@rush-temp/qms-tests-sanity' + version: 0.0.0 + dependencies: + '@playwright/test': 1.41.2 + '@types/jest': 29.5.12 + '@types/node': 20.11.19 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + allure-playwright: 2.12.2 + cross-env: 7.0.3 + dotenv: 16.0.3 + eslint: 8.56.0 + eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3) + eslint-plugin-import: 2.29.1(eslint@8.56.0) + eslint-plugin-n: 15.7.0(eslint@8.56.0) + eslint-plugin-promise: 6.1.1(eslint@8.56.0) + prettier: 3.2.5 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: false + file:projects/query.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2): resolution: {integrity: sha512-XIZxHSUO5njnO8+G1dIkH+RFiZ3F3xsCoSCSySE1Gy6QEGebjZUu5uT0YTFZxYe9bo3jceL7gMHPaFs3fWgMxA==, tarball: file:projects/query.tgz} id: file:projects/query.tgz diff --git a/plugins/controlled-documents-resources/src/components/create-doc/steps/InfoStep.svelte b/plugins/controlled-documents-resources/src/components/create-doc/steps/InfoStep.svelte index 5d96b4d7de..4d0351db53 100644 --- a/plugins/controlled-documents-resources/src/components/create-doc/steps/InfoStep.svelte +++ b/plugins/controlled-documents-resources/src/components/create-doc/steps/InfoStep.svelte @@ -206,6 +206,7 @@ placeholder={documents.string.DocumentCodePlaceholder} disabled={loadingCodes} bind:value={docObject.code} + id="doc-code" kind="large-style" /> {#if codeNotUnique} diff --git a/qms-tests/.env b/qms-tests/.env new file mode 100644 index 0000000000..90fe452486 --- /dev/null +++ b/qms-tests/.env @@ -0,0 +1 @@ +STORAGE_CONFIG="minio|minio?accessKey=minioadmin&secretKey=minioadmin" \ No newline at end of file diff --git a/qms-tests/branding-test.json b/qms-tests/branding-test.json new file mode 100644 index 0000000000..d4c7d4ee17 --- /dev/null +++ b/qms-tests/branding-test.json @@ -0,0 +1,88 @@ +{ + "localhost:8083": { + "key": "tracex", + "title": "TraceX", + "protocol": "http", + "languages": "en", + "defaultLanguage": "en", + "defaultApplication": "documents", + "defaultSpace": "documents:space:QualityDocuments", + "defaultSpecial": "", + "lastNameFirst": "true", + "initWorkspace": "init-ws-qms", + "links": [ + { + "rel": "shortcut icon", + "type": "image/x-icon", + "href": "/tracex/favicon.ico" + }, + { + "rel": "icon", + "type": "image/svg+xml", + "sizes": "any", + "href": "/tracex/favicon.svg" + }, + { + "rel": "icon", + "type": "image/png", + "sizes": "16x16", + "href": "/tracex/favicon_16.png" + }, + { + "rel": "icon", + "type": "image/png", + "sizes": "32x32", + "href": "/tracex/favicon_32.png" + }, + { + "rel": "icon", + "type": "image/png", + "sizes": "192x192", + "href": "/tracex/favicon_192.png" + } + ] + }, + "localhost:8080": { + "key": "tracex-dev", + "title": "TraceX", + "protocol": "http", + "languages": "en", + "defaultLanguage": "en", + "defaultApplication": "documents", + "defaultSpace": "documents:space:QualityDocuments", + "defaultSpecial": "", + "lastNameFirst": "true", + "initWorkspace": "init-ws-qms", + "links": [ + { + "rel": "shortcut icon", + "type": "image/x-icon", + "href": "/tracex/favicon.ico" + }, + { + "rel": "icon", + "type": "image/svg+xml", + "sizes": "any", + "href": "/tracex/favicon.svg" + }, + { + "rel": "icon", + "type": "image/png", + "sizes": "16x16", + "href": "/tracex/favicon_16.png" + }, + { + "rel": "icon", + "type": "image/png", + "sizes": "32x32", + "href": "/tracex/favicon_32.png" + }, + { + "rel": "icon", + "type": "image/png", + "sizes": "192x192", + "href": "/tracex/favicon_192.png" + } + ] + } +} diff --git a/qms-tests/docker-compose.yaml b/qms-tests/docker-compose.yaml new file mode 100644 index 0000000000..7e9e1d7182 --- /dev/null +++ b/qms-tests/docker-compose.yaml @@ -0,0 +1,153 @@ +version: "3" +services: + mongodb: + image: 'mongo:7-jammy' + command: mongod --port 27018 + environment: + - PUID=1000 + - PGID=1000 + ports: + - 27018:27018 + restart: unless-stopped + minio: + image: 'minio/minio' + command: server /data --address ":9000" + expose: + - 9000 + ports: + - 9002:9000 + elastic: + image: 'elasticsearch:7.14.2' + command: | + /bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment; + /usr/local/bin/docker-entrypoint.sh eswrapper" + expose: + - 9200 + ports: + - 9201:9200 + environment: + - ELASTICSEARCH_PORT_NUMBER=9200 + - BITNAMI_DEBUG=true + - discovery.type=single-node + - ES_JAVA_OPTS=-Xms1024m -Xmx1024m + - http.cors.enabled=true + - http.cors.allow-origin=http://localhost:8082 + healthcheck: + interval: 20s + retries: 10 + test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"' + account: + image: hardcoreeng/account + pull_policy: never + links: + - mongodb + - minio + ports: + - 3003:3003 + volumes: + - ./branding-test.json:/var/cfg/branding-test.json + environment: + - ACCOUNT_PORT=3003 + - SERVER_SECRET=secret + - MONGO_URL=mongodb://mongodb:27018 + - TRANSACTOR_URL=ws://transactor:3334 + - ENDPOINT_URL=ws://localhost:3334 + - STORAGE_CONFIG=${STORAGE_CONFIG} + - MODEL_ENABLED=* + - BRANDING_PATH=/var/cfg/branding-test.json + front: + image: hardcoreeng/front + pull_policy: never + links: + - account + - mongodb + - minio + - elastic + - collaborator + - transactor + ports: + - 8083:8083 + volumes: + - ./branding-test.json:/app/dist/branding-test.json + environment: + - SERVER_PORT=8083 + - SERVER_SECRET=secret + - ACCOUNTS_URL=http://localhost:3003 + - MONGO_URL=mongodb://mongodb:27018 + - UPLOAD_URL=/files + - ELASTIC_URL=http://elastic:9200 + - GMAIL_URL=http://localhost:8088 + - CALENDAR_URL=http://localhost:8095 + - REKONI_URL=http://rekoni:4005 + - TELEGRAM_URL=http://localhost:8086 + - COLLABORATOR_URL=ws://localhost:3079 + - COLLABORATOR_API_URL=http://localhost:3079 + - STORAGE_CONFIG=${STORAGE_CONFIG} + - BRANDING_URL=http://localhost:8083/branding-test.json + transactor: + image: hardcoreeng/transactor + pull_policy: never + links: + - mongodb + - elastic + - minio + - rekoni + - account + ports: + - 3334:3334 + volumes: + - ./branding-test.json:/var/cfg/branding-test.json + environment: + - SERVER_PROVIDER=${SERVER_PROVIDER} + - SERVER_PORT=3334 + - SERVER_SECRET=secret + - ELASTIC_URL=http://elastic:9200 + - MONGO_URL=mongodb://mongodb:27018 + - METRICS_CONSOLE=false + - METRICS_FILE=metrics.txt + - STORAGE_CONFIG=${STORAGE_CONFIG} + - REKONI_URL=http://rekoni:4005 + - FRONT_URL=http://localhost:8083 + - UPLOAD_URL=http://localhost:8083/files + - ACCOUNTS_URL=http://account:3003 + - LAST_NAME_FIRST=true + - ELASTIC_INDEX_NAME=local_storage_index + - BRANDING_PATH=/var/cfg/branding-test.json + collaborator: + image: hardcoreeng/collaborator + links: + - mongodb + - minio + - transactor + ports: + - 3079:3079 + environment: + - COLLABORATOR_PORT=3079 + - SECRET=secret + - ACCOUNTS_URL=http://account:3003 + - TRANSACTOR_URL=ws://transactor:3334 + - UPLOAD_URL=/files + - MONGO_URL=mongodb://mongodb:27018 + - STORAGE_CONFIG=${STORAGE_CONFIG} + restart: unless-stopped + rekoni: + image: hardcoreeng/rekoni-service + restart: on-failure + ports: + - 4005:4004 + deploy: + resources: + limits: + memory: 1024M + # qms-workers: + # image: hardcoreeng/qms-workers + # restart: always + # links: + # - transactor + # - account + # environment: + # - SECRET=secret + # - TRANSACTOR_URL=ws://transactor:3334 + # - ACCOUNTS_URL=http://account:3003 +volumes: + files: diff --git a/qms-tests/prepare.sh b/qms-tests/prepare.sh new file mode 100755 index 0000000000..5d8efe91bd --- /dev/null +++ b/qms-tests/prepare.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +docker compose -p sanity kill +docker compose -p sanity down --volumes +docker compose -p sanity up -d --force-recreate --renew-anon-volumes +docker_exit=$? +if [ ${docker_exit} -eq 0 ]; then + echo "Container started successfully" +else + echo "Container started with errors" + exit ${docker_exit} +fi + + +# Create init workspace +./tool.sh create-workspace init-ws-qms -w InitTest +./tool.sh configure init-ws-qms --enable=* +./tool.sh configure init-ws-qms --list + +# Create workspace record in accounts +./tool.sh create-workspace sanity-ws-qms -w SanityTest +# Create user record in accounts +./tool.sh create-account user1 -f John -l Appleseed -p 1234 +./tool.sh create-account user2 -f Kainin -l Dirak -p 1234 +./tool.sh assign-workspace user1 sanity-ws-qms +./tool.sh assign-workspace user2 sanity-ws-qms +# Make user the workspace maintainer +./tool.sh confirm-email user1 +./tool.sh confirm-email user2 + +./tool.sh create-account user_qara -f Qara -l Admin -p 1234 +./tool.sh assign-workspace user_qara sanity-ws-qms +./tool.sh confirm-email user_qara + +./restore-workspace.sh \ No newline at end of file diff --git a/qms-tests/restore-workspace.sh b/qms-tests/restore-workspace.sh new file mode 100755 index 0000000000..6bc008a0d8 --- /dev/null +++ b/qms-tests/restore-workspace.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Restore workspace contents in mongo/elastic +./tool.sh backup-restore ./sanity-ws-qms/ sanity-ws-qms + +./tool.sh upgrade-workspace sanity-ws-qms + +# Re-assign user to workspace. +./tool.sh assign-workspace user1 sanity-ws-qms +./tool.sh assign-workspace user2 sanity-ws-qms +./tool.sh assign-workspace user_qara sanity-ws-qms + +./tool.sh configure sanity-ws-qms --enable=* +./tool.sh configure sanity-ws-qms --list diff --git a/qms-tests/sanity-ws-qms/000001/_migrations-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/_migrations-1716580272084-0.snp.gz new file mode 100644 index 0000000000..7a0f734319 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/_migrations-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/_migrations-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/_migrations-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..510ba2d7ca Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/_migrations-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/activity-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/activity-1716580272084-0.snp.gz new file mode 100644 index 0000000000..078261c83b Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/activity-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/activity-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/activity-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..dc9b28b299 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/activity-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/attachment-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/attachment-1716580272084-0.snp.gz new file mode 100644 index 0000000000..fb16221f9b Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/attachment-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/attachment-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/attachment-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..4d03e15b49 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/attachment-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/automation-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/automation-1716580272084-0.snp.gz new file mode 100644 index 0000000000..4630fd5c4a Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/automation-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/automation-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/automation-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..1b696a3427 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/automation-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/blob-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/blob-1716580272084-0.snp.gz new file mode 100644 index 0000000000..c22a67caa4 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/blob-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/blob-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/blob-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..6c523429cf Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/blob-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/calendar-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/calendar-1716580272084-0.snp.gz new file mode 100644 index 0000000000..60da960ea8 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/calendar-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/calendar-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/calendar-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..0eea763123 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/calendar-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/channel-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/channel-1716580272084-0.snp.gz new file mode 100644 index 0000000000..e1774f02c5 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/channel-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/channel-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/channel-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..9b1b99382c Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/channel-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/contact-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/contact-1716580272084-0.snp.gz new file mode 100644 index 0000000000..a1217cc477 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/contact-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/contact-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/contact-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..94d15bd296 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/contact-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/doc-index-state-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/doc-index-state-1716580272084-0.snp.gz new file mode 100644 index 0000000000..9461eec03d Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/doc-index-state-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/doc-index-state-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/doc-index-state-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..8065b29937 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/doc-index-state-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/documents-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/documents-1716580272084-0.snp.gz new file mode 100644 index 0000000000..4cc17e51fa Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/documents-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/documents-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/documents-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..2e852a5bc8 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/documents-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/fulltext-blob-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/fulltext-blob-1716580272084-0.snp.gz new file mode 100644 index 0000000000..a5f3698c1e Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/fulltext-blob-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/fulltext-blob-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/fulltext-blob-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..a7a5259e7d Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/fulltext-blob-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/hr-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/hr-1716580272084-0.snp.gz new file mode 100644 index 0000000000..ca09f02eb0 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/hr-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/hr-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/hr-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..2aff487e72 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/hr-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/kanban-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/kanban-1716580272084-0.snp.gz new file mode 100644 index 0000000000..4bdd9a1ab8 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/kanban-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/kanban-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/kanban-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..188ba5672a Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/kanban-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/love-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/love-1716580272084-0.snp.gz new file mode 100644 index 0000000000..24608bf650 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/love-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/love-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/love-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..80aecd54cd Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/love-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/notification-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/notification-1716580272084-0.snp.gz new file mode 100644 index 0000000000..5ea5129a5e Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/notification-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/notification-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/notification-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..5c589657c3 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/notification-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/request-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/request-1716580272084-0.snp.gz new file mode 100644 index 0000000000..b391ccbaa7 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/request-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/request-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/request-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..8728ac015d Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/request-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/space-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/space-1716580272084-0.snp.gz new file mode 100644 index 0000000000..bdda97274d Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/space-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/space-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/space-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..5fff0eefa5 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/space-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/status-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/status-1716580272084-0.snp.gz new file mode 100644 index 0000000000..17e8c4e747 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/status-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/status-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/status-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..67e0482881 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/status-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/tags-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/tags-1716580272084-0.snp.gz new file mode 100644 index 0000000000..f2adad67f9 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/tags-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/tags-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/tags-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..0b2b70ce6c Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/tags-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/training-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/training-1716580272084-0.snp.gz new file mode 100644 index 0000000000..97c7ba4e41 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/training-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/training-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/training-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..10fde8c070 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/training-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/tx-1716580272084-0.snp.gz b/qms-tests/sanity-ws-qms/000001/tx-1716580272084-0.snp.gz new file mode 100644 index 0000000000..21593d1bee Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/tx-1716580272084-0.snp.gz differ diff --git a/qms-tests/sanity-ws-qms/000001/tx-data-1716580272084-1.tar.gz b/qms-tests/sanity-ws-qms/000001/tx-data-1716580272084-1.tar.gz new file mode 100644 index 0000000000..d602bbefe6 Binary files /dev/null and b/qms-tests/sanity-ws-qms/000001/tx-data-1716580272084-1.tar.gz differ diff --git a/qms-tests/sanity-ws-qms/backup.json.gz b/qms-tests/sanity-ws-qms/backup.json.gz new file mode 100644 index 0000000000..c680c4bce2 Binary files /dev/null and b/qms-tests/sanity-ws-qms/backup.json.gz differ diff --git a/qms-tests/sanity/.env b/qms-tests/sanity/.env new file mode 100644 index 0000000000..6666334b84 --- /dev/null +++ b/qms-tests/sanity/.env @@ -0,0 +1,9 @@ +PLATFORM_URI='http://localhost:8083' +PLATFORM_TRANSACTOR='ws://localhost:3334' +PLATFORM_USER='user1' +PLATFORM_USER_SECOND='user2' +PLATFORM_TOKEN='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb25maXJtZWQiOnRydWUsImVtYWlsIjoidXNlcjEiLCJ3b3Jrc3BhY2UiOiJzYW5pdHktd3MtcW1zIiwicHJvZHVjdElkIjoiIn0.vQK1jI8gHkjnNJf5XZ71L4dCqyHNmKW4_iBGrhXrqW8' +SETTING=storage.json +SETTING_SECOND=storageSecond.json +SETTING_QARA_MANAGER=storageQaraManager.json +PLATFORM_PASSWORD='1234' \ No newline at end of file diff --git a/qms-tests/sanity/.eslintrc.js b/qms-tests/sanity/.eslintrc.js new file mode 100644 index 0000000000..72235dc283 --- /dev/null +++ b/qms-tests/sanity/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/qms-tests/sanity/.gitignore b/qms-tests/sanity/.gitignore new file mode 100644 index 0000000000..4e411de3b3 --- /dev/null +++ b/qms-tests/sanity/.gitignore @@ -0,0 +1,5 @@ +playwright-report +test-results/* +allure-report +allure-results +screenshots/* \ No newline at end of file diff --git a/qms-tests/sanity/config/rig.json b/qms-tests/sanity/config/rig.json new file mode 100644 index 0000000000..48fd47af6b --- /dev/null +++ b/qms-tests/sanity/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "default" +} diff --git a/qms-tests/sanity/package.json b/qms-tests/sanity/package.json new file mode 100644 index 0000000000..ab5dd2ddaf --- /dev/null +++ b/qms-tests/sanity/package.json @@ -0,0 +1,53 @@ +{ + "name": "@hcengineering/qms-tests-sanity", + "version": "0.6.0", + "main": "lib/index.js", + "svelte": "src/index.ts", + "types": "types/index.d.ts", + "author": "Hardcore Engineering Inc", + "template": "@hcengineering/default-package", + "license": "EPL-2.0", + "scripts": { + "build": "compile", + "build:watch": "compile", + "_phase:build": "compile transpile tests", + "_phase:test": "", + "_phase:format": "format src", + "_phase:validate": "compile validate", + "lint:fix": "eslint --fix tests", + "lint": "eslint tests", + "format": "format tests", + "ci": "playwright install --with-deps chromium", + "test": "", + "uitest": "cross-env LOCAL_URL=http://localhost:3003/ DEV_URL= playwright test -c ./tests/playwright.config.ts", + "staging-uitest": "cross-env PLATFORM_URI=https://front.hc.engineering/ playwright test -c ./tests/playwright.config.ts --grep @staging", + "dev-uitest": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 SETTING=storage-dev.json SETTING_SECOND=storageSecond-dev.json DEV_URL=http://localhost:8080/account playwright test -c ./tests/playwright.config.ts", + "debug": "playwright test -c ./tests/playwright.config.ts --debug --headed", + "dev-debug": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 SETTING=storage-dev.json SETTING_SECOND=storageSecond-dev.json playwright test -c ./tests/playwright.config.ts --debug --headed", + "codegen": "playwright codegen --load-storage storage.json http://localhost:8083/workbench/sanity-ws/", + "dev-codegen": "cross-env playwright codegen --load-storage storage-dev.json http://localhost:8080/workbench/sanity-ws/", + "allure:generate": "allure generate allure-results -o allure-report --clean" + }, + "devDependencies": { + "@hcengineering/platform-rig": "^0.6.0", + "@types/jest": "^29.5.5", + "@types/node": "~20.11.16", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-n": "^15.4.0", + "eslint": "^8.54.0", + "@typescript-eslint/parser": "^6.11.0", + "eslint-config-standard-with-typescript": "^40.0.0", + "prettier": "^3.1.0", + "typescript": "^5.3.3", + "@playwright/test": "^1.41.2", + "allure-playwright": "^2.9.2" + }, + "dependencies": { + "dotenv": "~16.0.0", + "cross-env": "~7.0.3", + "@hcengineering/core": "^0.6.32", + "@hcengineering/client-resources": "^0.6.27" + } +} diff --git a/qms-tests/sanity/storage-dev.json b/qms-tests/sanity/storage-dev.json new file mode 100644 index 0000000000..4281c93edd --- /dev/null +++ b/qms-tests/sanity/storage-dev.json @@ -0,0 +1,34 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "login:metadata:LoginEmail", + "value": "user1" + }, + { + "name": "login:metadata:LoginTokens", + "value": "{\"sanity-ws-qms\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb25maXJtZWQiOnRydWUsImVtYWlsIjoidXNlcjEiLCJ3b3Jrc3BhY2UiOiJzYW5pdHktd3MtcW1zIiwicHJvZHVjdElkIjoiIn0.vQK1jI8gHkjnNJf5XZ71L4dCqyHNmKW4_iBGrhXrqW8\"}" + }, + { + "name": "login:metadata:LoginEndpoint", + "value": "ws://localhost:3334" + }, + { + "name": "#platform.notification.logging", + "value": "false" + }, + { + "name": "#platform.lazy.loading", + "value": "false" + }, + { + "name": "flagOpenInDesktopApp", + "value": "true" + } + ] + } + ] +} \ No newline at end of file diff --git a/qms-tests/sanity/storage.json b/qms-tests/sanity/storage.json new file mode 100644 index 0000000000..65a8911bb0 --- /dev/null +++ b/qms-tests/sanity/storage.json @@ -0,0 +1,34 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8083", + "localStorage": [ + { + "name": "login:metadata:LoginEmail", + "value": "user1" + }, + { + "name": "login:metadata:LoginTokens", + "value": "{\"sanity-ws-qms\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb25maXJtZWQiOnRydWUsImVtYWlsIjoidXNlcjEiLCJ3b3Jrc3BhY2UiOiJzYW5pdHktd3MtcW1zIiwicHJvZHVjdElkIjoiIn0.vQK1jI8gHkjnNJf5XZ71L4dCqyHNmKW4_iBGrhXrqW8\"}" + }, + { + "name": "login:metadata:LoginEndpoint", + "value": "ws://localhost:3334" + }, + { + "name": "#platform.notification.logging", + "value": "false" + }, + { + "name": "#platform.lazy.loading", + "value": "false" + }, + { + "name": "flagOpenInDesktopApp", + "value": "true" + } + ] + } + ] +} \ No newline at end of file diff --git a/qms-tests/sanity/storageQaraManager.json b/qms-tests/sanity/storageQaraManager.json new file mode 100644 index 0000000000..5b998b9b6c --- /dev/null +++ b/qms-tests/sanity/storageQaraManager.json @@ -0,0 +1,34 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8083", + "localStorage": [ + { + "name": "login:metadata:LoginEmail", + "value": "user_qara" + }, + { + "name": "login:metadata:LoginTokens", + "value": "{\"sanity-ws-qms\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb25maXJtZWQiOnRydWUsImVtYWlsIjoidXNlcl9xYXJhIiwid29ya3NwYWNlIjoic2FuaXR5LXdzLXFtcyIsInByb2R1Y3RJZCI6IiJ9.6OuCr6aIkzS9TiUAWbnFbsK98NKnCNqAY92ZkmYvw6k\"}" + }, + { + "name": "login:metadata:LoginEndpoint", + "value": "ws://localhost:3334" + }, + { + "name": "#platform.notification.logging", + "value": "false" + }, + { + "name": "#platform.lazy.loading", + "value": "false" + }, + { + "name": "flagOpenInDesktopApp", + "value": "true" + } + ] + } + ] +} \ No newline at end of file diff --git a/qms-tests/sanity/storageSecond-dev.json b/qms-tests/sanity/storageSecond-dev.json new file mode 100644 index 0000000000..ba2fd969ee --- /dev/null +++ b/qms-tests/sanity/storageSecond-dev.json @@ -0,0 +1,34 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "login:metadata:LoginEmail", + "value": "user2" + }, + { + "name": "login:metadata:LoginTokens", + "value": "{\"sanity-ws-qms\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb25maXJtZWQiOnRydWUsImVtYWlsIjoidXNlcjIiLCJ3b3Jrc3BhY2UiOiJzYW5pdHktd3MtcW1zIiwicHJvZHVjdElkIjoiIn0.-al3BXM41qvOaZ4s7c96xQZO73udvQrs8dH2bmhScww\"}" + }, + { + "name": "login:metadata:LoginEndpoint", + "value": "ws://localhost:3334" + }, + { + "name": "#platform.notification.logging", + "value": "false" + }, + { + "name": "#platform.lazy.loading", + "value": "false" + }, + { + "name": "flagOpenInDesktopApp", + "value": "true" + } + ] + } + ] +} \ No newline at end of file diff --git a/qms-tests/sanity/storageSecond.json b/qms-tests/sanity/storageSecond.json new file mode 100644 index 0000000000..ed4819a069 --- /dev/null +++ b/qms-tests/sanity/storageSecond.json @@ -0,0 +1,34 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8083", + "localStorage": [ + { + "name": "login:metadata:LoginEmail", + "value": "user2" + }, + { + "name": "login:metadata:LoginTokens", + "value": "{\"sanity-ws-qms\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb25maXJtZWQiOnRydWUsImVtYWlsIjoidXNlcjIiLCJ3b3Jrc3BhY2UiOiJzYW5pdHktd3MtcW1zIiwicHJvZHVjdElkIjoiIn0.-al3BXM41qvOaZ4s7c96xQZO73udvQrs8dH2bmhScww\"}" + }, + { + "name": "login:metadata:LoginEndpoint", + "value": "ws://localhost:3334" + }, + { + "name": "#platform.notification.logging", + "value": "false" + }, + { + "name": "#platform.lazy.loading", + "value": "false" + }, + { + "name": "flagOpenInDesktopApp", + "value": "true" + } + ] + } + ] +} \ No newline at end of file diff --git a/qms-tests/sanity/tests/documents/categories.spec.ts b/qms-tests/sanity/tests/documents/categories.spec.ts new file mode 100644 index 0000000000..752327c81c --- /dev/null +++ b/qms-tests/sanity/tests/documents/categories.spec.ts @@ -0,0 +1,170 @@ +import { test } from '@playwright/test' +import { attachScreenshot, generateId, HomepageURI, PlatformSetting, PlatformURI } from '../utils' +import { DocumentStatus, NewCategory, NewTemplate, UpdateCategory } from '../model/types' +import { allure } from 'allure-playwright' +import { CategoriesPage } from '../model/documents/categories-page' +import { CategoryDetailsPage } from '../model/documents/category-details-page' +import { prepareCategoryStep } from './common-documents-steps' +import { LeftSideMenuPage } from '../model/left-side-menu-page' +import { NavigationMenuPage } from '../model/documents/navigation-menu-page' +import { TemplatesPage } from '../model/documents/templates-page' +import { DocumentContentPage } from '../model/documents/document-content-page' + +test.use({ + storageState: PlatformSetting +}) + +test.describe('QMS. Categories tests', () => { + test.beforeEach(async ({ page }) => { + await (await page.goto(`${PlatformURI}/${HomepageURI}`))?.finished() + }) + + test('TESTS-131. Create a new category', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new category') + await allure.tms('TESTS-131', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-131') + const newCategory: NewCategory = { + title: `New category-${generateId()}`, + code: `CODE-${generateId(4)}`, + description: `New category description-${generateId()}`, + attachFileName: 'cat.jpeg' + } + + await prepareCategoryStep(page, newCategory) + + await test.step('2. Check category information', async () => { + const categoriesPage = new CategoriesPage(page) + await categoriesPage.openCategory(newCategory.title) + + const categoryDetailsPage = new CategoryDetailsPage(page) + await categoryDetailsPage.checkTitle(newCategory.title) + }) + + await attachScreenshot('TESTS-131_create_category.png', page) + }) + + test('TESTS-132. Edit a Category', async ({ page }) => { + await allure.description('Requirement\nUsers need to edit a category') + await allure.tms('TESTS-132', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-132') + const editCategory: NewCategory = { + title: `Edit category-${generateId()}`, + code: `EDIT-${generateId(4)}`, + description: `Edit category description-${generateId()}` + } + const updatedEditCategory: UpdateCategory = { + description: `Updated edit category description-${generateId()}`, + attachFileName: 'cat.jpeg' + } + + await prepareCategoryStep(page, editCategory) + + await test.step('2. Edit Description and add Attachments', async () => { + const categoriesPage = new CategoriesPage(page) + await categoriesPage.openCategory(editCategory.title) + + const categoryDetailsPage = new CategoryDetailsPage(page) + await categoryDetailsPage.checkCategory(editCategory) + await categoryDetailsPage.editCategory(updatedEditCategory) + }) + + await test.step('3. Check category information', async () => { + const categoryDetailsPage = new CategoryDetailsPage(page) + await categoryDetailsPage.checkCategory({ + ...editCategory, + ...updatedEditCategory + }) + }) + + await attachScreenshot('TESTS-132_edit_category.png', page) + }) + + test('TESTS-133. Delete Category', async ({ page }) => { + await allure.description('Requirement\nUsers need to delete category') + await allure.tms('TESTS-133', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-133') + const editCategory: NewCategory = { + title: `Delete category-${generateId()}`, + code: `DELETE-${generateId(4)}`, + description: `Delete category description-${generateId()}` + } + + await prepareCategoryStep(page, editCategory) + + await test.step('2. Delete a Category', async () => { + const categoriesPage = new CategoriesPage(page) + await categoriesPage.openCategory(editCategory.title) + + const categoryDetailsPage = new CategoryDetailsPage(page) + await categoryDetailsPage.checkCategory(editCategory) + await categoryDetailsPage.executeMoreAction('Delete') + }) + + await test.step('3. Check category doesn’t exist', async () => { + const categoriesPage = new CategoriesPage(page) + await categoriesPage.checkCategoryNotExist(editCategory.title) + }) + + await attachScreenshot('TESTS-133_delete_category.png', page) + }) + + test('TESTS-215. Can not Delete Category if there are templates associated', async ({ page }) => { + await allure.description('Requirement\nUsers can not delete the category if there are templates associated') + await allure.tms('TESTS-215', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-215') + const canNotDeleteCategory: NewCategory = { + title: `Can not Delete category-${generateId()}`, + code: `CANT_DELETE-${generateId(4)}`, + description: `Can not Delete category description-${generateId()}` + } + const canNotDeleteCategoryTemplate: NewTemplate = { + location: { + space: 'Quality documents', + parent: 'Quality documents' + }, + title: `Can not Delete Category Template-${generateId()}`, + description: `Can not Delete Category Template description-${generateId()}`, + code: generateId(4), + category: canNotDeleteCategory.title, + reason: 'Test reason', + reviewers: ['Appleseed John'], + approvers: ['Appleseed John'] + } + + await prepareCategoryStep(page, canNotDeleteCategory) + + await test.step('2. Create new templates to the category', async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() + + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonTemplates.click() + + const templatesPage = new TemplatesPage(page) + await templatesPage.buttonCreateTemplate.click() + + await templatesPage.createTemplate(canNotDeleteCategoryTemplate) + + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.checkDocumentTitle(canNotDeleteCategoryTemplate.title) + await documentContentPage.checkDocument({ + type: 'N/A', + category: canNotDeleteCategoryTemplate.category ?? '', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + }) + }) + + await test.step('3. Try to Delete a Category', async () => { + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonCategories.click() + + const categoriesPage = new CategoriesPage(page) + await categoriesPage.openCategory(canNotDeleteCategory.title) + + const categoryDetailsPage = new CategoryDetailsPage(page) + await categoryDetailsPage.checkCategory(canNotDeleteCategory) + await categoryDetailsPage.checkMoreActionNotExist('Delete') + }) + + await attachScreenshot('TESTS-215_can_not_delete_category.png', page) + }) +}) diff --git a/qms-tests/sanity/tests/documents/common-documents-steps.ts b/qms-tests/sanity/tests/documents/common-documents-steps.ts new file mode 100644 index 0000000000..921baa6505 --- /dev/null +++ b/qms-tests/sanity/tests/documents/common-documents-steps.ts @@ -0,0 +1,35 @@ +import { Page, test } from '@playwright/test' +import { LeftSideMenuPage } from '../model/left-side-menu-page' +import { DocumentsPage } from '../model/documents/documents-page' +import { NewCategory, NewDocument } from '../model/types' +import { NavigationMenuPage } from '../model/documents/navigation-menu-page' +import { CategoriesPage } from '../model/documents/categories-page' +import { CategoryCreatePopup } from '../model/documents/category-create-popup' + +export async function prepareDocumentStep (page: Page, document: NewDocument, stepNumber: number = 1): Promise { + await test.step(`${stepNumber}. Create a new document`, async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() + + const documentsPage = new DocumentsPage(page) + await documentsPage.buttonCreateDocument.click() + + await documentsPage.createDocument(document) + }) +} + +export async function prepareCategoryStep (page: Page, newCategory: NewCategory): Promise { + await test.step('1. Create a new category', async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() + + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonCategories.click() + + const categoriesPage = new CategoriesPage(page) + await categoriesPage.buttonCreateCategory.click() + + const categoryCreatePopup = new CategoryCreatePopup(page) + await categoryCreatePopup.createCategory(newCategory) + }) +} diff --git a/qms-tests/sanity/tests/documents/documents.spec.ts b/qms-tests/sanity/tests/documents/documents.spec.ts new file mode 100644 index 0000000000..ce2a72014e --- /dev/null +++ b/qms-tests/sanity/tests/documents/documents.spec.ts @@ -0,0 +1,1114 @@ +import { expect, test } from '@playwright/test' +import { + attachScreenshot, + DocumentURI, + generateId, + getQaraManagerPage, + getSecondPage, + HomepageURI, + PlatformSetting, + PlatformURI +} from '../utils' +import { allure } from 'allure-playwright' +import { DocumentsPage } from '../model/documents/documents-page' +import { Content, DocumentDetails, DocumentRights, DocumentStatus, NewDocument } from '../model/types' +import { DocumentContentPage } from '../model/documents/document-content-page' +import { DocumentCommentsPage } from '../model/documents/document-comments-page' +import { prepareDocumentStep } from './common-documents-steps' +import { DocumentApprovalsPage } from '../model/documents/document-approvals-page' +import { DocumentReleasePage } from '../model/documents/document-release-page' +import { DocumentReasonAndImpactPage } from '../model/documents/document-reason-impact-page' +import { LeftSideMenuPage } from '../model/left-side-menu-page' +import { DocumentHistoryPage } from '../model/documents/document-history-page' + +test.use({ + storageState: PlatformSetting +}) + +test.describe('QMS. Documents tests', () => { + test.beforeEach(async ({ page }) => { + await (await page.goto(`${PlatformURI}/${HomepageURI}`))?.finished() + }) + + test('TESTS-123. Create a document', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new document') + await allure.tms('TESTS-123', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-123') + const newDocument: NewDocument = { + template: 'HR (HR)', + title: `New Document-${generateId()}`, + description: `New Document description-${generateId()}` + } + await prepareDocumentStep(page, newDocument) + + await test.step('2. Check document information', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.checkDocumentTitle(newDocument.title) + }) + + await attachScreenshot('TESTS-123_create_document.png', page) + }) + + test('TESTS-124. Edit document', async ({ page }) => { + await allure.description('Requirement\nUsers need to edit the created document') + await allure.tms('TESTS-124', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-124') + const editDocument: NewDocument = { + template: 'HR (HR)', + title: `Edit Document-${generateId()}`, + description: `Edit Document description-${generateId()}` + } + const newContent: Content = { + sectionTitle: `Overview-${generateId()}`, + content: `New content-${generateId()}!!!!` + } + await prepareDocumentStep(page, editDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Update the created document content', async () => { + await documentContentPage.checkDocumentTitle(editDocument.title) + await documentContentPage.updateSectionTitle('1', newContent.sectionTitle) + await documentContentPage.addContentToTheSection(newContent) + }) + + await test.step('3. Check the updated document information', async () => { + await documentContentPage.checkContentForTheSection(newContent) + }) + + await attachScreenshot('TESTS-124_edit_document.png', page) + }) + + test('TESTS-127. Delete Document', async ({ page }) => { + await allure.description('Requirement\nUsers need to delete the document') + await allure.tms('TESTS-127', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-127') + const deleteDocument: NewDocument = { + template: 'HR (HR)', + title: `Delete Document-${generateId()}`, + description: `Delete Document description-${generateId()}` + } + await prepareDocumentStep(page, deleteDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Delete the document', async () => { + await documentContentPage.checkDocumentTitle(deleteDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.executeMoreActions('Delete') + await documentContentPage.pressYesForPopup(page) + }) + + await test.step('3. Check that the document status is equal to deleted status', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.DELETED) + }) + + await attachScreenshot('TESTS-127_delete_document.png', page) + }) + + test('TESTS-125. Create child document', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new child document') + await allure.tms('TESTS-125', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-125') + const parentDocument: NewDocument = { + template: 'HR (HR)', + title: `Parent Document-${generateId()}`, + description: `Parent Document description-${generateId()}` + } + const childDocument: NewDocument = { + template: 'HR (HR)', + title: `Child Document-${generateId()}`, + description: `Child Document description-${generateId()}` + } + await prepareDocumentStep(page, parentDocument) + + await test.step('2. Create a new child document for the document from the previous step', async () => { + const documentsPage = new DocumentsPage(page) + await documentsPage.executeMoreActionsOnDocument(parentDocument.title, 'Create child document') + await documentsPage.createDocument(childDocument, true) + }) + + await test.step('3. Check the child document information', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.checkDocumentTitle(childDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + }) + + await attachScreenshot('TESTS-125_create_child_document.png', page) + }) + + test('TESTS-126. Change document owner', async ({ page }) => { + await allure.description('Requirement\nUsers need to change document owner') + await allure.tms('TESTS-126', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-126') + const changeDocument: NewDocument = { + template: 'HR (HR)', + title: `Change document owner Document-${generateId()}`, + description: `Change document owner Document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + await prepareDocumentStep(page, changeDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Change document owner', async () => { + await documentContentPage.checkDocumentTitle(changeDocument.title) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.executeMoreActions('Change document owner') + await documentContentPage.fillChangeDocumentOwnerPopup('Dirak Kainin') + }) + + await test.step('3. Check the updated document information', async () => { + await documentContentPage.checkDocument({ + ...documentDetails, + owner: 'Dirak Kainin' + }) + }) + await attachScreenshot('TESTS-126_change_document_owner.png', page) + }) + + test('TESTS-134. Send for review document', async ({ page }) => { + await allure.description('Requirement\nUsers need to send the document for review') + await allure.tms('TESTS-134', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-134') + const sendForReviewDocument: NewDocument = { + template: 'HR (HR)', + title: `Send for review document-${generateId()}`, + description: `Send for review document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + await prepareDocumentStep(page, sendForReviewDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Send the document for review', async () => { + await documentContentPage.checkDocumentTitle(sendForReviewDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + await documentContentPage.buttonSendForReview.click() + await documentContentPage.fillSelectReviewersForm(['Dirak Kainin']) + }) + + await test.step('3. Check the document status', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_REVIEW + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + await attachScreenshot('TESTS-134_send_for_review_document.png', page) + }) + + test('TESTS-135. Send for approval document', async ({ page }) => { + await allure.description('Requirement\nUsers need to send the document for approval') + await allure.tms('TESTS-135', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-135') + const changeDocument: NewDocument = { + template: 'HR (HR)', + title: `Send for approval document-${generateId()}`, + description: `Send for approval document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + await prepareDocumentStep(page, changeDocument) + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Send the document for approval', async () => { + await documentContentPage.checkDocumentTitle(changeDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm(['Dirak Kainin']) + }) + + await test.step('3. Check the document status', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + await attachScreenshot('TESTS-135_send_for_approval_document.png', page) + }) + + test('TESTS-136. Add and resolve Comments', async ({ page }) => { + await allure.description('Requirement\nUsers need to create new comments and resolve them') + await allure.tms('TESTS-136', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-136') + const commentsDocument: NewDocument = { + template: 'HR (HR)', + title: `Add and Done Comments document-${generateId()}`, + description: `Add and Done Comments document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const newContentFirst: Content = { + sectionTitle: `Overview-${generateId()}`, + content: `New content-${generateId()}!!!!` + } + const messageToTitle: string = `Message to the title-${generateId()}` + const messageToContent: string = `Message to the content-${generateId()}` + + await prepareDocumentStep(page, commentsDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Add comments', async () => { + await documentContentPage.checkDocumentTitle(commentsDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + + await documentContentPage.updateSectionTitle('1', newContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(newContentFirst) + }) + + await test.step('3. Send to Review', async () => { + await documentContentPage.buttonSendForReview.click() + await documentContentPage.fillSelectReviewersForm([documentDetails.owner]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_REVIEW + }) + }) + + await test.step('4. Add comments and Complete Review', async () => { + await documentContentPage.addMessageToTheSectionTitle(newContentFirst.sectionTitle, messageToTitle) + await documentContentPage.addMessageToTheText(newContentFirst.content, messageToContent) + + await documentContentPage.buttonComments.click() + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.checkCommentExist(newContentFirst.sectionTitle, 2) + + await attachScreenshot('TESTS-136_add_comments.png', page) + + await documentContentPage.completeReview() + + await documentContentPage.checkDocumentStatus(DocumentStatus.REVIEWED) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('5. Resolve the first comment', async () => { + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.resolveComments(newContentFirst.sectionTitle, '1') + + await documentCommentsPage.checkCommentExist(newContentFirst.sectionTitle, 1) + + await attachScreenshot('TESTS-136_add_and_done_comments.png', page) + }) + + await test.step('6. Check Edit document flow', async () => { + await documentContentPage.buttonEditDocument.click() + await documentContentPage.buttonDocumentInformation.click() + + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.DRAFT, + version: 'v0.1' + }) + await attachScreenshot('TESTS-136_check_edit_document_flow.png', page) + }) + }) + + test('TESTS-137. Approve document', async ({ page }) => { + await allure.description('Requirement\nUsers need to approve the document') + await allure.tms('TESTS-137', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-137') + const approveDocument: NewDocument = { + template: 'HR (HR)', + title: `Approve document-${generateId()}`, + description: `Approve document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + + await prepareDocumentStep(page, approveDocument) + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Send for Approval', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([documentDetails.owner]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('3. Approve document', async () => { + await documentContentPage.confirmApproval() + }) + + await test.step('4. Check the document and status', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.EFFECTIVE) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.EFFECTIVE, + version: 'v0.1' + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + + await documentContentPage.openApprovals() + const documentApprovalsPage = new DocumentApprovalsPage(page) + await documentApprovalsPage.checkSuccessApproval(documentDetails.owner) + await attachScreenshot('TESTS-137_approve_document.png', page) + }) + }) + + test('TESTS-138. Reject document', async ({ page }) => { + await allure.description('Requirement\nUsers need to reject the document') + await allure.tms('TESTS-138', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-138') + const rejectionReason = `Test rejection-${generateId()}` + const rejectDocument: NewDocument = { + template: 'HR (HR)', + title: `Reject document-${generateId()}`, + description: `Reject document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + + await prepareDocumentStep(page, rejectDocument) + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Send for Approval', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([documentDetails.owner]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('3. Reject document', async () => { + await documentContentPage.confirmRejection(rejectionReason) + }) + + await test.step('4. Check the document and status', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.REJECTED) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.REJECTED + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + await documentContentPage.openApprovals() + const documentApprovalsPage = new DocumentApprovalsPage(page) + await documentApprovalsPage.checkRejectApproval(documentDetails.owner, rejectionReason) + await attachScreenshot('TESTS-138_reject_document.png', page) + }) + }) + + test('TESTS-139. Make Review', async ({ page }) => { + await allure.description('Requirement\nUsers need to make a review for the document') + await allure.tms('TESTS-139', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-139') + const makeReviewDocument: NewDocument = { + template: 'HR (HR)', + title: `Make Review document-${generateId()}`, + description: `Make Review document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const newContentFirst: Content = { + sectionTitle: `Make Review. Overview-${generateId()}`, + content: `Make Review. New content-${generateId()}!!!!` + } + const updateContentFirst: Content = { + sectionTitle: `Make Review Updated. Updated Overview-${generateId()}`, + content: `Make Review Updated. Updated content-${generateId()}!!!!` + } + const newContentSecond: Content = { + sectionTitle: `Make Review. Description-${generateId()}`, + content: `Make Review. New content Description-${generateId()}!!!!` + } + const messageToTitle: string = `Make Review. Message to the first title-${generateId()}` + const messageToSecondTitle: string = `Make Review. Message to the second title-${generateId()}` + const messageToContent: string = `Make Review. Message to the content-${generateId()}` + + await prepareDocumentStep(page, makeReviewDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Add section and content', async () => { + await documentContentPage.checkDocumentTitle(makeReviewDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + + await documentContentPage.updateSectionTitle('1', newContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(newContentFirst) + + await documentContentPage.addNewSection('1', 'below') + await documentContentPage.updateSectionTitle('2', newContentSecond.sectionTitle) + await documentContentPage.addContentToTheSection(newContentSecond) + await documentContentPage.checkContentForTheSection(newContentSecond) + await attachScreenshot('TESTS-139_add_section_and_content.png', page) + }) + + await test.step('3. Send for Review', async () => { + await documentContentPage.buttonSendForReview.click() + await documentContentPage.fillSelectReviewersForm([documentDetails.owner]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_REVIEW + }) + await attachScreenshot('TESTS-139_send_for_review.png', page) + }) + + await test.step('4. Add comments and Complete Review', async () => { + await documentContentPage.addMessageToTheSectionTitle(newContentFirst.sectionTitle, messageToTitle) + await documentContentPage.addMessageToTheText(newContentFirst.content, messageToContent) + await documentContentPage.addMessageToTheSectionTitle(newContentSecond.sectionTitle, messageToSecondTitle) + + await documentContentPage.buttonComments.click() + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.checkCommentExist(newContentFirst.sectionTitle, 2) + await documentCommentsPage.checkCommentExist(newContentSecond.sectionTitle, 1) + + await documentContentPage.completeReview() + + await documentContentPage.checkDocumentStatus(DocumentStatus.REVIEWED) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + await attachScreenshot('TESTS-139_add_comments.png', page) + }) + + await test.step('5. Update Document and fix reviews', async () => { + await documentContentPage.buttonEditDocument.click() + + await documentContentPage.updateSectionTitle('1', updateContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(updateContentFirst) + await documentContentPage.checkContentForTheSection(updateContentFirst) + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.checkCommentExist(updateContentFirst.sectionTitle, 2) + await documentCommentsPage.checkCommentExist(newContentSecond.sectionTitle, 1) + await documentCommentsPage.resolveAllComments() + + await documentCommentsPage.checkCommentNotExist(updateContentFirst.sectionTitle) + await documentCommentsPage.checkCommentNotExist(newContentSecond.sectionTitle) + await attachScreenshot('TESTS-136_fix_reviews.png', page) + }) + + await test.step('6. Check document status and details', async () => { + await documentContentPage.buttonDocumentInformation.click() + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.DRAFT, + version: 'v0.1' + }) + await attachScreenshot('TESTS-139_check_document.png', page) + }) + }) + + test('TESTS-140. Comparing several document versions', async ({ page }) => { + await allure.description('Requirement\nUsers need to compare several document versions') + await allure.tms('TESTS-140', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-140') + const makeReviewDocument: NewDocument = { + template: 'HR (HR)', + title: `Comparing document versions-${generateId()}`, + description: `Comparing document versions description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const newContentFirst: Content = { + sectionTitle: `Comparing versions. Overview-${generateId()}`, + content: `Comparing versions. New content-${generateId()}!!!!` + } + const updateContentFirst: Content = { + sectionTitle: '', + content: `Comparing versions Updated. Updated content-${generateId()}!!!!` + } + const newContentSecond: Content = { + sectionTitle: `Comparing versions. Description-${generateId()}`, + content: `Comparing versions. New content Description-${generateId()}!!!!` + } + + await prepareDocumentStep(page, makeReviewDocument) + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Add section and content', async () => { + await documentContentPage.checkDocumentTitle(makeReviewDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + + await documentContentPage.updateSectionTitle('1', newContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(newContentFirst) + await attachScreenshot('TESTS-140_add_section_and_content.png', page) + }) + + await test.step('3. Send for Review', async () => { + await documentContentPage.buttonSendForReview.click() + await documentContentPage.fillSelectReviewersForm([documentDetails.owner]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_REVIEW + }) + await attachScreenshot('TESTS-140_send_for_review.png', page) + }) + + await test.step('4. Add comments and Complete Review', async () => { + await documentContentPage.completeReview() + await documentContentPage.checkDocumentStatus(DocumentStatus.REVIEWED) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + await attachScreenshot('TESTS-140_add_comments.png', page) + }) + + await test.step('5. Compare the versions with several documents', async () => { + await documentContentPage.buttonEditDocument.click() + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.DRAFT, + version: 'v0.1' + }) + + await documentContentPage.addContentToTheSection({ + sectionTitle: newContentFirst.sectionTitle, + content: updateContentFirst.content + }) + await documentContentPage.checkContentForTheSection({ + sectionTitle: newContentFirst.sectionTitle, + content: updateContentFirst.content + }) + + await documentContentPage.addNewSection('1', 'below') + await documentContentPage.updateSectionTitle('2', newContentSecond.sectionTitle) + await documentContentPage.addContentToTheSection(newContentSecond) + + await documentContentPage.changeCurrentRight(DocumentRights.COMPARING) + await documentContentPage.checkComparingTextAdded('Updated') + await documentContentPage.checkComparingTextDeleted('New') + await documentContentPage.checkComparingTextAdded(newContentSecond.sectionTitle) + await documentContentPage.checkComparingTextAdded(newContentSecond.content) + + await attachScreenshot('TESTS-140_fix_reviews.png', page) + }) + }) + + test('TESTS-141. Send for approval document after resolve comments', async ({ page, browser }) => { + await allure.description( + 'Requirement\nUsers need to make a resolve all comments and done documents for the Effective status' + ) + await allure.tms('TESTS-141', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-141') + const userSecondPage = await getSecondPage(browser) + const completeDocument: NewDocument = { + template: 'HR (HR)', + title: `Complete document-${generateId()}`, + description: `Complete document description-${generateId()}` + } + const reviewer = 'Dirak Kainin' + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const newContentFirst: Content = { + sectionTitle: `Complete document. Overview-${generateId()}`, + content: `Complete document. New content-${generateId()}!!!!` + } + const updateContentFirst: Content = { + sectionTitle: `Complete document Updated. Updated Overview-${generateId()}`, + content: `Complete document Updated. Updated content-${generateId()}!!!!` + } + const newContentSecond: Content = { + sectionTitle: `Complete document. Description-${generateId()}`, + content: `Complete document. New content Description-${generateId()}!!!!` + } + const messageToTitle: string = `Complete document. Message to the first title-${generateId()}` + const messageToContent: string = `Complete document. Message to the content-${generateId()}` + + await prepareDocumentStep(page, completeDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Add section and content', async () => { + await documentContentPage.checkDocumentTitle(completeDocument.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkCurrentRights(DocumentRights.EDITING) + + await documentContentPage.updateSectionTitle('1', newContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(newContentFirst) + + await documentContentPage.addNewSection('1', 'below') + await documentContentPage.updateSectionTitle('2', newContentSecond.sectionTitle) + await documentContentPage.addContentToTheSection(newContentSecond) + await attachScreenshot('TESTS-141_add_section_and_content.png', page) + }) + + await test.step('3. Send for Review', async () => { + await documentContentPage.buttonSendForReview.click() + await documentContentPage.fillSelectReviewersForm([reviewer]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_REVIEW + }) + await attachScreenshot('TESTS-141_send_for_review.png', page) + }) + + await test.step('4. As author add comments to the first section', async () => { + await documentContentPage.addMessageToTheSectionTitle(newContentFirst.sectionTitle, messageToTitle) + await documentContentPage.addMessageToTheText(newContentFirst.content, messageToContent) + + await documentContentPage.buttonComments.click() + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.checkCommentExist(newContentFirst.sectionTitle, 2) + await documentCommentsPage.checkCommentCanBeResolved(newContentFirst.sectionTitle, 1) + await documentCommentsPage.checkCommentCanBeResolved(newContentFirst.sectionTitle, 2) + await attachScreenshot('TESTS-141_author_add_comments.png', page) + }) + + await test.step('5. As reviewer add comments to the second section and Complete Review', async () => { + await (await userSecondPage.goto(`${PlatformURI}/${DocumentURI}`))?.finished() + + const documentsPageSecond = new DocumentsPage(userSecondPage) + await documentsPageSecond.openDocument(completeDocument.title) + + const documentContentPageSecond = new DocumentContentPage(userSecondPage) + await documentContentPageSecond.addMessageToTheSectionTitle(newContentSecond.sectionTitle, messageToTitle) + await documentContentPageSecond.addMessageToTheText(newContentSecond.content, messageToContent) + + await documentContentPageSecond.buttonComments.click() + + const documentCommentsPageSecond = new DocumentCommentsPage(userSecondPage) + await documentCommentsPageSecond.checkCommentExist(newContentSecond.sectionTitle, 2) + await documentCommentsPageSecond.checkCommentCanBeResolved(newContentSecond.sectionTitle, 3) + await documentCommentsPageSecond.checkCommentCanBeResolved(newContentSecond.sectionTitle, 4) + + // // TODO uncomment after fix https://front.hc.engineering/workbench/platform/tracker/EZQMS-552 + // await documentCommentsPageSecond.checkCommentCanNotBeResolved(newContentFirst.sectionTitle, 1) + // await documentCommentsPageSecond.checkCommentCanNotBeResolved(newContentFirst.sectionTitle, 2) + + await documentContentPageSecond.completeReview() + + await documentContentPageSecond.checkDocumentStatus(DocumentStatus.REVIEWED) + await documentContentPageSecond.checkCurrentRights(DocumentRights.VIEWING) + await attachScreenshot('TESTS-141_reviewer_add_comments.png', page) + }) + + await test.step('6. Update Document and fix reviews', async () => { + await documentContentPage.buttonEditDocument.click() + + await documentContentPage.updateSectionTitle('1', updateContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(updateContentFirst) + await documentContentPage.checkContentForTheSection(updateContentFirst) + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.checkCommentExist(updateContentFirst.sectionTitle, 2) + await documentCommentsPage.resolveAllComments() + + await documentCommentsPage.checkCommentNotExist(updateContentFirst.sectionTitle) + await attachScreenshot('TESTS-141_fix_reviews.png', page) + + await documentContentPage.buttonDocumentInformation.click() + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.DRAFT, + version: 'v0.1' + }) + await attachScreenshot('TESTS-141_check_document.png', page) + }) + + await test.step('7. Send for Approval', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([reviewer]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL, + version: 'v0.1' + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('8. Approve document', async () => { + const documentsPageSecond = new DocumentsPage(userSecondPage) + await documentsPageSecond.openDocument(completeDocument.title) + + const documentContentPageSecond = new DocumentContentPage(userSecondPage) + await documentContentPageSecond.confirmApproval() + + await documentContentPageSecond.buttonDocumentInformation.click() + await documentContentPageSecond.checkDocumentStatus(DocumentStatus.EFFECTIVE) + await documentContentPageSecond.checkDocument({ + ...documentDetails, + status: DocumentStatus.EFFECTIVE, + version: 'v0.1' + }) + await documentContentPageSecond.checkCurrentRights(DocumentRights.VIEWING) + + await attachScreenshot('TESTS-141_approve_document.png', page) + }) + + await test.step('9. Check document', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.EFFECTIVE) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.EFFECTIVE, + version: 'v0.1' + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + + await attachScreenshot('TESTS-141_check_document.png', page) + }) + + await test.step('10. Check History tab', async () => { + await documentContentPage.buttonHistoryTab.click() + + const documentHistoryPage = new DocumentHistoryPage(page) + await documentHistoryPage.checkHistoryEventExist('New document creation') + await attachScreenshot('TESTS-141_check_history_tab.png', page) + }) + }) + + test('TESTS-162. Approve document with delayed release', async ({ page }) => { + await allure.description('Requirement\nUsers need to create document with delayed release') + await allure.tms('TESTS-162', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-162') + const approveDelayedDocument: NewDocument = { + template: 'HR (HR)', + title: `Approve document with delayed release-${generateId()}`, + description: `Approve document with delayed release description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + + await prepareDocumentStep(page, approveDelayedDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Set delayed Release', async () => { + await documentContentPage.buttonReleaseTab.click() + + const documentReleasePage = new DocumentReleasePage(page) + await documentReleasePage.setEffectiveDate('in 15 minutes') + }) + + await test.step('3. Send for Approval', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([documentDetails.owner]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('4. Approve document', async () => { + await documentContentPage.confirmApproval() + }) + + await test.step('5. Check the updated document information', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.APPROVED) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.APPROVED + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + + await documentContentPage.openApprovals() + const documentApprovalsPage = new DocumentApprovalsPage(page) + await documentApprovalsPage.checkSuccessApproval(documentDetails.owner) + + await attachScreenshot('TESTS-162_approve_document_delayed_release.png', page) + }) + }) + + test('TESTS-161. Check elements in the popup with the new Comment', async ({ page }) => { + await allure.description('Requirement\nUsers need the popup with the new Comment to work correctly') + await allure.tms('TESTS-161', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-161') + const checkPopupDocument: NewDocument = { + template: 'HR (HR)', + title: `Check comment popup elements document-${generateId()}`, + description: `Check comment popup elements document description-${generateId()}` + } + const author = 'Appleseed John' + const newContentFirst: Content = { + sectionTitle: `Overview-${generateId()}`, + content: `New content-${generateId()}!!!!` + } + const messageToTitle: string = `Make Review. Message to the first title-${generateId()}` + const replyCommentFirst = `Reply to first comment-${generateId(4)}` + + await prepareDocumentStep(page, checkPopupDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Add section and content', async () => { + await documentContentPage.checkDocumentTitle(checkPopupDocument.title) + await documentContentPage.updateSectionTitle('1', newContentFirst.sectionTitle) + await documentContentPage.addContentToTheSection(newContentFirst) + }) + + await test.step('3. Add comment and check popup', async () => { + await documentContentPage.addMessageToTheSectionTitle(newContentFirst.sectionTitle, messageToTitle, false) + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.addReplyInPopupByCommentId(1, replyCommentFirst) + await documentCommentsPage.checkCommentInPopupById( + 1, + newContentFirst.sectionTitle, + author, + messageToTitle, + replyCommentFirst + ) + + await attachScreenshot('TESTS-161_add_comment_and_check_popup.png', page) + await documentContentPage.closeNewMessagePopup() + }) + + await test.step('4. Add comment and check comment in the right panel', async () => { + await documentContentPage.buttonComments.click() + + const documentCommentsPage = new DocumentCommentsPage(page) + await documentCommentsPage.checkCommentInPanelById( + 1, + newContentFirst.sectionTitle, + author, + messageToTitle, + replyCommentFirst + ) + await attachScreenshot('TESTS-161_add_comment_and_check_comment_in_the_right_panel.png', page) + }) + }) + + test('TESTS-155. Change document owner. QARA user changes owner from one user to another', async ({ + page, + browser + }) => { + await allure.description('Requirement\nQARA manager needs to change the document owner') + await allure.tms('TESTS-155', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-155') + const qaraManagerPage = await getQaraManagerPage(browser) + const newDocumentOwner = 'Dirak Kainin' + const changeQaraDocument: NewDocument = { + template: 'HR (HR)', + title: `Change document owner by QARA user Document-${generateId()}`, + description: `Change document owner by QARA user Document description-${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + await prepareDocumentStep(page, changeQaraDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Move to effective status', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([documentDetails.owner]) + + await documentContentPage.confirmApproval() + await documentContentPage.checkDocumentStatus(DocumentStatus.EFFECTIVE) + }) + + const documentContentPageQara = new DocumentContentPage(qaraManagerPage) + await test.step('3. As QARA manager change the document owner', async () => { + await (await qaraManagerPage.goto(`${PlatformURI}/${DocumentURI}`))?.finished() + + const documentsPageQara = new DocumentsPage(qaraManagerPage) + await documentsPageQara.openDocument(changeQaraDocument.title) + + await documentContentPageQara.executeMoreActions('Change document owner') + await documentContentPageQara.fillChangeDocumentOwnerPopupByQaraManager(newDocumentOwner) + }) + + await test.step('4. As QARA manager Check the updated document information', async () => { + await documentContentPageQara.checkDocument({ + ...documentDetails, + owner: newDocumentOwner, + version: 'v0.1', + status: DocumentStatus.EFFECTIVE + }) + await attachScreenshot('TESTS-155_change_document_owner.png', page) + }) + + await test.step('5. As previous document owner check the document', async () => { + await documentContentPage.checkDocument({ + ...documentDetails, + owner: newDocumentOwner, + version: 'v0.1', + status: DocumentStatus.EFFECTIVE + }) + await expect(documentContentPage.buttonDraftNewVersion).toBeVisible({ visible: false }) + await attachScreenshot('TESTS-155_previous_owner.png', page) + }) + + await test.step('6. As new document owner check the document', async () => { + const secondPage = await getSecondPage(browser) + await (await secondPage.goto(`${PlatformURI}/${DocumentURI}`))?.finished() + + const documentsPageSecond = new DocumentsPage(secondPage) + await documentsPageSecond.openDocument(changeQaraDocument.title) + + const documentContentPageSecond = new DocumentContentPage(secondPage) + await expect(documentContentPageSecond.buttonDraftNewVersion).toBeVisible() + await documentContentPageSecond.checkDocument({ + ...documentDetails, + owner: newDocumentOwner, + version: 'v0.1', + status: DocumentStatus.EFFECTIVE + }) + await attachScreenshot('TESTS-155_new_owner.png', page) + }) + }) + + test('TESTS-205. Test Reason & Impact sections', async ({ page, browser }) => { + await allure.description('Requirement\nUsers need to add the Reason and Impact information') + await allure.tms('TESTS-205', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-205') + const reasonAndImpactDocument: NewDocument = { + template: 'HR (HR)', + title: `Test Reason and Impact section-${generateId()}`, + description: `Test Reason and Impact section--${generateId()}` + } + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + await prepareDocumentStep(page, reasonAndImpactDocument) + + const documentContentPage = new DocumentContentPage(page) + await test.step('2. Update Reason and Impact sections', async () => { + await documentContentPage.buttonReasonAndImpactTab.click() + + const documentReasonAndImpactPage = new DocumentReasonAndImpactPage(page) + await documentReasonAndImpactPage.setReasonAndImpactData( + 'Test description', + 'Test reason', + 'Test analysis', + 'TMPL-18 HR' + ) + + await documentContentPage.buttonContentTab.click() + }) + + await test.step('3. Move to effective status', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([documentDetails.owner]) + + await documentContentPage.confirmApproval() + await documentContentPage.checkDocumentStatus(DocumentStatus.EFFECTIVE) + }) + + await test.step('4. Check Reason and Impact sections', async () => { + await documentContentPage.buttonReasonAndImpactTab.click() + + const documentReasonAndImpactPage = new DocumentReasonAndImpactPage(page) + await documentReasonAndImpactPage.checkReasonAndImpactData( + 'Test description', + 'Test reason', + 'Test analysis', + 'TMPL-18 HR' + ) + }) + }) + + test('TESTS-214. Check old existing document content', async ({ page, browser }) => { + await allure.description( + 'Requirement\nAs a user, I want to open my previously created document and see all its content' + ) + await allure.tms('TESTS-214', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-214') + const existDocument: NewDocument = { + template: 'HR (HR)', + title: 'Existing document', + description: 'Existing document description' + } + const documentDetails: DocumentDetails = { + id: 'HR-1', + type: 'HR', + category: 'N/A', + version: 'v0.0', + status: DocumentStatus.IN_REVIEW, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const overviewContent: Content = { + sectionTitle: 'Overview', + content: + 'In this section, we explore [Medical Topic], shedding light on its key aspects, causes, symptoms, and available treatments. Gain insights into the latest advancements in [Medical Field] and discover valuable information for a better understanding of managing and addressing [Medical Condition].' + } + const mainContent: Content = { + sectionTitle: 'Main', + content: + '[Medical Topic] is a prevalent [medical condition/issue] affecting a significant number of individuals worldwide. This condition is characterized by [brief description of symptoms or key features]. It can arise due to [common causes or triggers], leading to [impact on health or daily life].' + } + + await test.step('1. Open the document created sometime ago', async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() + + const documentsPage = new DocumentsPage(page) + await documentsPage.openDocument(existDocument.title) + }) + + await test.step('2. Check the document content', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.checkDocumentTitle(existDocument.title) + await documentContentPage.checkDocument(documentDetails) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW) + await documentContentPage.checkContentForTheSection(overviewContent) + await documentContentPage.checkContentForTheSection(mainContent) + }) + }) +}) diff --git a/qms-tests/sanity/tests/documents/templates.spec.ts b/qms-tests/sanity/tests/documents/templates.spec.ts new file mode 100644 index 0000000000..e75026de36 --- /dev/null +++ b/qms-tests/sanity/tests/documents/templates.spec.ts @@ -0,0 +1,104 @@ +import { test } from '@playwright/test' +import { attachScreenshot, generateId, HomepageURI, PlatformSetting, PlatformURI } from '../utils' +import { LeftSideMenuPage } from '../model/left-side-menu-page' +import { DocumentStatus, NewTemplate } from '../model/types' +import { DocumentContentPage } from '../model/documents/document-content-page' +import { NavigationMenuPage } from '../model/documents/navigation-menu-page' +import { TemplatesPage } from '../model/documents/templates-page' +import { allure } from 'allure-playwright' + +test.use({ + storageState: PlatformSetting +}) + +test.describe('QMS. Templates tests', () => { + test.beforeEach(async ({ page }) => { + await (await page.goto(`${PlatformURI}/${HomepageURI}`))?.finished() + }) + + test('TESTS-129. Create Template', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new template') + await allure.tms('TESTS-129', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-129') + const newTemplate: NewTemplate = { + location: { + space: 'Quality documents', + parent: 'Quality documents' + }, + title: `New Template-${generateId()}`, + description: `New Template description-${generateId()}`, + code: generateId(4), + category: 'Document-Control', + reason: 'Test reason', + reviewers: ['Appleseed John'], + approvers: ['Appleseed John'] + } + + await test.step('1. Create a new template', async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() + + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonTemplates.click() + + const templatesPage = new TemplatesPage(page) + await templatesPage.buttonCreateTemplate.click() + + await templatesPage.createTemplate(newTemplate) + }) + + await test.step('2. Check document information', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.checkDocumentTitle(newTemplate.title) + await documentContentPage.checkDocument({ + type: 'N/A', + category: newTemplate.category ?? '', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + }) + }) + + await attachScreenshot('TESTS-129_create_template.png', page) + }) + + test('TESTS-181. Delete template', async ({ page }) => { + await allure.description('Requirement\nUsers need to delete the template') + await allure.tms('TESTS-181', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-181') + const deleteTemplate: NewTemplate = { + title: `Delete Template-${generateId()}`, + description: `Delete Template description-${generateId()}`, + code: generateId(4), + location: { + space: 'Quality documents', + parent: 'Quality documents' + } + } + + const documentContentPage = new DocumentContentPage(page) + await test.step('1. Create a new template', async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() + + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonTemplates.click() + + const templatesPage = new TemplatesPage(page) + await templatesPage.buttonCreateTemplate.click() + + await templatesPage.createTemplate(deleteTemplate) + await documentContentPage.checkDocumentTitle(deleteTemplate.title) + await documentContentPage.checkDocumentStatus(DocumentStatus.DRAFT) + }) + + await test.step('2. Delete the template', async () => { + await documentContentPage.executeMoreActions('Delete') + await documentContentPage.pressYesForPopup(page) + }) + + await test.step('3. Check that the document status is equal to the deleted', async () => { + await documentContentPage.checkDocumentStatus(DocumentStatus.DELETED) + }) + await attachScreenshot('TESTS-181_delete_template.png', page) + }) +}) diff --git a/qms-tests/sanity/tests/files/cat.jpeg b/qms-tests/sanity/tests/files/cat.jpeg new file mode 100644 index 0000000000..df681d18e1 Binary files /dev/null and b/qms-tests/sanity/tests/files/cat.jpeg differ diff --git a/qms-tests/sanity/tests/login/registration.spec.ts b/qms-tests/sanity/tests/login/registration.spec.ts new file mode 100644 index 0000000000..e21fc6e325 --- /dev/null +++ b/qms-tests/sanity/tests/login/registration.spec.ts @@ -0,0 +1,79 @@ +import { expect, test } from '@playwright/test' +import { attachScreenshot, generateId, PlatformURI, randomString } from '../utils' +import { allure } from 'allure-playwright' +import { UserSignUp } from '../model/types' +import { LoginPage } from '../model/login-page' +import { SignupPage } from '../model/signup-page' +import { SelectWorkspacePage } from '../model/select-workspace-page' +import { LeftSideMenuPage } from '../model/left-side-menu-page' + +test.describe('Registration tests', () => { + test.beforeEach(async ({ page }) => { + await (await page.goto(`${PlatformURI}`))?.finished() + }) + + // Skipped until we fix init workspace for tracex + test.skip('TESTS-143. User Registration', async ({ page }) => { + await allure.description('Requirement\nUsers need to register with the system') + await allure.tms('TESTS-143', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-143') + await allure.description('User Registration') + const workspaceName = `workspace-${generateId(4)}` + + const signUpUserData: UserSignUp = { + firstName: randomString(), + lastName: randomString(), + email: `${randomString()}@gmail.com`, + password: '1234' + } + + await test.step('1. Registration', async () => { + const loginPage = new LoginPage(page) + await loginPage.goto() + await loginPage.buttonSignUp.click() + + const signupPage = new SignupPage(page) + await signupPage.signup(signUpUserData) + + await attachScreenshot('TESTS-143_registration.png', page) + + await signupPage.buttonSignUp.click() + }) + + await test.step('2. Create workspace', async () => { + const selectWorkspacePage = new SelectWorkspacePage(page) + await selectWorkspacePage.createWorkspace(workspaceName) + + await attachScreenshot('TESTS-143_create_workspace.png', page) + + await selectWorkspacePage.buttonCreateWorkspace.click() + + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.waitFor({ state: 'visible' }) + await expect(leftSideMenuPage.buttonDocuments).toBeVisible() + }) + }) + + test('TESTS-144. Negative - Duplicate Email', async ({ page }) => { + await allure.description('Requirement\nThe system should reject the registration with an email that already exists') + await allure.tms('TESTS-144', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-144') + const signUpUserData: UserSignUp = { + firstName: randomString(), + lastName: randomString(), + email: 'user1', + password: '1234' + } + + await test.step('1. Attempt to register a user with an email that already exists', async () => { + const loginPage = new LoginPage(page) + await loginPage.goto() + await loginPage.buttonSignUp.click() + + const signupPage = new SignupPage(page) + await signupPage.signup(signUpUserData) + await signupPage.buttonSignUp.click() + + await expect(signupPage.textError).toHaveText('Account already exists') + }) + await attachScreenshot('TESTS-144_duplicate_email.png', page) + }) +}) diff --git a/qms-tests/sanity/tests/model/calendar-page.ts b/qms-tests/sanity/tests/model/calendar-page.ts new file mode 100644 index 0000000000..c240881a60 --- /dev/null +++ b/qms-tests/sanity/tests/model/calendar-page.ts @@ -0,0 +1,41 @@ +import { Locator, Page } from '@playwright/test' +import { CommonPage } from './common-page' + +export class CalendarPage extends CommonPage { + readonly page: Page + readonly buttonDatePopupToday: Locator + readonly inputPopupDateDay: Locator + readonly inputPopupDateMonth: Locator + readonly inputPopupDateYear: Locator + readonly inputPopupTime: Locator + readonly inputPopupDateSave: Locator + + constructor (page: Page) { + super() + this.page = page + this.buttonDatePopupToday = page.locator('div.popup div.today:not(.wrongMonth)') + this.inputPopupDateDay = page.locator('div[class*="date-popup"] div.datetime-input span.digit:first-child') + this.inputPopupDateMonth = page.locator('div[class*="date-popup"] div.datetime-input span.digit:nth-child(3)') + this.inputPopupDateYear = page.locator('div[class*="date-popup"] div.datetime-input span.digit:nth-child(5)') + this.inputPopupTime = page.locator('div[class*="date-popup"] div.datetime-input span.digit:nth-child(7)') + this.inputPopupDateSave = page.locator('div[class*="date-popup"] div.footer button') + } + + async fillSelectDatePopup (day: string, month: string, year: string, time: string): Promise { + await this.inputPopupDateDay.click() + await this.inputPopupDateDay.pressSequentially(day) + await this.inputPopupDateMonth.click() + await this.inputPopupDateMonth.pressSequentially(month) + await this.inputPopupDateYear.click() + await this.inputPopupDateYear.pressSequentially(year) + + await this.inputPopupTime.click() + await this.inputPopupTime.pressSequentially(time) + + await this.inputPopupDateSave.click() + } + + async fillSelectDateByShortcut (shortcut: string): Promise { + await this.page.locator('div.shift-container div.btn > span', { hasText: shortcut }).click() + } +} diff --git a/qms-tests/sanity/tests/model/common-page.ts b/qms-tests/sanity/tests/model/common-page.ts new file mode 100644 index 0000000000..c780cb1eb6 --- /dev/null +++ b/qms-tests/sanity/tests/model/common-page.ts @@ -0,0 +1,45 @@ +import { expect, Page } from '@playwright/test' + +export class CommonPage { + async selectListItemWithSearch (page: Page, name: string, fullWordSearch: boolean = false): Promise { + if (name !== 'first') { + const searchWord = fullWordSearch ? name : name.split(' ')[0] + await page.locator('div.selectPopup input').fill(searchWord) + } + await page.locator('div.selectPopup div.list-item:first-child').click({ delay: 100 }) + } + + async selectListItem (page: Page, name: string): Promise { + await page.locator('div.selectPopup div.list-item', { hasText: name }).click({ delay: 100 }) + } + + async pressCreateButtonSelectPopup (page: Page): Promise { + await page.locator('div.selectPopup div.header button:last-child').click() + } + + async addNewTagPopup (page: Page, title: string, description: string): Promise { + await page.locator('div.popup form[id="tags:string:AddTag"] input[placeholder$="title"]').fill(title) + await page + .locator('div.popup form[id="tags:string:AddTag"] input[placeholder="Please type description here"]') + .fill(description) + await page.locator('div.popup form[id="tags:string:AddTag"] button[type="submit"]').click() + } + + async selectMenuItem (page: Page, point: string): Promise { + await page.locator('div.selectPopup button.menu-item', { hasText: point }).click() + } + + async pressYesDeletePopup (page: Page): Promise { + await page.locator('div.popup button.dangerous').click() + await expect(page.locator('div.popup button.dangerous')).toBeHidden() + } + + async selectFromDropdown (page: Page, point: string): Promise { + await page.locator('div[class$="opup"] span[class*="label"]', { hasText: point }).click() + } + + async pressYesForPopup (page: Page): Promise { + await expect(page.locator('div.popup button[type="submit"]')).toBeVisible() + await page.locator('div.popup button[type="submit"]').click() + } +} diff --git a/qms-tests/sanity/tests/model/documents/categories-page.ts b/qms-tests/sanity/tests/model/documents/categories-page.ts new file mode 100644 index 0000000000..938e82866c --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/categories-page.ts @@ -0,0 +1,23 @@ +import { type Locator, type Page, expect } from '@playwright/test' +import { CalendarPage } from '../calendar-page' + +export class CategoriesPage extends CalendarPage { + readonly page: Page + readonly buttonCreateCategory: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonCreateCategory = page.locator('div[slot="header-tools"] button[type="submit"]', { hasText: 'Category' }) + } + + async openCategory (categoryTitle: string): Promise { + await this.page.locator(`//span[text()='${categoryTitle}']/../../..//div[contains(@class, "firstCell")]/a`).click() + } + + async checkCategoryNotExist (categoryTitle: string): Promise { + await expect( + this.page.locator(`//span[text()='${categoryTitle}']/../../..//div[contains(@class, "firstCell")]/a`) + ).toBeVisible({ visible: false }) + } +} diff --git a/qms-tests/sanity/tests/model/documents/category-create-popup.ts b/qms-tests/sanity/tests/model/documents/category-create-popup.ts new file mode 100644 index 0000000000..7ab527c793 --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/category-create-popup.ts @@ -0,0 +1,40 @@ +import { type Locator, type Page, expect } from '@playwright/test' +import { CalendarPage } from '../calendar-page' +import { NewCategory } from '../types' +import path from 'path' + +export class CategoryCreatePopup extends CalendarPage { + readonly page: Page + readonly inputNewCategoryTitle: Locator + readonly inputNewCategoryDescription: Locator + readonly inputNewCategoryCode: Locator + readonly inputAttachFile: Locator + readonly buttonNewCategoryCreate: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.inputNewCategoryTitle = page.locator('input[placeholder="Title"]') + this.inputNewCategoryCode = page.locator('input[placeholder="Code"]') + this.inputNewCategoryDescription = page.locator('div.inputMsg div.tiptap') + this.inputAttachFile = page.locator('form[id="documents:string:CreateDocumentCategory"] input[type="file"]#file') + this.buttonNewCategoryCreate = page.locator( + 'form[id="documents:string:CreateDocumentCategory"] button[type="submit"]' + ) + } + + async createCategory (data: NewCategory): Promise { + await this.inputNewCategoryTitle.fill(data.title) + await this.inputNewCategoryCode.fill(data.code) + await this.inputNewCategoryDescription.fill(data.description) + + if (data.attachFileName != null) { + await this.inputAttachFile.setInputFiles(path.join(__dirname, `../../files/${data.attachFileName}`)) + await expect( + this.page.locator('div[class*="attachments"] div.name', { hasText: data.attachFileName }) + ).toBeVisible() + } + + await this.buttonNewCategoryCreate.click() + } +} diff --git a/qms-tests/sanity/tests/model/documents/category-details-page.ts b/qms-tests/sanity/tests/model/documents/category-details-page.ts new file mode 100644 index 0000000000..8098c58ace --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/category-details-page.ts @@ -0,0 +1,59 @@ +import { type Locator, type Page, expect } from '@playwright/test' +import { CalendarPage } from '../calendar-page' +import { NewCategory, UpdateCategory } from '../types' +import path from 'path' + +export class CategoryDetailsPage extends CalendarPage { + readonly page: Page + readonly inputCategoryTitle: Locator + readonly inputCategoryCode: Locator + readonly textDescription: Locator + readonly inputAttachFile: Locator + readonly textAttachFile: Locator + readonly buttonMoreActions: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.inputCategoryTitle = page.locator('input[placeholder="documents:string:DomainTitle"]') + this.inputCategoryCode = page.locator('input[placeholder="Category"]') + this.textDescription = page.locator('div.grid div.tiptap') + this.inputAttachFile = page.locator('div.grid input#file') + this.buttonMoreActions = page.locator('div[class*="title"] > div:last-child > button:first-child') + this.textAttachFile = page.locator('div.attachment-grid-container div[class*="attachment"] div.name') + } + + async checkTitle (categoryTitle: string): Promise { + await expect(this.inputCategoryTitle).toHaveValue(categoryTitle) + } + + async editCategory (data: UpdateCategory): Promise { + await this.textDescription.fill(data.description) + + if (data.attachFileName != null) { + await this.inputAttachFile.setInputFiles(path.join(__dirname, `../../files/${data.attachFileName}`)) + await expect(this.textAttachFile.filter({ hasText: data.attachFileName })).toBeVisible() + } + } + + async checkCategory (data: NewCategory): Promise { + await expect(this.inputCategoryTitle).toHaveValue(data.title) + await expect(this.inputCategoryCode).toHaveValue(data.code) + await expect(this.textDescription).toContainText(data.description) + + if (data.attachFileName != null) { + await expect(this.textAttachFile.filter({ hasText: data.attachFileName })).toBeVisible() + } + } + + async executeMoreAction (action: string): Promise { + await this.buttonMoreActions.click() + await this.selectFromDropdown(this.page, action) + await this.pressYesDeletePopup(this.page) + } + + async checkMoreActionNotExist (action: string): Promise { + await this.buttonMoreActions.click() + await expect(this.page.locator('div.popup button.ap-menuItem', { hasText: action })).toBeVisible({ visible: false }) + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-approvals-page.ts b/qms-tests/sanity/tests/model/documents/document-approvals-page.ts new file mode 100644 index 0000000000..e0c1d8ebfa --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-approvals-page.ts @@ -0,0 +1,26 @@ +import { type Page, expect } from '@playwright/test' +import { DocumentCommonPage } from './document-common-page' + +export class DocumentApprovalsPage extends DocumentCommonPage { + readonly page: Page + + constructor (page: Page) { + super(page) + this.page = page + } + + async checkRejectApproval (approvalName: string, message: string): Promise { + await expect( + this.page + .locator('div.reject-message', { hasText: message }) + .locator('xpath=..') + .locator('div.approver span.ap-label') + ).toHaveText(approvalName) + } + + async checkSuccessApproval (approvalName: string): Promise { + await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=..').locator('span.ap-label')).toHaveText( + approvalName + ) + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-comments-page.ts b/qms-tests/sanity/tests/model/documents/document-comments-page.ts new file mode 100644 index 0000000000..4ac570602f --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-comments-page.ts @@ -0,0 +1,133 @@ +import { type Locator, type Page, expect } from '@playwright/test' +import { DocumentCommonPage } from './document-common-page' + +export class DocumentCommentsPage extends DocumentCommonPage { + readonly page: Page + readonly buttonDocumentTitle: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonDocumentTitle = page.locator('button.version-item span.name') + } + + async checkCommentExist (message: string, count: number = 1): Promise { + await expect(this.page.locator('div[data-float="aside"] div.root span', { hasText: message })).toHaveCount(count) + } + + async resolveComments (message: string, position: string = '1'): Promise { + await this.page + .locator('div[data-float="aside"] div.root span', { hasText: message }) + .locator('xpath=..') + .locator('span:first-child', { hasText: position }) + .hover() + + await this.page + .locator('div[data-float="aside"] div.root span', { hasText: message }) + .locator('xpath=..') + .locator('span:first-child', { hasText: position }) + .locator('xpath=../..') + .locator('div.tools button') + .click() + } + + async resolveAllComments (): Promise { + const buttonsCount: number = await this.page.locator('div[data-float="aside"] div.root div.tools button').count() + for (let i = 0; i < buttonsCount; i++) { + await this.page.locator('div[data-float="aside"] div.root').first().click() + await this.page.locator('div[data-float="aside"] div.root div.tools button').first().click() + } + } + + async checkCommentNotExist (message: string): Promise { + await expect(this.page.locator('div[data-float="aside"] div.root span', { hasText: message })).toHaveCount(0) + } + + async checkCommentCanBeResolved (message: string, position: number): Promise { + await this.page + .locator('div[data-float="aside"] div.root span', { hasText: message }) + .locator('xpath=..') + .locator('span:first-child', { hasText: String(position) }) + .hover() + + await expect( + this.page + .locator('div[data-float="aside"] div.root span', { hasText: message }) + .locator('xpath=..') + .locator('span:first-child', { hasText: String(position) }) + .locator('xpath=../..') + .locator('div.tools button') + ).toBeEnabled() + } + + async checkCommentCanNotBeResolved (message: string, position: number): Promise { + await this.page + .locator('div[data-float="aside"] div.root span', { hasText: message }) + .locator('xpath=..') + .locator('span:first-child', { hasText: String(position) }) + .hover() + + await expect( + this.page + .locator('div[data-float="aside"] div.root span', { hasText: message }) + .locator('xpath=..') + .locator('span:first-child', { hasText: String(position) }) + .locator('xpath=../..') + .locator('div.tools button') + ).not.toBeVisible() + } + + async addReplyInPopupByCommentId (commentId: number, replyText: string): Promise { + const comment = this.page + .locator('div.popup div.root div.header span:first-child', { hasText: String(commentId) }) + .locator('xpath=../../../..') + await comment.locator('div.ref-input div.tiptap').fill(replyText) + await comment.locator('div.ref-input div.buttons-panel > button').click() + } + + async checkCommentInPopupById ( + commentId: number, + header: string, + author: string, + message: string, + reply: string + ): Promise { + const comment = this.page + .locator('div.popup div.root div.header > div > span:first-child', { hasText: String(commentId) }) + .locator('xpath=../../../..') + // check header + await expect(comment.locator('div.header > div > span:last-child')).toHaveText(header) + // can be resolved + await comment.locator('div.header div.tools button').hover() + await expect(comment.locator('div.header div.tools button')).toBeEnabled() + // check author + await expect(comment.locator('div.root div.header > a span[class*="label"]').first()).toHaveText(author) + // check message + await expect(comment.locator('div.activityMessage div.flex-col div.clear-mins > p').first()).toHaveText(message) + // check comment + await expect(comment.locator('div.activityMessage div.flex-col div.clear-mins > p').last()).toHaveText(reply) + } + + async checkCommentInPanelById ( + commentId: number, + header: string, + author: string, + message: string, + reply: string + ): Promise { + const comment = this.page + .locator('div.box div.root div.header > div > span:first-child', { hasText: String(commentId) }) + .locator('xpath=../../../..') + // check header + await expect(comment.locator('div.header > div > span:last-child')).toHaveText(header) + // can be resolved + await comment.locator('div.header > div > span:last-child').hover() + await expect(comment.locator('div.header div.tools button')).toBeEnabled() + // check author + await expect(comment.locator('div.root div.header > a span[class*="label"]').first()).toHaveText(author) + // check message + await expect(comment.locator('div.activityMessage div.flex-col div.clear-mins > p').first()).toHaveText(message) + // check comment + await expect(comment.locator('div.activityMessage div.flex-col div.clear-mins > p').last()).toHaveText(reply) + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-common-page.ts b/qms-tests/sanity/tests/model/documents/document-common-page.ts new file mode 100644 index 0000000000..9717890cff --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-common-page.ts @@ -0,0 +1,28 @@ +import { type Locator, type Page } from '@playwright/test' +import { CalendarPage } from '../calendar-page' + +export class DocumentCommonPage extends CalendarPage { + readonly page: Page + readonly inputMessageText: Locator + readonly buttonMessageSend: Locator + readonly buttonReleaseTab: Locator + readonly buttonContentTab: Locator + readonly buttonReasonAndImpactTab: Locator + readonly buttonHistoryTab: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.inputMessageText = page.locator('div.popup div.tiptap') + this.buttonMessageSend = page.locator('div.popup button.primary') + this.buttonReleaseTab = page.locator('div.tab', { hasText: 'Release' }) + this.buttonContentTab = page.locator('div.tab', { hasText: 'Content' }) + this.buttonReasonAndImpactTab = page.locator('div.tab', { hasText: 'Reason & Impact' }) + this.buttonHistoryTab = page.locator('div.tab', { hasText: 'History' }) + } + + async addMessage (message: string): Promise { + await this.inputMessageText.fill(message) + await this.buttonMessageSend.click() + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-content-page.ts b/qms-tests/sanity/tests/model/documents/document-content-page.ts new file mode 100644 index 0000000000..9091ae6e8f --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-content-page.ts @@ -0,0 +1,306 @@ +import { expect, type Locator, type Page } from '@playwright/test' +import { Content, DocumentDetails, DocumentRights, DocumentStatus } from '../types' +import { DocumentCommonPage } from './document-common-page' +import { iterateLocator, PlatformPassword } from '../../utils' + +export class DocumentContentPage extends DocumentCommonPage { + readonly page: Page + readonly buttonDocumentTitle: Locator + readonly buttonMoreActions: Locator + readonly textDocumentStatus: Locator + readonly textType: Locator + readonly textCategory: Locator + readonly textVersion: Locator + readonly textStatus: Locator + readonly textOwner: Locator + readonly textAuthor: Locator + readonly buttonSelectNewOwner: Locator + readonly buttonSelectNewOwnerChange: Locator + readonly buttonSendForReview: Locator + readonly buttonSendForApproval: Locator + readonly buttonAddMembers: Locator + readonly buttonSelectMemberSubmit: Locator + readonly textSelectReviewersPopup: Locator + readonly textSelectApproversPopup: Locator + readonly buttonCurrentRights: Locator + readonly buttonAddMessageToText: Locator + readonly buttonComments: Locator + readonly textDocumentTitle: Locator + readonly buttonCompleteReview: Locator + readonly inputPassword: Locator + readonly buttonSubmit: Locator + readonly buttonReject: Locator + readonly inputRejectionReason: Locator + readonly buttonApprove: Locator + readonly buttonEditDocument: Locator + readonly buttonDraftNewVersion: Locator + readonly buttonDocumentInformation: Locator + readonly buttonDocument: Locator + readonly buttonDocumentApprovals: Locator + readonly textPageHeader: Locator + readonly buttonSelectNewOwnerChangeByQaraManager: Locator + readonly textId: Locator + readonly sectionsLocatorViewRight: Locator + readonly sectionsLocatorEditRight: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonDocumentTitle = page.locator('button.version-item span.name') + this.buttonMoreActions = page.locator('div.popupPanel-title > [class="no-print"] > button:not([id])') + this.textDocumentStatus = page.locator('button.version-item div.root span.label') + this.textType = page.locator('div.flex:has(div.label:text("Template name")) div.field') + this.textCategory = page.locator('div.flex:has(div.label:text("Category")) div.field') + this.textVersion = page.locator('div.flex:has(div.label:text("Version")) div.field') + this.textStatus = page.locator('div.flex:has(div.label:text("Status")) div.field') + this.textOwner = page.locator('div.flex:has(div.label:text("Owner")) div.field') + this.textAuthor = page.locator('div.flex:has(div.label:text("Author")) div.field') + this.buttonSelectNewOwner = page.locator('div.popup button.small') + this.buttonSelectNewOwnerChange = page.locator('div.popup button.dangerous') + this.buttonSendForReview = page.locator('div.popupPanel-title button[type="button"] > span', { + hasText: 'Send for review' + }) + this.buttonSendForApproval = page.locator('div.popupPanel-title button[type="button"] > span', { + hasText: 'Send for approval' + }) + this.buttonAddMembers = page.locator('div.popup div.addButton') + this.buttonSelectMemberSubmit = page.locator('div.popup div.footer button[type="submit"]') + this.textSelectReviewersPopup = page.locator('div.popup span.label', { hasText: 'Select reviewers' }) + this.textSelectApproversPopup = page.locator('div.popup span.label', { hasText: 'Select approvers' }) + this.buttonCurrentRights = page.locator('div.popupPanel-title button[type="button"] > span[slot="content"]') + this.buttonAddMessageToText = page.locator('div.text-editor-toolbar > button:last-child') + this.buttonComments = page.locator('button[id$="comment"]') + this.textDocumentTitle = page.locator('div.panel div.title') + this.buttonCompleteReview = page.locator('div.popupPanel-title button[type="button"] > span', { + hasText: 'Complete review' + }) + this.inputPassword = page.locator('input[name="documents:string:Password"]') + this.buttonSubmit = page.locator('div.popup button[type="submit"]') + this.buttonReject = page.locator('button[type="button"] > span', { hasText: 'Reject' }) + this.inputRejectionReason = page.locator('div.popup div[id="rejection-reason"] input') + this.buttonApprove = page.locator('button[type="button"] > span', { hasText: 'Approve' }) + this.buttonDocument = page.locator('button[id$="info"]') + this.buttonEditDocument = page.locator('div.popupPanel-title button[type="button"] > span', { + hasText: 'Edit document' + }) + this.buttonDraftNewVersion = page.locator('div.popupPanel-title button[type="button"] > span', { + hasText: 'Draft new version' + }) + this.buttonDocumentInformation = page.locator('button[id$="info"]') + this.buttonDocumentApprovals = page.locator('button[id$="approvals"]') + this.textPageHeader = page.locator('div.hulyNavPanel-header') + this.buttonSelectNewOwnerChangeByQaraManager = page.locator('div.popup button[type="submit"]') + this.textId = page.locator('div.flex:has(div.label:text("ID")) div.field') + this.sectionsLocatorViewRight = page.locator('div.section span.label') + this.sectionsLocatorEditRight = page.locator('div.section span.label input') + } + + async checkDocumentTitle (title: string): Promise { + await expect(this.buttonDocumentTitle).toContainText(title) + } + + async updateSectionTitle (sectionId: string, title: string): Promise { + await this.page + .locator('span.hdr-alignment:not([class*="label"])', { hasText: sectionId }) + .locator('xpath=..') + .locator('span.label input') + .fill(title) + } + + async addContentToTheSection (content: Content): Promise { + const section = await this.getSectionLocator(content.sectionTitle) + await section + .locator('xpath=../../../../../../../..') + .locator('div.content-container div.tiptap') + .clear({ force: true }) + + await section + .locator('xpath=../../../../../../../..') + .locator('div.content-container div.tiptap') + .fill(content.content) + } + + async checkContentForTheSection (content: Content): Promise { + const section = await this.getSectionLocator(content.sectionTitle) + const parentLocator = + (await this.buttonCurrentRights.textContent()) === DocumentRights.EDITING + ? '../../../../../../../..' + : '../../../..' + + await expect(section.locator(`xpath=${parentLocator}`).locator('div.content-container div.tiptap')).toHaveText( + content.content + ) + } + + async executeMoreActions (action: string): Promise { + await this.buttonMoreActions.click() + await this.selectFromDropdown(this.page, action) + } + + async checkDocumentStatus (status: DocumentStatus): Promise { + await expect(this.textDocumentStatus).toHaveText(status) + } + + async checkDocument (data: DocumentDetails): Promise { + if (data.type != null && data.type !== 'N/A') { + await expect(this.textType).toHaveText(data.type) + } + if (data.category != null) { + await expect(this.textCategory).toContainText(data.category) + } + if (data.version != null) { + await expect(this.textVersion).toHaveText(data.version) + } + if (data.status != null) { + await expect(this.textStatus).toHaveText(data.status) + } + if (data.owner != null) { + await expect(this.textOwner).toHaveText(data.owner) + } + if (data.author != null) { + await expect(this.textAuthor).toHaveText(data.author) + } + if (data.id != null) { + await expect(this.textId).toHaveText(data.id) + } + } + + async fillChangeDocumentOwnerPopup (newOwner: string): Promise { + await this.buttonSelectNewOwner.click() + await this.selectListItemWithSearch(this.page, newOwner) + await this.buttonSelectNewOwnerChange.click() + } + + async fillSelectReviewersForm (reviewers: Array): Promise { + await this.buttonAddMembers.click() + for (const reviewer of reviewers) { + await this.selectListItemWithSearch(this.page, reviewer) + } + await this.textSelectReviewersPopup.click({ force: true }) + await this.buttonSelectMemberSubmit.click() + } + + async fillSelectApproversForm (approvers: Array): Promise { + await this.buttonAddMembers.click() + for (const approver of approvers) { + await this.selectListItemWithSearch(this.page, approver) + } + await this.textSelectApproversPopup.click({ force: true }) + await this.buttonSelectMemberSubmit.click() + } + + async checkCurrentRights (right: DocumentRights): Promise { + await expect(this.buttonCurrentRights).toHaveText(right) + } + + async addMessageToTheText (text: string, message: string, closePopup: boolean = true): Promise { + await this.page.getByText(text).click() + await this.page.getByText(text).dblclick() + + await this.buttonAddMessageToText.click() + await this.addMessage(message) + + if (closePopup) { + await this.closeNewMessagePopup() + } + } + + async addMessageToTheSectionTitle (title: string, message: string, closePopup: boolean = true): Promise { + const locator = await this.getSectionLocator(title) + const parentLocator = + (await this.buttonCurrentRights.textContent()) === DocumentRights.EDITING ? '../../../../..' : '../..' + + await locator.locator(`xpath=${parentLocator}`).hover() + await locator.locator(`xpath=${parentLocator}`).locator('div.tools button[type="button"]').click() + await this.addMessage(message) + + if (closePopup) { + await this.closeNewMessagePopup() + } + } + + async closeNewMessagePopup (): Promise { + await this.textPageHeader.press('Escape', { delay: 300 }) + await this.textPageHeader.click({ force: true, delay: 300, position: { x: 1, y: 1 } }) + } + + async completeReview (): Promise { + await this.buttonCompleteReview.click() + await this.inputPassword.fill(PlatformPassword) + await this.buttonSubmit.click() + } + + async confirmRejection (rejectionReason: string): Promise { + await this.buttonReject.click() + await this.inputPassword.fill(PlatformPassword) + await this.inputRejectionReason.fill(rejectionReason) + await this.buttonSubmit.click() + } + + async confirmApproval (): Promise { + await this.buttonApprove.click() + await this.inputPassword.fill(PlatformPassword) + await this.buttonSubmit.click() + } + + async addNewSection (startSectionId: string, direction: 'above' | 'below'): Promise { + await this.page.locator('span.hdr-alignment:not([class*="label"])', { hasText: startSectionId }).hover() + + await this.page + .locator('span.hdr-alignment:not([class*="label"])', { hasText: startSectionId }) + .locator('xpath=../../..') + .locator('div.tools[draggable="true"]') + .click() + + await this.selectFromDropdown(this.page, `Add new section ${direction}`) + } + + async changeCurrentRight (newRight: DocumentRights): Promise { + await this.buttonCurrentRights.click() + await this.selectMenuItem(this.page, newRight) + } + + async checkComparingTextAdded (text: string): Promise { + await expect(this.page.locator('span.text-editor-highlighted-node-add', { hasText: text }).first()).toBeVisible() + } + + async checkComparingTextDeleted (text: string): Promise { + await expect(this.page.locator('span.text-editor-highlighted-node-delete', { hasText: text }).first()).toBeVisible() + } + + async openApprovals (): Promise { + await expect(this.buttonDocumentApprovals).toBeVisible() + await this.buttonDocumentApprovals.click({ position: { x: 1, y: 1 }, force: true }) + } + + async fillChangeDocumentOwnerPopupByQaraManager (newOwner: string): Promise { + await this.buttonSelectNewOwner.click() + await this.selectListItemWithSearch(this.page, newOwner) + await this.buttonSelectNewOwnerChangeByQaraManager.click() + } + + private async getSectionLocator (title: string): Promise { + let result: Locator | null = null + const sectionsLocator = iterateLocator( + (await this.buttonCurrentRights.textContent()) === DocumentRights.EDITING + ? this.sectionsLocatorEditRight + : this.sectionsLocatorViewRight + ) + for await (const locator of sectionsLocator) { + const value = + (await this.buttonCurrentRights.textContent()) === DocumentRights.EDITING + ? await locator.inputValue() + : ((await locator.textContent()) ?? '').trim() + if (value === title) { + result = locator + break + } + } + + if (result == null) { + throw new Error('Section not found') + } + + return result + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-history-page.ts b/qms-tests/sanity/tests/model/documents/document-history-page.ts new file mode 100644 index 0000000000..d4e47be259 --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-history-page.ts @@ -0,0 +1,15 @@ +import { expect, type Page } from '@playwright/test' +import { DocumentCommonPage } from './document-common-page' + +export class DocumentHistoryPage extends DocumentCommonPage { + readonly page: Page + + constructor (page: Page) { + super(page) + this.page = page + } + + async checkHistoryEventExist (eventDescription: string): Promise { + await expect(this.page.locator('div.root div.description', { hasText: eventDescription })).toBeVisible() + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-reason-impact-page.ts b/qms-tests/sanity/tests/model/documents/document-reason-impact-page.ts new file mode 100644 index 0000000000..097ce4bf0f --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-reason-impact-page.ts @@ -0,0 +1,66 @@ +import { expect, type Locator, type Page } from '@playwright/test' +import { DocumentCommonPage } from './document-common-page' + +export class DocumentReasonAndImpactPage extends DocumentCommonPage { + readonly page: Page + readonly buttonReasonAndImpactTabSelected: Locator + readonly textAreaDescription: Locator + readonly textAreaReason: Locator + readonly textAreaImpactAnalysis: Locator + readonly buttonImpactedDocuments: Locator + readonly textDescription: Locator + readonly textReason: Locator + readonly textImpactAnalysis: Locator + readonly textImpactedDocuments: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonReasonAndImpactTabSelected = page.locator('div.tab.selected', { hasText: 'Reason & Impact' }) + this.textAreaDescription = page.locator('div.box textarea.root').first() + this.textAreaReason = page.locator('div.box textarea.root').nth(1) + this.textAreaImpactAnalysis = page.locator('div.box textarea.root').last() + this.buttonImpactedDocuments = page.locator('div.addButton') + this.textDescription = page.locator('//div[contains(@class, "title") and text()="Description"]/..') + this.textReason = page.locator('//div[contains(@class, "title") and text()="Reason"]/..') + this.textImpactAnalysis = page.locator('//div[contains(@class, "title") and text()="Impact analysis"]/..') + this.textImpactedDocuments = page.locator( + '//div[contains(@class, "title") and text()="Impacted documents"]/..//a/span' + ) + } + + async setReasonAndImpactData ( + description: string, + reason: string, + analysis: string, + documentName: string + ): Promise { + await this.textAreaDescription.clear() + await this.textAreaDescription.fill(description) + + await this.textAreaReason.clear() + await this.textAreaReason.fill(reason) + + await this.textAreaImpactAnalysis.focus() + await this.textAreaImpactAnalysis.press('Meta+A') + await this.textAreaImpactAnalysis.press('Backspace') + await this.textAreaImpactAnalysis.fill(analysis) + + await this.buttonImpactedDocuments.click() + await this.selectListItem(this.page, documentName) + + await this.buttonReasonAndImpactTabSelected.click({ force: true }) + } + + async checkReasonAndImpactData ( + description: string, + reason: string, + analysis: string, + documentName: string + ): Promise { + await expect(this.textDescription).toContainText(description) + await expect(this.textReason).toContainText(reason) + await expect(this.textImpactAnalysis).toContainText(analysis) + await expect(this.textImpactedDocuments).toHaveText(documentName) + } +} diff --git a/qms-tests/sanity/tests/model/documents/document-release-page.ts b/qms-tests/sanity/tests/model/documents/document-release-page.ts new file mode 100644 index 0000000000..a5f03dd66a --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/document-release-page.ts @@ -0,0 +1,22 @@ +import { type Locator, type Page } from '@playwright/test' +import { DocumentCommonPage } from './document-common-page' + +export class DocumentReleasePage extends DocumentCommonPage { + readonly page: Page + readonly buttonReleaseTabSelected: Locator + readonly buttonMakeEffectiveOn: Locator + readonly buttonDateTimeButton: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonReleaseTabSelected = page.locator('div.tab.selected', { hasText: 'Release' }) + this.buttonMakeEffectiveOn = page.locator('label[for="documents:string:EffectiveOn"]') + this.buttonDateTimeButton = page.locator('label[for="documents:string:EffectiveOn"] button.datetime-button') + } + + async setEffectiveDate (delayTime: string): Promise { + await this.buttonDateTimeButton.click() + await this.fillSelectDateByShortcut(delayTime) + } +} diff --git a/qms-tests/sanity/tests/model/documents/documents-page.ts b/qms-tests/sanity/tests/model/documents/documents-page.ts new file mode 100644 index 0000000000..d5930d8a40 --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/documents-page.ts @@ -0,0 +1,70 @@ +import { type Locator, type Page } from '@playwright/test' +import { CalendarPage } from '../calendar-page' +import { NewDocument } from '../types' +export class DocumentsPage extends CalendarPage { + readonly page: Page + readonly buttonCreateDocument: Locator + readonly buttonPopupNextStep: Locator + readonly buttonSpaceSelector: Locator + readonly buttonParentSelector: Locator + readonly inputNewDocumentTitle: Locator + readonly inputNewDocumentDescription: Locator + readonly inputNewDocumentCreateDaft: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonCreateDocument = page.locator( + 'div[data-float="navigator"] button[type="submit"]:not([class*="only-icon"])' + ) + this.buttonPopupNextStep = page.locator('div.popup button[type="submit"]') + this.buttonSpaceSelector = page.locator('button[id="space.selector"]') + this.buttonParentSelector = page.locator('div.parentSelector span[class*="label"]') + this.inputNewDocumentTitle = page.locator('div[id="doc-title"] input') + this.inputNewDocumentDescription = page.locator('div[id="doc-description"] input') + this.inputNewDocumentCreateDaft = page.locator('div.footer div.footerButtons button[type="button"]') + } + + async createDocument (data: NewDocument, startSecondStep: boolean = false): Promise { + if (data.location != null) { + await this.buttonSpaceSelector.click() + await this.selectListItemWithSearch(this.page, data.location.space ?? '') + + await this.page.locator('div.parentSelector span[class*="label"]', { hasText: data.location.parent }).click() + } + + // template + if (!startSecondStep) { + await this.buttonPopupNextStep.click() + } + await this.page.locator('div.templates div.tmpHeader', { hasText: data.template }).click() + + // title + await this.buttonPopupNextStep.click() + await this.inputNewDocumentTitle.fill(data.title) + await this.inputNewDocumentDescription.fill(data.description) + + if (data.reason != null) { + await this.page.locator('div.radio label', { hasText: data.reason }).click() + } + + // team + await this.buttonPopupNextStep.click() + + await this.inputNewDocumentCreateDaft.click() + } + + async openDocument (name: string): Promise { + await this.page.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: name }).click() + } + + async executeMoreActionsOnDocument (documentName: string, action: string): Promise { + await this.page.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: documentName }).hover() + await this.page + .locator('button.hulyNavItem-container > span[class*="label"]', { hasText: documentName }) + .locator('xpath=..') + .locator('div[class*="actions"]:not([class*="arrow"])') + .click() + await this.selectFromDropdown(this.page, action) + } +} diff --git a/qms-tests/sanity/tests/model/documents/navigation-menu-page.ts b/qms-tests/sanity/tests/model/documents/navigation-menu-page.ts new file mode 100644 index 0000000000..099be02e6b --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/navigation-menu-page.ts @@ -0,0 +1,13 @@ +import { type Locator, type Page } from '@playwright/test' + +export class NavigationMenuPage { + readonly page: Page + readonly buttonTemplates: Locator + readonly buttonCategories: Locator + + constructor (page: Page) { + this.page = page + this.buttonTemplates = page.locator('a[href$="templates"]', { hasText: 'Templates' }) + this.buttonCategories = page.locator('a[href$="categories"]', { hasText: 'Categories' }) + } +} diff --git a/qms-tests/sanity/tests/model/documents/templates-page.ts b/qms-tests/sanity/tests/model/documents/templates-page.ts new file mode 100644 index 0000000000..4f6f35a7ff --- /dev/null +++ b/qms-tests/sanity/tests/model/documents/templates-page.ts @@ -0,0 +1,84 @@ +import { type Locator, type Page } from '@playwright/test' +import { CalendarPage } from '../calendar-page' +import { NewTemplate } from '../types' + +export class TemplatesPage extends CalendarPage { + readonly page: Page + readonly buttonCreateTemplate: Locator + readonly buttonSpaceSelector: Locator + readonly buttonPopupNextStep: Locator + readonly inputNewTemplateTitle: Locator + readonly inputNewTemplateDescription: Locator + readonly inputNewTemplateCreateDaft: Locator + readonly buttonNewTemplateCategory: Locator + readonly buttonNewTemplateCustomReason: Locator + readonly inputNewTemplateCustomReason: Locator + readonly sectionHighlightedTeam: Locator + readonly inputNewTemplateCode: Locator + + constructor (page: Page) { + super(page) + this.page = page + this.buttonCreateTemplate = page.locator('div[slot="header-tools"] button[type="submit"]') + this.buttonSpaceSelector = page.locator('button[id="space.selector"]') + this.buttonPopupNextStep = page.locator('div.popup button[type="submit"]') + this.inputNewTemplateTitle = page.locator('div[id="doc-title"] input') + this.inputNewTemplateDescription = page.locator('div[id="doc-description"] input') + this.inputNewTemplateCreateDaft = page.locator('div.footer div.footerButtons button[type="button"]') + this.buttonNewTemplateCategory = page.locator('div.popup div.sectionContent button') + this.buttonNewTemplateCustomReason = page.locator('div.radio div.antiRadio:last-child') + this.inputNewTemplateCustomReason = page.locator('div.popup div[id="doc-reason"] input') + this.sectionHighlightedTeam = page.locator('div.popup div.highlighted', { hasText: 'Team' }) + this.inputNewTemplateCode = page.locator('input[placeholder="DOC-1"]') + } + + async createTemplate (data: NewTemplate): Promise { + if (data.location?.space != null) { + await this.buttonSpaceSelector.click() + await this.selectMenuItem(this.page, data.location.space) + } + + if (data.location?.parent != null) { + await this.page.locator('div.parentSelector span[class*="label"]', { hasText: data.location.parent }).click() + } + + // title + await this.buttonPopupNextStep.click() + await this.inputNewTemplateTitle.fill(data.title) + await this.inputNewTemplateDescription.fill(data.description) + + if (data.code != null) { + await this.inputNewTemplateCode.fill(data.code) + } + + if (data.category != null) { + await this.buttonNewTemplateCategory.click() + await this.selectListItemWithSearch(this.page, data.category, true) + } + + if (data.reason != null) { + await this.buttonNewTemplateCustomReason.click() + await this.inputNewTemplateCustomReason.fill(data.reason) + } + + // team + await this.buttonPopupNextStep.click() + if (data.reviewers != null) { + await this.page.locator('div.addButton').nth(1).click() + for (const reviewer of data.reviewers) { + await this.selectListItemWithSearch(this.page, reviewer) + } + await this.sectionHighlightedTeam.click({ force: true }) + } + + if (data.approvers != null) { + await this.page.locator('div.addButton').last().click() + for (const approver of data.approvers) { + await this.selectListItemWithSearch(this.page, approver) + } + await this.sectionHighlightedTeam.click({ force: true }) + } + + await this.inputNewTemplateCreateDaft.click() + } +} diff --git a/qms-tests/sanity/tests/model/left-side-menu-page.ts b/qms-tests/sanity/tests/model/left-side-menu-page.ts new file mode 100644 index 0000000000..6c604fa42a --- /dev/null +++ b/qms-tests/sanity/tests/model/left-side-menu-page.ts @@ -0,0 +1,15 @@ +import { type Locator, type Page } from '@playwright/test' + +export class LeftSideMenuPage { + readonly page: Page + readonly buttonPlanning: Locator + readonly buttonTeam: Locator + readonly buttonDocuments: Locator + + constructor (page: Page) { + this.page = page + this.buttonPlanning = page.locator('button[id$="Planning"]') + this.buttonTeam = page.locator('button[id$="Team"]') + this.buttonDocuments = page.locator('button[id$="documents:string:DocumentApplication"]') + } +} diff --git a/qms-tests/sanity/tests/model/login-page.ts b/qms-tests/sanity/tests/model/login-page.ts new file mode 100644 index 0000000000..8558a0e245 --- /dev/null +++ b/qms-tests/sanity/tests/model/login-page.ts @@ -0,0 +1,29 @@ +import { expect, type Locator, type Page } from '@playwright/test' +import { PlatformURI } from '../utils' + +export class LoginPage { + readonly page: Page + readonly inputEmail: Locator + readonly inputPassword: Locator + readonly buttonLogin: Locator + readonly buttonSignUp: Locator + + constructor (page: Page) { + this.page = page + this.inputEmail = page.locator('input[name=email]') + this.inputPassword = page.locator('input[name=current-password]') + this.buttonLogin = page.locator('button', { hasText: 'Log In' }) + this.buttonSignUp = page.locator('a.title', { hasText: 'Sign Up' }) + } + + async goto (): Promise { + await (await this.page.goto(`${PlatformURI}/login/login`))?.finished() + } + + async login (email: string, password: string): Promise { + await this.inputEmail.fill(email) + await this.inputPassword.fill(password) + expect(await this.buttonLogin.isEnabled()).toBe(true) + await this.buttonLogin.click() + } +} diff --git a/qms-tests/sanity/tests/model/select-workspace-page.ts b/qms-tests/sanity/tests/model/select-workspace-page.ts new file mode 100644 index 0000000000..87819c0e46 --- /dev/null +++ b/qms-tests/sanity/tests/model/select-workspace-page.ts @@ -0,0 +1,25 @@ +import { expect, type Locator, type Page } from '@playwright/test' + +export class SelectWorkspacePage { + readonly page: Page + readonly buttonWorkspace: Locator + readonly buttonCreateWorkspace: Locator + readonly inputWorkspaceName: Locator + + constructor (page: Page) { + this.page = page + this.buttonWorkspace = page.locator('div[class*="workspace"]') + this.buttonCreateWorkspace = page.locator('button > span', { hasText: 'Create workspace' }) + this.inputWorkspaceName = page.locator('div.form input') + } + + async selectWorkspace (workspace: string): Promise { + await this.buttonWorkspace.filter({ hasText: workspace }).click() + } + + async createWorkspace (workspaceName: string): Promise { + await this.buttonCreateWorkspace.waitFor({ state: 'visible' }) + await expect(this.inputWorkspaceName).toBeVisible() + await this.inputWorkspaceName.fill(workspaceName) + } +} diff --git a/qms-tests/sanity/tests/model/signup-page.ts b/qms-tests/sanity/tests/model/signup-page.ts new file mode 100644 index 0000000000..4dddb3b1f4 --- /dev/null +++ b/qms-tests/sanity/tests/model/signup-page.ts @@ -0,0 +1,32 @@ +import { type Locator, type Page } from '@playwright/test' +import { UserSignUp } from './types' + +export class SignupPage { + readonly page: Page + readonly inputFirstName: Locator + readonly inputLastName: Locator + readonly inputEmail: Locator + readonly inputNewPassword: Locator + readonly inputRepeatNewPassword: Locator + readonly buttonSignUp: Locator + readonly textError: Locator + + constructor (page: Page) { + this.page = page + this.inputFirstName = page.locator('input[name="given-name"]') + this.inputLastName = page.locator('input[name="family-name"]') + this.inputEmail = page.locator('input[name="email"]') + this.inputNewPassword = page.locator('input[name="new-password"]').nth(0) + this.inputRepeatNewPassword = page.locator('input[name="new-password"]').nth(1) + this.buttonSignUp = page.locator('div.send button') + this.textError = page.locator('div.ERROR > span') + } + + async signup (userData: UserSignUp): Promise { + await this.inputFirstName.fill(userData.firstName) + await this.inputLastName.fill(userData.lastName) + await this.inputEmail.fill(userData.email) + await this.inputNewPassword.fill(userData.password) + await this.inputRepeatNewPassword.fill(userData.password) + } +} diff --git a/qms-tests/sanity/tests/model/types.ts b/qms-tests/sanity/tests/model/types.ts new file mode 100644 index 0000000000..8e6c698c3c --- /dev/null +++ b/qms-tests/sanity/tests/model/types.ts @@ -0,0 +1,73 @@ +export interface NewDocument { + location?: Location + template: string + title: string + description: string + reason?: string + reviewers?: Array + approvers?: Array +} + +export interface Location { + space?: string + parent?: string +} +export interface Content { + sectionTitle: string + content: string +} + +export interface UserSignUp { + firstName: string + lastName: string + email: string + password: string +} + +export enum DocumentStatus { + DELETED = 'Deleted', + DRAFT = 'Draft', + IN_REVIEW = 'In Review', + IN_APPROVAL = 'In Approval', + REVIEWED = 'Reviewed', + REJECTED = 'Rejected', + APPROVED = 'Approved', + EFFECTIVE = 'Effective' +} + +export interface DocumentDetails { + type: string + category: string + version: string + status: string + owner: string + author: string + id?: string +} + +export enum DocumentRights { + VIEWING = 'Viewing', + EDITING = 'Editing', + COMPARING = 'Comparing' +} + +export interface NewTemplate { + location?: Location + title: string + description: string + code?: string + category?: string + reason?: string + reviewers?: Array + approvers?: Array +} + +export interface NewCategory extends UpdateCategory { + title: string + code: string +} + +export interface UpdateCategory { + description: string + attachFileName?: string +} diff --git a/qms-tests/sanity/tests/playwright.config.ts b/qms-tests/sanity/tests/playwright.config.ts new file mode 100644 index 0000000000..0974cecd9d --- /dev/null +++ b/qms-tests/sanity/tests/playwright.config.ts @@ -0,0 +1,46 @@ +import { devices, PlaywrightTestConfig } from '@playwright/test' +import { config as dotenvConfig } from 'dotenv' +dotenvConfig() + +let maxFailures: number | undefined +if (process.env.TESTS_MAX_FAILURES !== undefined) { + maxFailures = parseInt(process.env.TESTS_MAX_FAILURES) +} + +const config: PlaywrightTestConfig = { + projects: [ + { + name: 'QMS', + use: { + permissions: ['clipboard-read', 'clipboard-write'], + ...devices['Desktop Chrome'], + screenshot: 'only-on-failure', + viewport: { + width: 1440, + height: 900 + }, + trace: { + mode: 'retain-on-failure', + snapshots: true, + screenshots: true, + sources: true + } + } + } + ], + retries: 1, + timeout: 60000, + maxFailures, + reporter: [ + ['list'], + ['html'], + [ + 'allure-playwright', + { + detail: false, + suiteTitle: false + } + ] + ] +} +export default config diff --git a/qms-tests/sanity/tests/utils.ts b/qms-tests/sanity/tests/utils.ts new file mode 100644 index 0000000000..3bcf628cf2 --- /dev/null +++ b/qms-tests/sanity/tests/utils.ts @@ -0,0 +1,75 @@ +import { Browser, Locator, Page } from '@playwright/test' +import { allure } from 'allure-playwright' + +export const PlatformURI = process.env.PLATFORM_URI as string +export const PlatformTransactor = process.env.PLATFORM_TRANSACTOR as string +export const PlatformUser = process.env.PLATFORM_USER as string +export const PlatformToken = process.env.PLATFORM_TOKEN as string +export const PlatformSetting = process.env.SETTING as string +export const PlatformSettingSecond = process.env.SETTING_SECOND as string +export const PlatformSettingQaraManager = process.env.SETTING_QARA_MANAGER as string +export const PlatformPassword = process.env.PLATFORM_PASSWORD as string +export const DefaultWorkspace = 'sanity-ws-qms' +export const DocumentURI = `workbench/${DefaultWorkspace}/documents` +export const HomepageURI = `workbench/${DefaultWorkspace}` + +function toHex (value: number, chars: number): string { + const result = value.toString(16) + if (result.length < chars) { + return '0'.repeat(chars - result.length) + result + } + return result +} + +let counter = 0 +const random = toHex((Math.random() * (1 << 24)) | 0, 6) + toHex((Math.random() * (1 << 16)) | 0, 4) + +function timestamp (): string { + const time = (Date.now() / 1000) | 0 + return toHex(time, 8) +} + +function count (): string { + const val = counter++ & 0xffffff + return toHex(val, 6) +} + +/** + * @public + * @returns + */ +export function generateId (len = 100): string { + const v = timestamp() + random + let s = v.length - len + if (s < 0) { + s = 0 + } + return v.slice(s, v.length) + count() +} + +export async function getSecondPage (browser: Browser): Promise { + const userSecondContext = await browser.newContext({ storageState: PlatformSettingSecond }) + return await userSecondContext.newPage() +} + +export function randomString (): string { + return (Math.random() * 1000000).toString(36).replace('.', '') +} + +export async function attachScreenshot (name: string, page: Page): Promise { + await allure.attachment(name, await page.screenshot(), { + contentType: 'image/png' + }) + await page.screenshot({ path: `screenshots/${name}` }) +} + +export async function getQaraManagerPage (browser: Browser): Promise { + const qaraManagerContext = await browser.newContext({ storageState: PlatformSettingQaraManager }) + return await qaraManagerContext.newPage() +} + +export async function * iterateLocator (locator: Locator): AsyncGenerator { + for (let index = 0; index < (await locator.count()); index++) { + yield locator.nth(index) + } +} diff --git a/qms-tests/sanity/tsconfig.json b/qms-tests/sanity/tsconfig.json new file mode 100644 index 0000000000..cbe74b86ee --- /dev/null +++ b/qms-tests/sanity/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json", + "compilerOptions": { + "rootDir": "./tests", + "outDir": "./lib", + "declarationDir": "./types", + "lib": ["esnext", "dom"] + } +} \ No newline at end of file diff --git a/qms-tests/tool.sh b/qms-tests/tool.sh new file mode 100755 index 0000000000..05ca1b1889 --- /dev/null +++ b/qms-tests/tool.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +export MINIO_ACCESS_KEY=minioadmin +export MINIO_SECRET_KEY=minioadmin +export MINIO_ENDPOINT=localhost:9002 +export MONGO_URL=mongodb://localhost:27018 +export TRANSACTOR_URL=ws://localhost:3334 +export ELASTIC_URL=http://localhost:9201 +export SERVER_SECRET=secret +export STORAGE_CONFIG="minio|localhost:9002?accessKey=minioadmin&secretKey=minioadmin" + +node ../dev/tool/bundle/bundle.js $@ \ No newline at end of file diff --git a/qms-tests/update-snapshot.sh b/qms-tests/update-snapshot.sh new file mode 100755 index 0000000000..e5c755a449 --- /dev/null +++ b/qms-tests/update-snapshot.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +location="${1:-./sanity-ws-qms}" +#backup +./tool.sh backup ${location} sanity-ws-qms \ No newline at end of file diff --git a/rush.json b/rush.json index 4f2f63434d..942bfb7e14 100644 --- a/rush.json +++ b/rush.json @@ -1056,6 +1056,11 @@ "projectFolder": "tests/sanity", "shouldPublish": false }, + { + "packageName": "@hcengineering/qms-tests-sanity", + "projectFolder": "qms-tests/sanity", + "shouldPublish": false + }, { "packageName": "@hcengineering/rekoni", "projectFolder": "packages/rekoni",