mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Migrate QMS tests (#6063)
This commit is contained in:
@@ -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
|
||||
|
||||
Generated
+27
@@ -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
|
||||
|
||||
@@ -206,6 +206,7 @@
|
||||
placeholder={documents.string.DocumentCodePlaceholder}
|
||||
disabled={loadingCodes}
|
||||
bind:value={docObject.code}
|
||||
id="doc-code"
|
||||
kind="large-style"
|
||||
/>
|
||||
{#if codeNotUnique}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
STORAGE_CONFIG="minio|minio?accessKey=minioadmin&secretKey=minioadmin"
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
Executable
+35
@@ -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
|
||||
Executable
+14
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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'
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
playwright-report
|
||||
test-results/*
|
||||
allure-report
|
||||
allure-results
|
||||
screenshots/*
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "default"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
})
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
await this.page.locator('div.shift-container div.btn > span', { hasText: shortcut }).click()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
|
||||
export class CommonPage {
|
||||
async selectListItemWithSearch (page: Page, name: string, fullWordSearch: boolean = false): Promise<void> {
|
||||
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<void> {
|
||||
await page.locator('div.selectPopup div.list-item', { hasText: name }).click({ delay: 100 })
|
||||
}
|
||||
|
||||
async pressCreateButtonSelectPopup (page: Page): Promise<void> {
|
||||
await page.locator('div.selectPopup div.header button:last-child').click()
|
||||
}
|
||||
|
||||
async addNewTagPopup (page: Page, title: string, description: string): Promise<void> {
|
||||
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<void> {
|
||||
await page.locator('div.selectPopup button.menu-item', { hasText: point }).click()
|
||||
}
|
||||
|
||||
async pressYesDeletePopup (page: Page): Promise<void> {
|
||||
await page.locator('div.popup button.dangerous').click()
|
||||
await expect(page.locator('div.popup button.dangerous')).toBeHidden()
|
||||
}
|
||||
|
||||
async selectFromDropdown (page: Page, point: string): Promise<void> {
|
||||
await page.locator('div[class$="opup"] span[class*="label"]', { hasText: point }).click()
|
||||
}
|
||||
|
||||
async pressYesForPopup (page: Page): Promise<void> {
|
||||
await expect(page.locator('div.popup button[type="submit"]')).toBeVisible()
|
||||
await page.locator('div.popup button[type="submit"]').click()
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await this.page.locator(`//span[text()='${categoryTitle}']/../../..//div[contains(@class, "firstCell")]/a`).click()
|
||||
}
|
||||
|
||||
async checkCategoryNotExist (categoryTitle: string): Promise<void> {
|
||||
await expect(
|
||||
this.page.locator(`//span[text()='${categoryTitle}']/../../..//div[contains(@class, "firstCell")]/a`)
|
||||
).toBeVisible({ visible: false })
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await expect(this.inputCategoryTitle).toHaveValue(categoryTitle)
|
||||
}
|
||||
|
||||
async editCategory (data: UpdateCategory): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.buttonMoreActions.click()
|
||||
await this.selectFromDropdown(this.page, action)
|
||||
await this.pressYesDeletePopup(this.page)
|
||||
}
|
||||
|
||||
async checkMoreActionNotExist (action: string): Promise<void> {
|
||||
await this.buttonMoreActions.click()
|
||||
await expect(this.page.locator('div.popup button.ap-menuItem', { hasText: action })).toBeVisible({ visible: false })
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=..').locator('span.ap-label')).toHaveText(
|
||||
approvalName
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await expect(this.page.locator('div[data-float="aside"] div.root span', { hasText: message })).toHaveCount(count)
|
||||
}
|
||||
|
||||
async resolveComments (message: string, position: string = '1'): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await expect(this.page.locator('div[data-float="aside"] div.root span', { hasText: message })).toHaveCount(0)
|
||||
}
|
||||
|
||||
async checkCommentCanBeResolved (message: string, position: number): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await this.inputMessageText.fill(message)
|
||||
await this.buttonMessageSend.click()
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await expect(this.buttonDocumentTitle).toContainText(title)
|
||||
}
|
||||
|
||||
async updateSectionTitle (sectionId: string, title: string): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.buttonMoreActions.click()
|
||||
await this.selectFromDropdown(this.page, action)
|
||||
}
|
||||
|
||||
async checkDocumentStatus (status: DocumentStatus): Promise<void> {
|
||||
await expect(this.textDocumentStatus).toHaveText(status)
|
||||
}
|
||||
|
||||
async checkDocument (data: DocumentDetails): Promise<void> {
|
||||
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<void> {
|
||||
await this.buttonSelectNewOwner.click()
|
||||
await this.selectListItemWithSearch(this.page, newOwner)
|
||||
await this.buttonSelectNewOwnerChange.click()
|
||||
}
|
||||
|
||||
async fillSelectReviewersForm (reviewers: Array<string>): Promise<void> {
|
||||
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<string>): Promise<void> {
|
||||
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<void> {
|
||||
await expect(this.buttonCurrentRights).toHaveText(right)
|
||||
}
|
||||
|
||||
async addMessageToTheText (text: string, message: string, closePopup: boolean = true): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.textPageHeader.press('Escape', { delay: 300 })
|
||||
await this.textPageHeader.click({ force: true, delay: 300, position: { x: 1, y: 1 } })
|
||||
}
|
||||
|
||||
async completeReview (): Promise<void> {
|
||||
await this.buttonCompleteReview.click()
|
||||
await this.inputPassword.fill(PlatformPassword)
|
||||
await this.buttonSubmit.click()
|
||||
}
|
||||
|
||||
async confirmRejection (rejectionReason: string): Promise<void> {
|
||||
await this.buttonReject.click()
|
||||
await this.inputPassword.fill(PlatformPassword)
|
||||
await this.inputRejectionReason.fill(rejectionReason)
|
||||
await this.buttonSubmit.click()
|
||||
}
|
||||
|
||||
async confirmApproval (): Promise<void> {
|
||||
await this.buttonApprove.click()
|
||||
await this.inputPassword.fill(PlatformPassword)
|
||||
await this.buttonSubmit.click()
|
||||
}
|
||||
|
||||
async addNewSection (startSectionId: string, direction: 'above' | 'below'): Promise<void> {
|
||||
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<void> {
|
||||
await this.buttonCurrentRights.click()
|
||||
await this.selectMenuItem(this.page, newRight)
|
||||
}
|
||||
|
||||
async checkComparingTextAdded (text: string): Promise<void> {
|
||||
await expect(this.page.locator('span.text-editor-highlighted-node-add', { hasText: text }).first()).toBeVisible()
|
||||
}
|
||||
|
||||
async checkComparingTextDeleted (text: string): Promise<void> {
|
||||
await expect(this.page.locator('span.text-editor-highlighted-node-delete', { hasText: text }).first()).toBeVisible()
|
||||
}
|
||||
|
||||
async openApprovals (): Promise<void> {
|
||||
await expect(this.buttonDocumentApprovals).toBeVisible()
|
||||
await this.buttonDocumentApprovals.click({ position: { x: 1, y: 1 }, force: true })
|
||||
}
|
||||
|
||||
async fillChangeDocumentOwnerPopupByQaraManager (newOwner: string): Promise<void> {
|
||||
await this.buttonSelectNewOwner.click()
|
||||
await this.selectListItemWithSearch(this.page, newOwner)
|
||||
await this.buttonSelectNewOwnerChangeByQaraManager.click()
|
||||
}
|
||||
|
||||
private async getSectionLocator (title: string): Promise<Locator> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await expect(this.page.locator('div.root div.description', { hasText: eventDescription })).toBeVisible()
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
await expect(this.textDescription).toContainText(description)
|
||||
await expect(this.textReason).toContainText(reason)
|
||||
await expect(this.textImpactAnalysis).toContainText(analysis)
|
||||
await expect(this.textImpactedDocuments).toHaveText(documentName)
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await this.buttonDateTimeButton.click()
|
||||
await this.fillSelectDateByShortcut(delayTime)
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
await this.page.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: name }).click()
|
||||
}
|
||||
|
||||
async executeMoreActionsOnDocument (documentName: string, action: string): Promise<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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' })
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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"]')
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await (await this.page.goto(`${PlatformURI}/login/login`))?.finished()
|
||||
}
|
||||
|
||||
async login (email: string, password: string): Promise<void> {
|
||||
await this.inputEmail.fill(email)
|
||||
await this.inputPassword.fill(password)
|
||||
expect(await this.buttonLogin.isEnabled()).toBe(true)
|
||||
await this.buttonLogin.click()
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
await this.buttonWorkspace.filter({ hasText: workspace }).click()
|
||||
}
|
||||
|
||||
async createWorkspace (workspaceName: string): Promise<void> {
|
||||
await this.buttonCreateWorkspace.waitFor({ state: 'visible' })
|
||||
await expect(this.inputWorkspaceName).toBeVisible()
|
||||
await this.inputWorkspaceName.fill(workspaceName)
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
export interface NewDocument {
|
||||
location?: Location
|
||||
template: string
|
||||
title: string
|
||||
description: string
|
||||
reason?: string
|
||||
reviewers?: Array<string>
|
||||
approvers?: Array<string>
|
||||
}
|
||||
|
||||
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<string>
|
||||
approvers?: Array<string>
|
||||
}
|
||||
|
||||
export interface NewCategory extends UpdateCategory {
|
||||
title: string
|
||||
code: string
|
||||
}
|
||||
|
||||
export interface UpdateCategory {
|
||||
description: string
|
||||
attachFileName?: string
|
||||
}
|
||||
@@ -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
|
||||
@@ -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<Page> {
|
||||
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<void> {
|
||||
await allure.attachment(name, await page.screenshot(), {
|
||||
contentType: 'image/png'
|
||||
})
|
||||
await page.screenshot({ path: `screenshots/${name}` })
|
||||
}
|
||||
|
||||
export async function getQaraManagerPage (browser: Browser): Promise<Page> {
|
||||
const qaraManagerContext = await browser.newContext({ storageState: PlatformSettingQaraManager })
|
||||
return await qaraManagerContext.newPage()
|
||||
}
|
||||
|
||||
export async function * iterateLocator (locator: Locator): AsyncGenerator<Locator> {
|
||||
for (let index = 0; index < (await locator.count()); index++) {
|
||||
yield locator.nth(index)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./tests",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"lib": ["esnext", "dom"]
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -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 $@
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
location="${1:-./sanity-ws-qms}"
|
||||
#backup
|
||||
./tool.sh backup ${location} sanity-ws-qms
|
||||
Reference in New Issue
Block a user