diff --git a/.gitignore b/.gitignore index 1443aac7da70..9e56afe03f66 100644 --- a/.gitignore +++ b/.gitignore @@ -81,5 +81,7 @@ tests/end-to-end/temporary_staged_test /storybook-static /tests/cypress/screenshots /tests/cypress/videos +/tests/e2e/test-failures coverage .nyc_output +data diff --git a/package-lock.json b/package-lock.json index e22f9d29abe6..2dba67f786fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5363,6 +5363,470 @@ } } }, + "@playwright/test": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.1.tgz", + "integrity": "sha512-muk3KZXfA7sXTwUEXfL3m4tusj/MBGYjxIFmooi+F2Pf6hKjjVl4+8niy77Xujk4jpL7hZbbqq9v5bRl2m+C8Q==", + "dev": true, + "requires": { + "@babel/code-frame": "7.16.7", + "@babel/core": "7.16.12", + "@babel/helper-plugin-utils": "7.16.7", + "@babel/plugin-proposal-class-properties": "7.16.7", + "@babel/plugin-proposal-dynamic-import": "7.16.7", + "@babel/plugin-proposal-export-namespace-from": "7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", + "@babel/plugin-proposal-numeric-separator": "7.16.7", + "@babel/plugin-proposal-optional-chaining": "7.16.7", + "@babel/plugin-proposal-private-methods": "7.16.11", + "@babel/plugin-proposal-private-property-in-object": "7.16.7", + "@babel/plugin-syntax-async-generators": "7.8.4", + "@babel/plugin-syntax-json-strings": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "7.8.3", + "@babel/plugin-transform-modules-commonjs": "7.16.8", + "@babel/preset-typescript": "7.16.7", + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "expect": "27.2.5", + "jest-matcher-utils": "27.2.5", + "json5": "2.2.1", + "mime": "3.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "open": "8.4.0", + "pirates": "4.0.4", + "playwright-core": "1.20.1", + "rimraf": "3.0.2", + "source-map-support": "0.4.18", + "stack-utils": "2.0.5", + "yazl": "2.5.1" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/compat-data": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "dev": true + }, + "@babel/core": { + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", + "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.12", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.10", + "@babel/types": "^7.16.8", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-simple-access": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", + "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + } + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", + "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.10", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "browserslist": { + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", + "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001317", + "electron-to-chromium": "^1.4.84", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001323", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001323.tgz", + "integrity": "sha512-e4BF2RlCVELKx8+RmklSEIVub1TWrmdhvA5kEUueummz1XyySW0DVk+3x9HyhU9MuWTa2BhqLgEuEmUwASAdCA==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "electron-to-chromium": { + "version": "1.4.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz", + "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "dev": true + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "pirates": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "dev": true + }, + "playwright-core": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.1.tgz", + "integrity": "sha512-A8ZsZ09gaSbxP0UijoLyzp3LJc0kWMxDooLPi+mm4/5iYnTbd6PF5nKjoFw1a7KwjZIEgdhJduah4BcUIh+IPA==", + "dev": true, + "requires": { + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "jpeg-js": "0.4.3", + "mime": "3.0.0", + "pixelmatch": "5.2.1", + "pngjs": "6.0.0", + "progress": "2.0.3", + "proper-lockfile": "4.1.2", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "socks-proxy-agent": "6.1.1", + "stack-utils": "2.0.5", + "ws": "8.4.2", + "yauzl": "2.10.0", + "yazl": "2.5.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "ws": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", + "dev": true + } + } + }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz", @@ -11126,6 +11590,12 @@ "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", "dev": true }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, "@types/stream-buffers": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.3.tgz", @@ -16330,8 +16800,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true + "dev": true }, "combined-stream": { "version": "1.0.7", @@ -17764,6 +18233,12 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -17907,6 +18382,12 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -19711,6 +20192,101 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, + "expect": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.5.tgz", + "integrity": "sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-regex-util": "^27.0.6" + }, + "dependencies": { + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -24579,6 +25155,69 @@ "iterate-iterator": "^1.0.1" } }, + "jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true + }, "jest-haste-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", @@ -24665,6 +25304,261 @@ } } }, + "jest-matcher-utils": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz", + "integrity": "sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.2.5", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "jest-mock": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", @@ -24919,6 +25813,12 @@ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true }, + "jpeg-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", + "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==", + "dev": true + }, "jquery": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", @@ -30156,6 +31056,23 @@ "node-modules-regexp": "^1.0.0" } }, + "pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "requires": { + "pngjs": "^4.0.1" + }, + "dependencies": { + "pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true + } + } + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -30215,6 +31132,12 @@ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" }, + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true + }, "pnp-webpack-plugin": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", @@ -31893,6 +32816,25 @@ "react-is": "^16.8.1" } }, + "proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + } + } + }, "property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -31945,6 +32887,12 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -34035,6 +34983,12 @@ "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", "integrity": "sha1-vQSN23TefRymkV+qSldXCzVQwtc=" }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, "snake-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", @@ -34178,6 +35132,53 @@ } } }, + "socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "sodium-native": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.2.1.tgz", @@ -34400,6 +35401,23 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, "stackframe": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", @@ -38868,6 +39886,15 @@ "fd-slicer": "~1.1.0" } }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 5422fbf756b2..2742a44998ba 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "scripts": { "prepare": "husky install", "start": "meteor", + "dev": "meteor reset && npm start", "ha": "meteor npm run ha:start", "ha:start": "ts-node .scripts/run-ha.ts main", "ha:add": "ts-node .scripts/run-ha.ts instance", @@ -36,6 +37,9 @@ "testunit-definition": "mocha --config ./.mocharc.definition.js", "testunit-watch": "mocha --watch --config ./.mocharc.js", "test": "npm run testapi && npm run testui", + "test:playwright": "playwright test", + "test:playwright:watch": "PWDEBUG=1 playwright test", + "test:playwright:headed": "playwright test --headed", "translation-diff": "node .scripts/translationDiff.js", "translation-check": "node .scripts/check-i18n.js", "translation-fix-order": "node .scripts/fix-i18n.js", @@ -60,6 +64,7 @@ "@babel/preset-env": "^7.14.7", "@babel/preset-react": "^7.14.5", "@babel/register": "^7.14.5", + "@playwright/test": "^1.20.0", "@rocket.chat/eslint-config": "^0.4.0", "@rocket.chat/livechat": "^1.12.2", "@settlin/spacebars-loader": "^1.0.9", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000000..335158ca4134 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + outputDir: 'tests/e2e/test-failures', + reporter: [['list']], + workers: 3, + use: { + baseURL: process.env.BASE_URL || 'http://localhost:3000', + headless: false, + viewport: { width: 1368, height: 768 }, + ignoreHTTPSErrors: true, + video: 'retain-on-failure', + screenshot: 'only-on-failure', + trace: 'retain-on-failure', + }, + testDir: 'tests/e2e', +}; +export default config; diff --git a/tests/e2e/00-wizard.spec.ts b/tests/e2e/00-wizard.spec.ts new file mode 100644 index 000000000000..ead1a357e0af --- /dev/null +++ b/tests/e2e/00-wizard.spec.ts @@ -0,0 +1,99 @@ +import { test, expect } from '@playwright/test'; + +import SetupWizard from './utils/pageobjects/wizard.page'; +import { adminRegister, VALID_EMAIL } from './utils/mocks/userAndPasswordMock'; +import { LOCALHOST, setupWizardStepRegex } from './utils/mocks/urlMock'; +import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; + +test.describe('[Wizard]', () => { + let setupWizard: SetupWizard; + test.beforeEach(async ({ page }) => { + setupWizard = new SetupWizard(page); + }); + test.describe('[Step 1]', () => { + test.beforeEach(async ({ baseURL }) => { + const baseUrl = baseURL || LOCALHOST; + await setupWizard.goto(baseUrl); + }); + + test('expect required field alert showed when user dont inform data', async () => { + await setupWizard.stepOneFailedBlankFields(); + }); + + test('expect alert showed when email provided is invalid', async () => { + await setupWizard.stepOneFailedWithInvalidEmail(adminRegister); + }); + + test('expect go to Step 2 successfully', async () => { + await setupWizard.stepOneSucess(adminRegister); + + await expect(setupWizard.getPage()).toHaveURL(setupWizardStepRegex._2); + }); + }); + + test.describe('[Step 2]', async () => { + test.beforeEach(async ({ baseURL }) => { + const baseUrl = baseURL || LOCALHOST; + await setupWizard.goto(baseUrl); + await setupWizard.stepOneSucess(adminRegister); + }); + + test('expect required field alert showed when user dont inform data', async () => { + await setupWizard.stepTwoFailedWithBlankFields(); + }); + + test('expect go to Step 3 successfully', async () => { + await setupWizard.stepTwoSucess(); + await expect(setupWizard.getPage()).toHaveURL(setupWizardStepRegex._3); + }); + }); + + test.describe('[Step 3]', async () => { + test.beforeEach(async () => { + await setupWizard.goto(''); + await setupWizard.stepOneSucess(adminRegister); + await setupWizard.stepTwoSucess(); + }); + + test('expect have email field to register the server', async () => { + await expect(setupWizard.registeredServer()).toBeVisible(); + }); + + test('expect start "Register" button disabled', async () => { + await expect(setupWizard.registerButton()).toBeDisabled(); + }); + + test('expect show an error on invalid email', async () => { + await setupWizard.stepThreeFailedWithInvalidField(); + }); + + test('expect enable "Register" button when email is valid and terms checked', async () => { + await setupWizard.registeredServer().type(VALID_EMAIL); + await setupWizard.agreementField().click(); + await expect(setupWizard.registerButton()).toBeEnabled(); + }); + + test('expect have option for standalone server', async () => { + await expect(setupWizard.standaloneServer()).toBeVisible(); + }); + }); + + test.describe('[Final Step]', async () => { + test.beforeEach(async () => { + await setupWizard.goto(''); + await setupWizard.stepOneSucess(adminRegister); + await setupWizard.stepTwoSucess(); + await setupWizard.stepThreeSucess(); + }); + + test('expect confirm the standalone option', async () => { + await expect(setupWizard.goToWorkspace()).toBeVisible(); + await expect(setupWizard.standaloneConfirmText()).toBeVisible(); + }); + + test('expect confirm standalone', async () => { + await setupWizard.goToWorkspace().click(); + await setupWizard.waitForSelector(HOME_SELECTOR); + }); + }); +}); diff --git a/tests/e2e/01-forgot-password.spec.ts b/tests/e2e/01-forgot-password.spec.ts new file mode 100644 index 000000000000..0ba155b302a4 --- /dev/null +++ b/tests/e2e/01-forgot-password.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; + +import LoginPage from './utils/pageobjects/login.page'; +import { LOCALHOST } from './utils/mocks/urlMock'; +import { VALID_EMAIL, INVALID_EMAIL, INVALID_EMAIL_WITHOUT_MAIL_PROVIDER } from './utils/mocks/userAndPasswordMock'; + +test.describe('[Forgot Password]', () => { + let loginPage: LoginPage; + + test.beforeEach(async ({ page, baseURL }) => { + loginPage = new LoginPage(page); + const baseUrl = baseURL || LOCALHOST; + await loginPage.goto(baseUrl); + await loginPage.gotToForgotPassword(); + }); + + test('expect be required', async () => { + loginPage.submit(); + + await expect(loginPage.emailInvalidText()).toBeVisible(); + }); + + test('expect invalid for email without domain', async () => { + await loginPage.emailField().type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); + await loginPage.submit(); + await expect(loginPage.emailInvalidText()).toBeVisible(); + }); + + test('expect be invalid for email with invalid domain', async () => { + await loginPage.emailField().type(INVALID_EMAIL); + await loginPage.submit(); + await expect(loginPage.emailInvalidText()).toBeVisible(); + }); + + test('expect user type a valid email', async () => { + await loginPage.emailField().type(VALID_EMAIL); + await loginPage.submit(); + await expect(loginPage.getToastMessageSuccess()).toBeVisible(); + }); +}); diff --git a/tests/e2e/02-register.spec.ts b/tests/e2e/02-register.spec.ts new file mode 100644 index 000000000000..fb34fd2738b7 --- /dev/null +++ b/tests/e2e/02-register.spec.ts @@ -0,0 +1,28 @@ +import { test } from '@playwright/test'; + +import { registerUser, WRONG_PASSWORD } from './utils/mocks/userAndPasswordMock'; +import LoginPage from './utils/pageobjects/login.page'; +import { LOCALHOST } from './utils/mocks/urlMock'; + +test.describe('[Register]', () => { + let loginPage: LoginPage; + + test.beforeEach(async ({ page, baseURL }) => { + const URL = baseURL || LOCALHOST; + loginPage = new LoginPage(page); + await loginPage.goto(URL); + }); + + test('expect user click in register button without data', async () => { + await loginPage.registerFail(); + }); + + test('expect user click in register button with different password', async () => { + await loginPage.registerFailWithDifentPassword(registerUser, WRONG_PASSWORD); + }); + + test('expect new user is created', async () => { + await loginPage.gotToRegister(); + await loginPage.registerNewUser(registerUser); + }); +}); diff --git a/tests/e2e/03-login.spec.ts b/tests/e2e/03-login.spec.ts new file mode 100644 index 000000000000..b46ef768b948 --- /dev/null +++ b/tests/e2e/03-login.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test'; + +import { validUser } from './utils/mocks/userAndPasswordMock'; +import LoginPage from './utils/pageobjects/login.page'; +import { LOCALHOST } from './utils/mocks/urlMock'; +import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; + +test.describe('[Login]', () => { + let loginPage: LoginPage; + + test.beforeEach(async ({ page, baseURL }) => { + const baseUrl = baseURL || LOCALHOST; + loginPage = new LoginPage(page); + await loginPage.goto(baseUrl); + }); + + test('expect user make login', async () => { + await loginPage.login(validUser); + await loginPage.waitForSelector(HOME_SELECTOR); + }); + + test('expect user write a password incorrectly', async () => { + const invalidUserPassword = { + email: validUser.email, + password: 'any_password1', + }; + await loginPage.login(invalidUserPassword); + await expect(loginPage.getToastError()).toBeVisible(); + }); +}); diff --git a/tests/e2e/04-main-elements-render.spec.ts b/tests/e2e/04-main-elements-render.spec.ts new file mode 100644 index 000000000000..6fc7a4005ed8 --- /dev/null +++ b/tests/e2e/04-main-elements-render.spec.ts @@ -0,0 +1,293 @@ +import { test, expect } from '@playwright/test'; + +import MainContent from './utils/pageobjects/main-content.page'; +import SideNav from './utils/pageobjects/side-nav.page'; +import FlexTab from './utils/pageobjects/flex-tab.page'; +import LoginPage from './utils/pageobjects/login.page'; +import { adminLogin, adminRegister } from './utils/mocks/userAndPasswordMock'; +import { LOCALHOST } from './utils/mocks/urlMock'; + +const username = adminRegister.name; + +test.describe('[Main Elements Render]', function () { + let loginPage: LoginPage; + let mainContent: MainContent; + let sideNav: SideNav; + let flexTab: FlexTab; + + test.beforeAll(async ({ browser, baseURL }) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const URL = baseURL || LOCALHOST; + loginPage = new LoginPage(page); + await loginPage.goto(URL); + + await loginPage.login(adminLogin); + sideNav = new SideNav(page); + mainContent = new MainContent(page); + flexTab = new FlexTab(page); + }); + + test.describe('[Side Nav Bar]', () => { + test.describe('render:', () => { + test('expect show the new channel button', async () => { + await expect(sideNav.newChannelBtnToolbar()).toBeVisible(); + }); + + test('expect show "general" channel', async () => { + await expect(sideNav.general()).toBeVisible(); + }); + }); + + test.describe('spotlight search render:', () => { + test('expect show spotlight search bar', async () => { + await sideNav.spotlightSearchIcon().click(); + await expect(sideNav.spotlightSearch()).toBeVisible(); + }); + + test('expect click the spotlight and show the channel list', async () => { + await sideNav.spotlightSearch().click(); + await expect(sideNav.spotlightSearchPopUp()).toBeVisible(); + }); + + test('expect add text to the spotlight and show the channel list', async () => { + await sideNav.spotlightSearch().type('rocket.cat'); + await expect(sideNav.spotlightSearchPopUp()).toBeVisible(); + await sideNav.getPage().locator('//*[@data-qa="sidebar-search-result"]//*[@data-index="0"]').click(); + }); + }); + }); + test.describe('[User Options]', () => { + test.describe('render:', () => { + test.beforeEach(async () => { + await sideNav.sidebarUserMenu().click(); + }); + + test.afterEach(async () => { + await sideNav.sidebarUserMenu().click(); + }); + + test('expect show online button', async () => { + await expect(sideNav.statusOnline()).toBeVisible(); + }); + + test('expect show away button', async () => { + await expect(sideNav.statusAway()).toBeVisible(); + }); + + test('expect show busy button', async () => { + await expect(sideNav.statusBusy()).toBeVisible(); + }); + + test('expect show offline button', async () => { + await expect(sideNav.statusOffline()).toBeVisible(); + }); + + test('expect show my account button', async () => { + await expect(sideNav.account()).toBeVisible(); + }); + + test('expect show logout button', async () => { + await expect(sideNav.logout()).toBeVisible(); + }); + }); + }); + + test.describe('[Main Content]', () => { + test.describe('render:', () => { + test.beforeAll(async () => { + await sideNav.openChannel('general'); + }); + + test('expect show the title of the channel', async () => { + await expect(mainContent.channelTitle('general')).toBeVisible(); + }); + + test('expect show the empty favorite star (before)', async () => { + await expect(mainContent.emptyFavoriteStar()).toBeVisible(); + }); + + test('expect click the empty star', async () => { + await mainContent.emptyFavoriteStar().click(); + }); + + test('expect show the filled favorite star', async () => { + await expect(mainContent.favoriteStar()).toBeVisible(); + }); + + test('expect click the star', async () => { + await mainContent.favoriteStar().click(); + }); + + test('expect show the empty favorite star (after)', async () => { + await expect(mainContent.emptyFavoriteStar()).toBeVisible(); + }); + + test('expect show the message input bar', async () => { + await expect(mainContent.messageInput()).toBeVisible(); + }); + + test('expect show the message box actions button', async () => { + await expect(mainContent.messageBoxActions()).toBeVisible(); + }); + + // issues with the new message box action button and the no animations on tests + + test('expect show the audio recording button', async () => { + await expect(mainContent.recordBtn()).toBeVisible(); + }); + + test('expect show the emoji button', async () => { + await expect(mainContent.emojiBtn()).toBeVisible(); + }); + + test('expect show the last message', async () => { + await expect(mainContent.lastMessage()).toBeVisible(); + }); + + test('expect be that the last message is from the logged user', async () => { + await expect(mainContent.lastMessageUser()).toContainText(username); + }); + + test('expect not show the Admin tag', async () => { + await expect(mainContent.lastMessageUserTag()).not.toBeVisible(); + }); + }); + }); + + test.describe('[Flextab]', () => { + test.describe('[Render]', () => { + test.beforeAll(async () => { + await sideNav.openChannel('general'); + }); + + test.describe('Room Info Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('info', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('info', false); + }); + + test('expect show the room info button', async () => { + await expect(flexTab.channelTab()).toBeVisible(); + }); + + test('expect show the room info tab content', async () => { + await expect(flexTab.channelSettings()).toBeVisible(); + }); + }); + + test.describe('Search Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('search', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('search', false); + }); + + test('expect show the message search button', async () => { + await expect(flexTab.searchTab()).toBeVisible(); + }); + + test('expect show the message tab content', async () => { + await expect(flexTab.searchTabContent()).toBeVisible(); + }); + }); + + test.describe('Members Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('members', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('members', false); + }); + + test('expect show the members tab button', async () => { + await expect(flexTab.membersTab()).toBeVisible(); + }); + + test('expect show the members content', async () => { + await expect(flexTab.membersTabContent()).toBeVisible(); + }); + }); + + test.describe('Notifications Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('notifications', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('notifications', false); + }); + + test('expect not show the notifications button', async () => { + await expect(flexTab.notificationsTab()).not.toBeVisible(); + }); + + test('expect show the notifications Tab content', async () => { + await expect(flexTab.notificationsSettings()).toBeVisible(); + }); + }); + + test.describe('Files Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('files', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('files', false); + }); + + test('expect show the files Tab content', async () => { + await expect(flexTab.filesTabContent()).toBeVisible(); + }); + }); + + test.describe('Mentions Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('mentions', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('mentions', false); + }); + + test('expect show the mentions Tab content', async () => { + await expect(flexTab.mentionsTabContent()).toBeVisible(); + }); + }); + + test.describe('Starred Messages Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('starred', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('starred', false); + }); + + test('expect show the starred messages Tab content', async () => { + await expect(flexTab.starredTabContent()).toBeVisible(); + }); + }); + + test.describe('Pinned Messages Tab:', () => { + test.beforeAll(async () => { + await flexTab.operateFlexTab('pinned', true); + }); + + test.afterAll(async () => { + await flexTab.operateFlexTab('pinned', false); + }); + + test('expect show the pinned messages Tab content', async () => { + await expect(flexTab.pinnedTabContent()).toBeVisible(); + }); + }); + }); + }); +}); diff --git a/tests/e2e/05-channel-creation.spec.ts b/tests/e2e/05-channel-creation.spec.ts new file mode 100644 index 000000000000..22ec6edff958 --- /dev/null +++ b/tests/e2e/05-channel-creation.spec.ts @@ -0,0 +1,42 @@ +import { test } from '@playwright/test'; +import { v4 } from 'uuid'; + +import ChannelCreation from './utils/pageobjects/ChannelCreation'; +import LoginPage from './utils/pageobjects/login.page'; +import { validUser, ROCKET_CAT } from './utils/mocks/userAndPasswordMock'; +import { LOCALHOST } from './utils/mocks/urlMock'; + +test.describe.parallel('[Channel]', async () => { + let channelCreation: ChannelCreation; + let loginPage: LoginPage; + + const HELLO = 'Hello'; + + test.beforeEach(async ({ page, baseURL }) => { + const baseUrl = baseURL || LOCALHOST; + loginPage = new LoginPage(page); + await loginPage.goto(baseUrl); + await loginPage.login(validUser); + + channelCreation = new ChannelCreation(page); + }); + + test.describe('[Public and private channel creation]', () => { + let channelName: string; + test.beforeEach(async () => { + channelName = v4(); + }); + + test('expect create privateChannel channel', async () => { + await channelCreation.createChannel(channelName, true); + }); + + test('expect create public channel', async () => { + await channelCreation.createChannel(channelName, false); + }); + }); + + test('expect send to channel created', async () => { + await channelCreation.sendMessage(ROCKET_CAT, HELLO); + }); +}); diff --git a/tests/e2e/06-messaging.spec.ts b/tests/e2e/06-messaging.spec.ts new file mode 100644 index 000000000000..d273d0889ffa --- /dev/null +++ b/tests/e2e/06-messaging.spec.ts @@ -0,0 +1,8 @@ +import { expect, test } from '@playwright/test'; + +// TODO: will be implemented soon +test.describe('[Messaging]', () => { + test.beforeAll(async () => { + expect(1).toBe(1); + }); +}); diff --git a/tests/e2e/07-emoji.spec.ts b/tests/e2e/07-emoji.spec.ts new file mode 100644 index 000000000000..9b0e77b6318e --- /dev/null +++ b/tests/e2e/07-emoji.spec.ts @@ -0,0 +1,162 @@ +import { test, expect } from '@playwright/test'; + +import MainContent from './utils/pageobjects/main-content.page'; +import SideNav from './utils/pageobjects/side-nav.page'; +import LoginPage from './utils/pageobjects/login.page'; +import { adminLogin } from './utils/mocks/userAndPasswordMock'; +import { LOCALHOST } from './utils/mocks/urlMock'; + +test.describe('[Emoji]', function () { + let loginPage: LoginPage; + let mainContent: MainContent; + let sideNav: SideNav; + + test.beforeAll(async ({ browser, baseURL }) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const URL = baseURL || LOCALHOST; + loginPage = new LoginPage(page); + await loginPage.goto(URL); + + await loginPage.login(adminLogin); + sideNav = new SideNav(page); + mainContent = new MainContent(page); + + await sideNav.openChannel('general'); + }); + + test.describe('Render:', () => { + test.beforeAll(async () => { + await mainContent.emojiBtn().click(); + }); + + test.afterAll(async () => { + await mainContent.emojiSmile().first().click(); + await mainContent.setTextToInput(''); + }); + + test('expect show the emoji picker menu', async () => { + await expect(mainContent.emojiPickerMainScreen()).toBeVisible(); + }); + + test('expect click the emoji picker people tab', async () => { + await mainContent.emojiPickerPeopleIcon().click(); + }); + + test('expect show the emoji picker people tab', async () => { + await expect(mainContent.emojiPickerPeopleIcon()).toBeVisible(); + }); + + test('expect show the emoji picker nature tab', async () => { + await expect(mainContent.emojiPickerNatureIcon()).toBeVisible(); + }); + + test('expect show the emoji picker food tab', async () => { + await expect(mainContent.emojiPickerFoodIcon()).toBeVisible(); + }); + + test('expect show the emoji picker activity tab', async () => { + await expect(mainContent.emojiPickerActivityIcon()).toBeVisible(); + }); + + test('expect show the emoji picker travel tab', async () => { + await expect(mainContent.emojiPickerTravelIcon()).toBeVisible(); + }); + + test('expect show the emoji picker objects tab', async () => { + await expect(mainContent.emojiPickerObjectsIcon()).toBeVisible(); + }); + + test('expect show the emoji picker symbols tab', async () => { + await expect(mainContent.emojiPickerSymbolsIcon()).toBeVisible(); + }); + + test('expect show the emoji picker flags tab', async () => { + await expect(mainContent.emojiPickerFlagsIcon()).toBeVisible(); + }); + + test('expect show the emoji picker custom tab', async () => { + await expect(mainContent.emojiPickerCustomIcon()).toBeVisible(); + }); + + test('expect show the emoji picker change tone button', async () => { + await expect(mainContent.emojiPickerChangeTone()).toBeVisible(); + }); + + test('expect show the emoji picker search bar', async () => { + await expect(mainContent.emojiPickerFilter()).toBeVisible(); + }); + }); + + test.describe('[Usage]', () => { + test.describe('send emoji via screen:', () => { + test.beforeAll(async () => { + await mainContent.emojiBtn().click(); + await mainContent.emojiPickerPeopleIcon().click(); + }); + + test('expect select a grinning emoji', async () => { + await mainContent.emojiGrinning().first().click(); + }); + + test('expect be that the value on the message input is the same as the emoji clicked', async () => { + await expect(mainContent.messageInput()).toHaveValue(':grinning: '); + }); + + test('expect send the emoji', async () => { + await mainContent.addTextToInput(' '); + await mainContent.sendBtn().click(); + }); + + test('expect be that the value on the message is the same as the emoji clicked', async () => { + await expect(mainContent.lastMessage()).toContainText('😀'); + }); + }); + + test.describe('send emoji via text:', () => { + test('expect add emoji text to the message input', async () => { + await mainContent.addTextToInput(':smiley'); + }); + + test('expect show the emoji popup bar', async () => { + await expect(mainContent.messagePopUp()).toBeVisible(); + }); + + test('expect be that the emoji popup bar title is emoji', async () => { + await expect(mainContent.messagePopUpTitle()).toContainText('Emoji'); + }); + + test('expect show the emoji popup bar items', async () => { + await expect(mainContent.messagePopUpItems()).toBeVisible(); + }); + + test('expect click the first emoji on the popup list', async () => { + await mainContent.messagePopUpFirstItem().click(); + }); + + test('expect be that the value on the message input is the same as the emoji clicked', async () => { + await expect(mainContent.messageInput()).toHaveValue(':smiley: '); + }); + + test('expect send the emoji', async () => { + await mainContent.sendBtn().click(); + }); + + test('expect be that the value on the message is the same as the emoji clicked', async () => { + await expect(mainContent.lastMessage()).toContainText('😃'); + }); + }); + + test.describe("send texts and make sure they're not converted to emojis:", () => { + test('should render numbers', async () => { + await mainContent.sendMessage('0 1 2 3 4 5 6 7 8 9'); + await mainContent.waitForLastMessageEqualsHtml('0 1 2 3 4 5 6 7 8 9'); + }); + + test('should render special characters', async () => { + await mainContent.sendMessage('# * ® © ™'); + await mainContent.waitForLastMessageEqualsHtml('# * ® © ™'); + }); + }); + }); +}); diff --git a/tests/e2e/10-user-preferences.spec.ts b/tests/e2e/10-user-preferences.spec.ts new file mode 100644 index 000000000000..57b08a938c7b --- /dev/null +++ b/tests/e2e/10-user-preferences.spec.ts @@ -0,0 +1,112 @@ +import { test, expect } from '@playwright/test'; + +import MainContent from './utils/pageobjects/main-content.page'; +import SideNav from './utils/pageobjects/side-nav.page'; +import LoginPage from './utils/pageobjects/login.page'; +import FlexTab from './utils/pageobjects/flex-tab.page'; +import PreferencesMainContent from './utils/pageobjects/preferences-main-content.page'; +import { adminLogin, adminRegister } from './utils/mocks/userAndPasswordMock'; +import { LOCALHOST } from './utils/mocks/urlMock'; + +test.describe('[User Preferences]', function () { + test.describe('default', () => { + let flexTab: FlexTab; + let loginPage: LoginPage; + let mainContent: MainContent; + let sideNav: SideNav; + let preferencesMainContent: PreferencesMainContent; + + test.beforeAll(async ({ browser, baseURL }) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const URL = baseURL || LOCALHOST; + loginPage = new LoginPage(page); + await loginPage.goto(URL); + + await loginPage.login(adminLogin); + sideNav = new SideNav(page); + mainContent = new MainContent(page); + preferencesMainContent = new PreferencesMainContent(page); + + await sideNav.sidebarUserMenu().click(); + await sideNav.account().click(); + }); + + test.describe('render:', () => { + test('expect show the preferences link', async () => { + await expect(sideNav.preferences()).toBeVisible(); + }); + + test('expect show the profile link', async () => { + await expect(sideNav.profile()).toBeVisible(); + }); + + test('expect click on the profile link', async () => { + await sideNav.profile().click(); + }); + + test('expect show the username input', async () => { + await expect(preferencesMainContent.userNameTextInput()).toBeVisible(); + }); + + test('expect show the real name input', async () => { + await expect(preferencesMainContent.realNameTextInput()).toBeVisible(); + }); + + test('expect show the email input', async () => { + await expect(preferencesMainContent.emailTextInput()).toBeVisible(); // .scrollIntoView() + }); + + test('expect show the password input', async () => { + await expect(preferencesMainContent.passwordTextInput()).toBeVisible(); // .scrollIntoView() + }); + + test('expect show the submit button', async () => { + await expect(preferencesMainContent.submitBtn()).toBeVisible(); + await expect(preferencesMainContent.submitBtn()).toBeDisabled(); + }); + }); + + test.describe('user info change:', () => { + test('expect click on the profile link', async () => { + await sideNav.profile().click(); + }); + + test('expect change the name field', async () => { + await preferencesMainContent.changeRealName(`Edited${adminRegister.name}${Date.now()}`); + }); + + test('expect change the Username field', async () => { + await preferencesMainContent.changeUsername(`Edited${adminRegister.name}${Date.now()}`); + }); + + test('expect save the settings', async () => { + await preferencesMainContent.saveChanges(); + }); + + test('expect close the preferences menu', async () => { + await sideNav.preferencesClose().click(); + await sideNav.getChannelFromList('general').scrollIntoViewIfNeeded(); + await sideNav.getChannelFromList('general').click(); + }); + + test('expect send a message to be tested', async () => { + await mainContent.sendMessage('HI'); + await mainContent.waitForLastMessageEqualsText('HI'); + }); + + test('expect be that the name on the last message is the edited one', async () => { + await expect(mainContent.lastMessageUser()).toContainText(`Edited${adminRegister.name}`); + }); + + test.skip('expect be that the user name on the members flex tab is the edited one', async () => { + mainContent.lastMessageUser().click(); + await expect(flexTab.memberUserName()).toContainText(`Edited${adminRegister.name}`); + }); + + test.skip('expect that the real name on the members flex tab is the edited one', async () => { + await expect(flexTab.memberRealName()).toContainText(`Edited${adminRegister.name}`); + }); + }); + }); +}); diff --git a/tests/e2e/globalSetup.ts b/tests/e2e/globalSetup.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/e2e/utils/interfaces/Login.ts b/tests/e2e/utils/interfaces/Login.ts new file mode 100644 index 000000000000..fdba700a1851 --- /dev/null +++ b/tests/e2e/utils/interfaces/Login.ts @@ -0,0 +1,10 @@ +export interface ILogin { + email: string; + password: string; +} + +export interface IRegister { + email: string; + password: string; + name: string; +} diff --git a/tests/e2e/utils/mocks/keyboardKeyMock.ts b/tests/e2e/utils/mocks/keyboardKeyMock.ts new file mode 100644 index 000000000000..035611d3ee70 --- /dev/null +++ b/tests/e2e/utils/mocks/keyboardKeyMock.ts @@ -0,0 +1,2 @@ +export const ENTER = 'Enter'; +export const BACKSPACE = 'Backspace'; diff --git a/tests/e2e/utils/mocks/urlMock.ts b/tests/e2e/utils/mocks/urlMock.ts new file mode 100644 index 000000000000..e23a083833d2 --- /dev/null +++ b/tests/e2e/utils/mocks/urlMock.ts @@ -0,0 +1,7 @@ +export const LOCALHOST = 'localhost:3000'; + +export const setupWizardStepRegex = { + _1: /.*\/setup-wizard\/1/, + _2: /.*\/setup-wizard\/2/, + _3: /.*\/setup-wizard\/3/, +}; diff --git a/tests/e2e/utils/mocks/userAndPasswordMock.ts b/tests/e2e/utils/mocks/userAndPasswordMock.ts new file mode 100644 index 000000000000..e477564fc2fb --- /dev/null +++ b/tests/e2e/utils/mocks/userAndPasswordMock.ts @@ -0,0 +1,38 @@ +import { v4 } from 'uuid'; + +import { ILogin, IRegister } from '../interfaces/Login'; + +export const reason = 'rocket.chat.reason'; + +export const adminRegister: IRegister = { + name: 'rocketchat.internal.admin.test', + email: 'rocketchat.internal.admin.test@rocket.chat', + password: 'rocketchat.internal.admin.test', +}; + +export const adminLogin: ILogin = { + email: 'rocketchat.internal.admin.test@rocket.chat', + password: 'rocketchat.internal.admin.test', +}; + +export const registerUser: IRegister = { + email: `any_user@email.com`, + password: 'any_password', + name: `any_name${v4()}`, +}; + +export const validUser: ILogin = { + email: 'any_user@email.com', + password: 'any_password', +}; + +export const incorrectUser: ILogin = { + email: `${v4()}@email.com`, + password: 'any_password', +}; + +export const VALID_EMAIL = 'email@email.com'; +export const INVALID_EMAIL = 'mail@mail'; +export const INVALID_EMAIL_WITHOUT_MAIL_PROVIDER = 'email'; +export const ROCKET_CAT = 'rocket.cat'; +export const WRONG_PASSWORD = 'passwo1'; diff --git a/tests/e2e/utils/mocks/waitSelectorsMock.ts b/tests/e2e/utils/mocks/waitSelectorsMock.ts new file mode 100644 index 000000000000..a7acce309050 --- /dev/null +++ b/tests/e2e/utils/mocks/waitSelectorsMock.ts @@ -0,0 +1,2 @@ +export const HOME_SELECTOR = '//span[@class="rc-header__block"]'; +export const REGISTER_STEP2_BUTTON = '//button[contains(text(), "Use this username")]'; diff --git a/tests/e2e/utils/pageobjects/BasePage.ts b/tests/e2e/utils/pageobjects/BasePage.ts new file mode 100644 index 000000000000..e3a45d9ebe2c --- /dev/null +++ b/tests/e2e/utils/pageobjects/BasePage.ts @@ -0,0 +1,26 @@ +import { Page } from '@playwright/test'; + +class BasePage { + private page: Page; + + constructor(page: Page) { + this.page = page; + } + + public getPage(): Page { + return this.page; + } + + public async goto(path: string): Promise { + await this.page.goto(path); + } + + public async waitForSelector(selector: string): Promise { + await this.page.waitForSelector(selector); + } + + protected async keyboardPress(key: string): Promise { + await this.page.keyboard.press(key); + } +} +export default BasePage; diff --git a/tests/e2e/utils/pageobjects/ChannelCreation.ts b/tests/e2e/utils/pageobjects/ChannelCreation.ts new file mode 100644 index 000000000000..d5f63d48cc9c --- /dev/null +++ b/tests/e2e/utils/pageobjects/ChannelCreation.ts @@ -0,0 +1,75 @@ +import { Locator, expect } from '@playwright/test'; + +import BasePage from './BasePage'; +import { ENTER } from '../mocks/keyboardKeyMock'; + +export default class ChannelCreation extends BasePage { + private buttonCreate(): Locator { + return this.getPage().locator('[data-qa="sidebar-create"]'); + } + + private inputChannelName(): Locator { + return this.getPage().locator('[placeholder="Channel Name"]'); + } + + private inputChannelDescription(): Locator { + return this.getPage().locator('[placeholder="What is this channel about?"]'); + } + + private buttonCreateChannel(): Locator { + return this.getPage().locator('//ul[@class="rc-popover__list"]//li[@class="rcx-option"][1]'); + } + + private channelName(): Locator { + return this.getPage().locator('//header//div//div//div//div[2]'); + } + + private buttonConfirmCreation(): Locator { + return this.getPage().locator('//button[contains(text(), "Create" )]'); + } + + private privateChannel(): Locator { + return this.getPage().locator('//label[contains(text(),"Private")]/../following-sibling::label/i'); + } + + private searchChannel(): Locator { + return this.getPage().locator('[data-qa="sidebar-search"]'); + } + + private searchChannelInput(): Locator { + return this.getPage().locator('[data-qa="sidebar-search-input"]'); + } + + private textArea(): Locator { + return this.getPage().locator('.rc-message-box__textarea'); + } + + private lastMessage(): Locator { + return this.getPage().locator('.message:last-child .body'); + } + + public async createChannel(name: string, isPrivate: boolean): Promise { + await this.buttonCreate().click(); + await this.buttonCreateChannel().click(); + await this.inputChannelName().type(name); + await this.inputChannelDescription().type('any_description'); + if (!isPrivate) { + await this.privateChannel().click(); + } + await this.buttonConfirmCreation().click(); + + await expect(this.channelName()).toHaveText(name); + } + + public async sendMessage(targetUser: string, message: string): Promise { + await this.searchChannel().click(); + await this.searchChannelInput().type(targetUser); + await this.keyboardPress(ENTER); + + await this.textArea().type(message); + await this.keyboardPress(ENTER); + + await expect(this.lastMessage()).toBeVisible(); + await expect(this.lastMessage()).toHaveText(message); + } +} diff --git a/tests/e2e/utils/pageobjects/flex-tab.page.ts b/tests/e2e/utils/pageobjects/flex-tab.page.ts new file mode 100644 index 000000000000..610e323f12ec --- /dev/null +++ b/tests/e2e/utils/pageobjects/flex-tab.page.ts @@ -0,0 +1,466 @@ +import { expect, Locator } from '@playwright/test'; + +import BasePage from './BasePage'; +// import Global from './global'; + +class FlexTab extends BasePage { + public headerMoreActions(): Locator { + return this.getPage().locator('//main/header//*[contains(@class, "rcx-icon--name-kebab")]/..'); + } + + public moreActions(): Locator { + return this.getPage().locator('.rcx-button-group__item:not(.hidden) .rcx-icon--name-kebab'); + } + + public sendBtn(): Locator { + return this.getPage().locator('.rcx-vertical-bar .rc-message-box__icon.js-send'); + } + + public messageInput(): Locator { + return this.getPage().locator('.rcx-vertical-bar .js-input-message'); + } + + public threadTab(): Locator { + return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-thread'); + } + + // Channel Info Tab + public channelTab(): Locator { + return this.getPage().locator('(//main//*[contains(@class, "rcx-icon--name-info-circled")])[1]/..'); + } + + public channelSettings(): Locator { + return this.getPage().locator( + '//aside/h3/div/i[contains(@class,"rcx-icon--name-info-circled") and contains(@class,"rcx-icon--name-info-circled")]', + ); + } + + public channelSettingName(): Locator { + return this.getPage().locator('.channel-settings .rc-user-info__name'); + } + + public archiveBtn(): Locator { + return this.getPage().locator('.clearfix:last-child .icon-pencil'); + } + + public archiveRadio(): Locator { + return this.getPage().locator('.editing'); + } + + public archiveSave(): Locator { + return this.getPage().locator('.save'); + } + + public editNameBtn(): Locator { + return this.getPage().locator('[data-edit="name"]'); + } + + public editTopicBtn(): Locator { + return this.getPage().locator('[data-edit="topic"]'); + } + + public editAnnouncementBtn(): Locator { + return this.getPage().locator('[data-edit="announcement"]'); + } + + public editDescriptionBtn(): Locator { + return this.getPage().locator('[data-edit="description"]'); + } + + public editNotificationBtn(): Locator { + return this.getPage().locator('[data-edit="desktopNotifications"]'); + } + + public editMobilePushBtn(): Locator { + return this.getPage().locator('[data-edit="mobilePushNotifications"]'); + } + + public editEmailNotificationBtn(): Locator { + return this.getPage().locator('[data-edit="emailNotifications"]'); + } + + public editUnreadAlertBtn(): Locator { + return this.getPage().locator('[data-edit="unreadAlert"]'); + } + + public editNameTextInput(): Locator { + return this.getPage().locator('.channel-settings input[name="name"]'); + } + + public editTopicTextInput(): Locator { + return this.getPage().locator('.channel-settings input[name="topic"]'); + } + + public editAnnouncementTextInput(): Locator { + return this.getPage().locator('.channel-settings input[name="announcement"]'); + } + + public editDescriptionTextInput(): Locator { + return this.getPage().locator('.channel-settings input[name="description"]'); + } + + public editNameSave(): Locator { + return this.getPage().locator('.channel-settings .save'); + } + + public deleteBtn(): Locator { + return this.getPage().locator('.channel-settings .js-delete'); + } + + // Members Tab + public membersTab(): Locator { + return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-members'); + } + + public membersTabContent(): Locator { + return this.getPage().locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-members'); + } + + public userSearchBar(): Locator { + return this.getPage().locator('#user-add-search'); + } + + public removeUserBtn(): Locator { + return this.getPage().locator('.remove-user'); + } + + public setOwnerBtn(): Locator { + return this.getPage().locator('.set-owner'); + } + + public setModeratorBtn(): Locator { + return this.getPage().locator('.set-moderator'); + } + + public muteUserBtn(): Locator { + return this.getPage().locator('.mute-user'); + } + + public viewAllBtn(): Locator { + return this.getPage().locator('.button.back'); + } + + public startVideoCall(): Locator { + return this.getPage().locator('.start-video-call'); + } + + public startAudioCall(): Locator { + return this.getPage().locator('.start-audio-call'); + } + + public showAll(): Locator { + return this.getPage().locator('.see-all'); + } + + public membersUserInfo(): Locator { + return this.getPage().locator('.flex-tab-container .info'); + } + + public avatarImage(): Locator { + return this.getPage().locator('aside.rcx-vertical-bar .rcx-avatar'); + } + + public memberUserName(): Locator { + return this.getPage().locator('.info h3'); + } + + public memberRealName(): Locator { + return this.getPage().locator('.info p'); + } + + // Search Tab + public searchTab(): Locator { + return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-magnifier'); + } + + public searchTabContent(): Locator { + return this.getPage().locator('.rocket-search-result'); + } + + public messageSearchBar(): Locator { + return this.getPage().locator('#message-search'); + } + + public searchResult(): Locator { + return this.getPage().locator('.new-day'); + } + + // Notifications Tab + public notificationsTab(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Notifications Preferences")]'); + // return this.getPage().locator('.rcx-option__content:contains("Notifications Preferences")'); + } + + public notificationsSettings(): Locator { + return this.getPage().locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-bell'); + } + + // Files Tab + public filesTab(): Locator { + return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-clip'); + } + + public fileItem(): Locator { + return this.getPage().locator('.uploaded-files-list ul:first-child'); + } + + public filesTabContent(): Locator { + return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-attachment'); + } + + public fileDelete(): Locator { + return this.getPage().locator('.uploaded-files-list ul:first-child .file-delete'); + } + + public fileDownload(): Locator { + return this.getPage().locator('.uploaded-files-list ul:first-child .file-download'); + } + + public fileName(): Locator { + return this.getPage().locator('.uploaded-files-list ul:first-child .room-file-item'); + } + + // Mentions Tab + public mentionsTab(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Mentions")]'); + } + + public mentionsTabContent(): Locator { + return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-at'); + // aside//h3//div//i[contains(@class, "i.rcx-icon--name-at")] + } + + // Starred Tab + public starredTab(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Starred Messages")]'); + } + + public starredTabContent(): Locator { + return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-star'); + } + + // Pinned Tab + public pinnedTab(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Pinned Messages")]'); + } + + public pinnedTabContent(): Locator { + return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-pin'); + } + + public firstSetting(): Locator { + return this.getPage().locator('.clearfix li:nth-child(1) .current-setting'); + } + + public secondSetting(): Locator { + return this.getPage().locator('.clearfix li:nth-child(2) .current-setting'); + } + + public thirdSetting(): Locator { + return this.getPage().locator('.clearfix li:nth-child(3) .current-setting'); + } + + public fourthSetting(): Locator { + return this.getPage().locator('.clearfix li:nth-child(4) .current-setting'); + } + + // admin view flextab items + public usersSendInvitationTab(): Locator { + return this.getPage().locator('.tab-button:not(.hidden) .tab-button-icon--send'); + } + + public usersAddUserTab(): Locator { + return this.getPage().locator('.tab-button:not(.hidden) .tab-button-icon--plus'); + } + + public usersSendInvitationTextArea(): Locator { + return this.getPage().locator('#inviteEmails'); + } + + public usersButtonCancel(): Locator { + return this.getPage().locator('.button.cancel'); + } + + public usersSendInvitationSend(): Locator { + return this.getPage().locator('.button.send'); + } + + public usersButtonSave(): Locator { + return this.getPage().locator('.button.save'); + } + + public usersAddUserName(): Locator { + return this.getPage().locator('#name'); + } + + public usersAddUserUsername(): Locator { + return this.getPage().locator('#username'); + } + + public usersAddUserEmail(): Locator { + return this.getPage().locator('#email'); + } + + public usersAddUserRoleList(): Locator { + return this.getPage().locator('#roleSelect'); + } + + public usersAddUserPassword(): Locator { + return this.getPage().locator('#password'); + } + + public usersAddUserRoleButton(): Locator { + return this.getPage().locator('#addRole'); + } + + public usersAddUserVerifiedCheckbox(): Locator { + return this.getPage().locator('#verified'); + } + + public usersAddUserChangePasswordCheckbox(): Locator { + return this.getPage().locator('#changePassword'); + } + + public usersAddUserDefaultChannelCheckbox(): Locator { + return this.getPage().locator('#joinDefaultChannels'); + } + + public usersAddUserWelcomeEmailCheckbox(): Locator { + return this.getPage().locator('#sendWelcomeEmail'); + } + + public usersAddUserRandomPassword(): Locator { + return this.getPage().locator('#randomPassword'); + } + + public emojiNewAliases(): Locator { + return this.getPage().locator('#aliases'); + } + + public emojiNewImageInput(): Locator { + return this.getPage().locator('#image'); + } + + public usersView(): Locator { + return this.getPage().locator('.rcx-vertical-bar:contains("User Info")'); + } + + public usersActivate(): Locator { + return this.getPage().locator('.rcx-option__content:contains("Activate")'); + } + + public usersDeactivate(): Locator { + return this.getPage().locator('.rcx-option__content:contains("Deactivate")'); + } + + public getUserEl(username: any): Locator { + return this.getPage().locator(`.flex-tab button[title="${username}"] > p`); + } + + public async archiveChannel(): Promise { + await this.archiveBtn().waitFor(); + await this.archiveBtn().click(); + await this.archiveRadio().waitFor(); + await this.archiveRadio().click(); + await this.archiveSave().click(); + } + + public async addPeopleToChannel(user: any): Promise { + await this.userSearchBar().waitFor(); + await this.userSearchBar().type(user); + this.getPage().waitForSelector('.-autocomplete-item'); + this.getPage().click('.-autocomplete-item'); + } + + public async operateFlexTab(desiredTab: string, desiredState: boolean): Promise { + // desiredState true=open false=closed + const locator: { [K: string]: Locator } = { + channelSettings: this.channelSettings(), + messageSearchBar: this.messageSearchBar(), + avatarImage: this.avatarImage(), + notificationsSettings: this.notificationsSettings(), + filesTabContent: this.filesTabContent(), + mentionsTabContent: this.mentionsTabContent(), + starredTabContent: this.starredTabContent(), + pinnedTabContent: this.pinnedTabContent(), + channelTab: this.channelTab(), + searchTab: this.searchTab(), + membersTab: this.membersTab(), + notificationsTab: this.notificationsTab(), + filesTab: this.filesTab(), + mentionsTab: this.mentionsTab(), + starredTab: this.starredTab(), + pinnedTab: this.pinnedTab(), + }; + + const operate = async (tab: string, panel: string, more: boolean): Promise => { + // this[panel].should(!desiredState ? 'be.visible' : 'not.exist'); + if (!desiredState) { + await expect(locator[panel]).toBeVisible(); + } else { + await expect(locator[panel]).not.toBeVisible(); + } + + if (more) { + await this.headerMoreActions().click(); + } + + await locator[tab].click(); + + // The button "more" keeps the focus when popover is closed from a click + // on an item, need to click again to change the status to unselected and + // allow the next click to open the popover again + if (more) { + await this.headerMoreActions().click(); + } + + if (desiredState) { + await expect(locator[panel]).toBeVisible(); + } else { + await expect(locator[panel]).not.toBeVisible(); + } + }; + + const tabs: { [K: string]: Function } = { + info: async (): Promise => { + await operate('channelTab', 'channelSettings', false); + }, + + search: async (): Promise => { + await operate('searchTab', 'messageSearchBar', false); + }, + + members: async (): Promise => { + await operate('membersTab', 'avatarImage', false); + }, + + notifications: async (): Promise => { + await operate('notificationsTab', 'notificationsSettings', true); + }, + + files: async (): Promise => { + await operate('filesTab', 'filesTabContent', false); + }, + + mentions: async (): Promise => { + await operate('mentionsTab', 'mentionsTabContent', true); + }, + + starred: async (): Promise => { + await operate('starredTab', 'starredTabContent', true); + }, + + pinned: async (): Promise => { + await operate('pinnedTab', 'pinnedTabContent', true); + }, + }; + + const callFunctionTabs = async (name: string): Promise => { + return tabs[name](); + }; + + await callFunctionTabs(desiredTab); + } +} + +export default FlexTab; diff --git a/tests/e2e/utils/pageobjects/login.page.ts b/tests/e2e/utils/pageobjects/login.page.ts new file mode 100644 index 000000000000..11f7228e12b0 --- /dev/null +++ b/tests/e2e/utils/pageobjects/login.page.ts @@ -0,0 +1,140 @@ +import { Locator, expect } from '@playwright/test'; + +import { ILogin, IRegister } from '../interfaces/Login'; +import BasePage from './BasePage'; +import { HOME_SELECTOR, REGISTER_STEP2_BUTTON } from '../mocks/waitSelectorsMock'; + +class LoginPage extends BasePage { + private registerButton(): Locator { + return this.getPage().locator('button.register'); + } + + private forgotPasswordButton(): Locator { + return this.getPage().locator('.forgot-password'); + } + + public submitButton(): Locator { + return this.getPage().locator('.login'); + } + + public registerNextButton(): Locator { + return this.getPage().locator('button[data-loading-text=" Please_wait ..."]'); + } + + public registerMessage(): Locator { + return this.getPage().locator('//form[@id["login-card"]]//header//p'); + } + + public emailOrUsernameField(): Locator { + return this.getPage().locator('[name=emailOrUsername]'); + } + + public nameField(): Locator { + return this.getPage().locator('[name=name]'); + } + + public emailField(): Locator { + return this.getPage().locator('[name=email]'); + } + + public passwordField(): Locator { + return this.getPage().locator('[name=pass]'); + } + + public userNameField(): Locator { + return this.getPage().locator('[name=username]'); + } + + public confirmPasswordField(): Locator { + return this.getPage().locator('[name=confirm-pass]'); + } + + public getToastError(): Locator { + return this.getPage().locator('.toast'); + } + + public getToastMessageSuccess(): Locator { + return this.getPage().locator('.toast-message'); + } + + public emailOrUsernameInvalidText(): Locator { + return this.getPage().locator('[name=emailOrUsername]~.input-error'); + } + + public nameInvalidText(): Locator { + return this.getPage().locator('[name=name]~.input-error'); + } + + public emailInvalidText(): Locator { + return this.getPage().locator('[name=email]~.input-error'); + } + + public passwordInvalidText(): Locator { + return this.getPage().locator('[name=pass]~.input-error'); + } + + public confirmPasswordInvalidText(): Locator { + return this.getPage().locator('[name=confirm-pass]~.input-error'); + } + + public getHomeMessage(): Locator { + return this.getPage().locator('//span[@class="rc-header__block"]'); + } + + public async open(path: string): Promise { + await super.goto(path); + } + + public async gotToRegister(): Promise { + await this.registerButton().click(); + } + + public async gotToForgotPassword(): Promise { + await this.forgotPasswordButton().click(); + } + + public async registerNewUser({ name, email, password }: IRegister): Promise { + await this.nameField().type(name); + await this.emailField().type(email); + await this.passwordField().type(password); + await this.confirmPasswordField().type(password); + await this.submit(); + + await this.waitForSelector(REGISTER_STEP2_BUTTON); + await this.registerNextButton().click(); + await this.waitForSelector(HOME_SELECTOR); + } + + public async login({ email, password }: ILogin): Promise { + await this.emailOrUsernameField().type(email); + await this.passwordField().type(password); + await this.submitButton().click(); + } + + public async submit(): Promise { + await this.submitButton().click(); + } + + public async registerFail(): Promise { + await this.gotToRegister(); + await this.submit(); + + await expect(this.nameInvalidText()).toBeVisible(); + await expect(this.emailInvalidText()).toBeVisible(); + await expect(this.passwordInvalidText()).toBeVisible(); + } + + public async registerFailWithDifentPassword({ name, email, password }: IRegister, invalidPassword: string): Promise { + await this.gotToRegister(); + await this.passwordField().type(password); + await this.emailField().type(email); + await this.nameField().type(name); + await this.confirmPasswordField().type(invalidPassword); + + await this.submit(); + await expect(this.confirmPasswordInvalidText()).toBeVisible(); + await expect(this.confirmPasswordInvalidText()).toHaveText('The password confirmation does not match password'); + } +} + +export default LoginPage; diff --git a/tests/e2e/utils/pageobjects/main-content.page.ts b/tests/e2e/utils/pageobjects/main-content.page.ts new file mode 100644 index 000000000000..8891b9f157aa --- /dev/null +++ b/tests/e2e/utils/pageobjects/main-content.page.ts @@ -0,0 +1,269 @@ +import { expect, Locator } from '@playwright/test'; + +import BasePage from './BasePage'; + +class MainContent extends BasePage { + public mainContent(): Locator { + return this.getPage().locator('.main-content'); + } + + // Main Content Header (Channel Title Area) + public emptyFavoriteStar(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-room-header")]//*[contains(@class, "rcx-icon--name-star")]'); + } + + public favoriteStar(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-room-header")]//*[contains(@class, "rcx-icon--name-star-filled")]'); + } + + public channelTitle(title: string): Locator { + return this.getPage().locator('.rcx-room-header', { hasText: title }); + } + + // Main Content Footer (Message Input Area) + public messageInput(): Locator { + return this.getPage().locator('.js-input-message'); + } + + public sendBtn(): Locator { + return this.getPage().locator('.rc-message-box__icon.js-send'); + } + + public messageBoxActions(): Locator { + return this.getPage().locator('(//*[contains(@class, "rc-message-box__icon")])[1]'); + } + + public recordBtn(): Locator { + return this.getPage().locator('.js-audio-message-record'); + } + + public emojiBtn(): Locator { + return this.getPage().locator('.rc-message-box__icon.emoji-picker-icon'); + } + + public messagePopUp(): Locator { + return this.getPage().locator('.message-popup'); + } + + public messagePopUpTitle(): Locator { + return this.getPage().locator('.message-popup-title'); + } + + public messagePopUpItems(): Locator { + return this.getPage().locator('.message-popup-items'); + } + + public messagePopUpFirstItem(): Locator { + return this.getPage().locator('.popup-item.selected'); + } + + public mentionAllPopUp(): Locator { + return this.getPage().locator('.popup-item[data-id="all"]'); + } + + public joinChannelBtn(): Locator { + return this.getPage().locator('.button.join'); + } + + // Messages + public lastMessageUser(): Locator { + return this.getPage().locator('(//*[contains(@class, "message") and contains(@class, "user-card-message")])[last()]'); + } + + public lastMessage(): Locator { + return this.getPage().locator('.message:last-child'); + } + + public lastMessageDesc(): Locator { + return this.getPage().locator('.message:last-child .body .attachment-description'); + } + + public lastMessageRoleAdded(): Locator { + return this.getPage().locator('.message:last-child.subscription-role-added .body'); + } + + public beforeLastMessage(): Locator { + return this.getPage().locator('.message:nth-last-child(2) .body'); + } + + public lastMessageUserTag(): Locator { + return this.getPage().locator('.message:last-child .role-tag'); + } + + public lastMessageImg(): Locator { + return this.getPage().locator('.message:last-child .attachment-image img'); + } + + public lastMessageTextAttachment(): Locator { + return this.getPage().locator('.message:last-child .attachment-text'); + } + + public beforeLastMessageQuote(): Locator { + return this.getPage().locator('.message:nth-last-child(2)'); + } + + public lastMessageQuote(): Locator { + return this.getPage().locator('.message:last-child'); + } + + public messageOptionsBtn(): Locator { + return this.getPage().locator('.message:last-child .message-actions__menu'); + } + + public messageOptionsBtns(): Locator { + return this.getPage().locator('.message:last-child .message-actions'); + } + + public messageActionMenu(): Locator { + return this.getPage().locator('.rc-popover .rc-popover__content'); + } + + public messageReply(): Locator { + return this.getPage().locator('.message:last-child .message-actions__button[data-message-action="reply-in-thread"]'); + } + + public messageEdit(): Locator { + return this.getPage().locator('[data-id="edit-message"][data-type="message-action"]'); + } + + public messageDelete(): Locator { + return this.getPage().locator('[data-id="delete-message"][data-type="message-action"]'); + } + + public messagePermalink(): Locator { + return this.getPage().locator('[data-id="permalink"][data-type="message-action"]'); + } + + public messageCopy(): Locator { + return this.getPage().locator('[data-id="copy"][data-type="message-action"]'); + } + + public messageQuote(): Locator { + return this.getPage().locator('[data-id="quote-message"][data-type="message-action"]'); + } + + public messageStar(): Locator { + return this.getPage().locator('[data-id="star-message"][data-type="message-action"]'); + } + + public messageUnread(): Locator { + return this.getPage().locator('[data-id="mark-message-as-unread"][data-type="message-action"]'); + } + + public messageReplyInDM(): Locator { + return this.getPage().locator('[data-id="reply-directly"][data-type="message-action"]'); + } + + // public messageReaction(): Locator { return this.getPage().locator('.message-actions__button[data-message-action="reaction-message"]'); } + public messagePin(): Locator { + return this.getPage().locator('[data-id="pin-message"][data-type="message-action"]'); + } + // public messageClose(): Locator { return this.getPage().locator('[data-id="rc-popover-close"][data-type="message-action"]'); } + + // Emojis + public emojiPickerMainScreen(): Locator { + return this.getPage().locator('.emoji-picker'); + } + + public emojiPickerPeopleIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-people")]'); + } + + public emojiPickerNatureIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-nature")]'); + } + + public emojiPickerFoodIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-food")]'); + } + + public emojiPickerActivityIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-activity")]'); + } + + public emojiPickerTravelIcon(): Locator { + return this.getPage().locator('.emoji-picker .icon-travel'); + } + + public emojiPickerObjectsIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-objects")]'); + } + + public emojiPickerSymbolsIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-symbols")]'); + } + + public emojiPickerFlagsIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-flags")]'); + } + + public emojiPickerModifierIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-symbols")]'); + } + + public emojiPickerChangeTone(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "change-tone")]'); + } + + public emojiPickerCustomIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-rocket")]'); + } + + public emojiPickerRecentIcon(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-recent")]'); + } + + public emojiPickerFilter(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "js-emojipicker-search")]'); + } + + public emojiPickerEmojiContainer(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emojis")]'); + } + + public emojiGrinning(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-grinning")]'); + } + + public emojiSmile(): Locator { + return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-smile")]'); + } + + // Popover + public popoverWrapper(): Locator { + return this.getPage().locator('.rc-popover'); + } + + public async waitForLastMessageEqualsHtml(text: string): Promise { + await expect(this.getPage().locator('(//*[contains(@class, "message") and contains(@class, "body")])[last()]')).toContainText(text); + } + + public async waitForLastMessageEqualsText(text: string): Promise { + await expect(this.getPage().locator('(//*[contains(@class, "message") and contains(@class, "body")])[last()]')).toContainText(text); + } + + // Sends a message and wait for the message to equal the text sent + public async sendMessage(text: any): Promise { + await this.setTextToInput(text); + await this.sendBtn().click(); + await expect( + this.getPage().locator('(//*[contains(@class, "message-body-wrapper")])[last()]/div[contains(@class, "body")]'), + ).toContainText(text); + } + + // adds text to the input + public async addTextToInput(text: any): Promise { + await this.messageInput().type(text); + } + + // Clear and sets the text to the input + public async setTextToInput(text: any): Promise { + // cy.wait(200); + await this.messageInput().fill(''); + if (text) { + await this.messageInput().type(text); + } + } +} + +export default MainContent; diff --git a/tests/e2e/utils/pageobjects/preferences-main-content.page.ts b/tests/e2e/utils/pageobjects/preferences-main-content.page.ts new file mode 100644 index 000000000000..8e74a12b54b8 --- /dev/null +++ b/tests/e2e/utils/pageobjects/preferences-main-content.page.ts @@ -0,0 +1,65 @@ +import { expect, Locator } from '@playwright/test'; + +import BasePage from './BasePage'; + +class PreferencesMainContent extends BasePage { + public formTextInput(): Locator { + return this.getPage().locator('.rocket-form'); + } + + public realNameTextInput(): Locator { + return this.getPage().locator('//label[contains(text(), "Name")]/..//input'); + } + + public userNameTextInput(): Locator { + return this.getPage().locator('//label[contains(text(), "Username")]/..//input'); + } + + public emailTextInput(): Locator { + return this.getPage().locator('//label[contains(text(), "Email")]/..//input'); + } + + public passwordTextInput(): Locator { + return this.getPage().locator('//label[contains(text(), "Password")]/..//input'); + } + + public resendVerificationEmailBtn(): Locator { + return this.getPage().locator('#resend-verification-email'); + } + + public avatarFileInput(): Locator { + return this.getPage().locator('.avatar-file-input'); + } + + public useUploadedAvatar(): Locator { + return this.getPage().locator('.avatar-suggestion-item:nth-of-type(2) .select-service'); + } + + public submitBtn(): Locator { + return this.getPage().locator('//button[contains(text(), "Save changes")]'); + } + + public async changeUsername(userName: string): Promise { + await this.userNameTextInput().fill(userName); + } + + public async changeRealName(realName: string): Promise { + await this.realNameTextInput().fill(realName); + } + + public async changeEmail(email: string): Promise { + await this.emailTextInput().fill(email); + } + + public async saveChanges(): Promise { + await expect(this.submitBtn()).toBeEnabled(); + await this.submitBtn().click(); + } + + public async changeAvatarUpload(url: string): Promise { + await this.avatarFileInput().setInputFiles(url); + await this.useUploadedAvatar().click(); + } +} + +export default PreferencesMainContent; diff --git a/tests/e2e/utils/pageobjects/side-nav.page.ts b/tests/e2e/utils/pageobjects/side-nav.page.ts new file mode 100644 index 000000000000..bb3ad41f1d88 --- /dev/null +++ b/tests/e2e/utils/pageobjects/side-nav.page.ts @@ -0,0 +1,223 @@ +import { expect, Locator } from '@playwright/test'; + +import BasePage from './BasePage'; +// import mainContent from './main-content.page'; + +class SideNav extends BasePage { + // New channel + public channelType(): Locator { + return this.getPage().locator('#modal-root .rcx-field:contains("Private") .rcx-toggle-switch__fake'); + } + + public channelReadOnly(): Locator { + return this.getPage().locator('.create-channel__switches .rc-switch__button'); + } + + public channelName(): Locator { + return this.getPage().locator('#modal-root [placeholder="Channel Name"]'); + } + + public saveChannelBtn(): Locator { + return this.getPage().locator('#modal-root button:contains("Create")'); + } + + // Account box + public getPopOverContent(): Locator { + return this.getPage().locator('.rc-popover__content'); + } + + public accountBoxUserName(): Locator { + return this.getPage().locator('.sidebar__account-username'); + } + + public accountBoxUserAvatar(): Locator { + return this.getPage().locator('.sidebar__account .avatar-image'); + } + + public accountMenu(): Locator { + return this.getPage().locator('.sidebar__account'); + } + + public sidebarHeader(): Locator { + return this.getPage().locator('.sidebar__header'); + } + + public sidebarUserMenu(): Locator { + return this.getPage().locator('[data-qa="sidebar-avatar-button"]'); + } + + public sidebarMenu(): Locator { + return this.getPage().locator('.sidebar__toolbar-button-icon--menu'); + } + + public popOverContent(): Locator { + return this.getPage().locator('.rc-popover__content'); + } + + public popOverHideOption(): Locator { + return this.getPage().locator('.rcx-option__content:contains("Hide")'); + } + + public statusOnline(): Locator { + return this.getPage().locator('(//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "online")])[1]'); + } + + public statusAway(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "away")]'); + } + + public statusBusy(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "busy")]'); + } + + public statusOffline(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "offline")]'); + } + + public account(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "My Account")]'); + } + + public admin(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Administration")]'); + } + + public logout(): Locator { + return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Logout")]'); + } + + public sideNavBar(): Locator { + return this.getPage().locator('.sidebar'); + } + + // Toolbar + public spotlightSearchIcon(): Locator { + return this.getPage().locator('[data-qa="sidebar-search"]'); + } + + public spotlightSearch(): Locator { + return this.getPage().locator('[data-qa="sidebar-search-input"]'); + } + + public spotlightSearchPopUp(): Locator { + return this.getPage().locator('[data-qa="sidebar-search-result"]'); + } + + public newChannelBtnToolbar(): Locator { + return this.getPage().locator('[data-qa="sidebar-create"]'); + } + + public newChannelBtn(): Locator { + return this.getPage().locator('.rcx-option__content:contains("Channel")'); + } + + public newDiscussionBtn(): Locator { + return this.getPage().locator('.rcx-option__content:contains("Discussion")'); + } + + public newChannelIcon(): Locator { + return this.getPage().locator('[data-qa="sidebar-create-channel"]'); + } + + // Rooms List + public general(): Locator { + return this.getChannelFromList('general'); + } + + public channelLeave(): Locator { + return this.getPage().locator('.leave-room'); + } + + public channelHoverIcon(): Locator { + return this.getPage().locator('.rooms-list > .wrapper > ul [title="general"] .icon-eye-off'); + } + + // Account + public preferences(): Locator { + return this.getPage().locator('[href="/account/preferences"]'); + } + + public profile(): Locator { + return this.getPage().locator('[href="/account/profile"]'); + } + + public avatar(): Locator { + return this.getPage().locator('[href="/changeavatar"]'); + } + + public preferencesClose(): Locator { + return this.getPage().locator('//*[contains(@class,"flex-nav")]//i[contains(@class, "rcx-icon--name-cross")]'); + } + + public burgerBtn(): Locator { + return this.getPage().locator('.burger, [aria-label="Open_menu"]'); + } + + public sidebarWrap(): Locator { + return this.getPage().locator('.sidebar-wrap'); + } + + public firstSidebarItem(): Locator { + return this.getPage().locator('.sidebar-item'); + } + + public firstSidebarItemMenu(): Locator { + return this.getPage().locator('[data-qa=sidebar-avatar-button]'); + } + + public popoverOverlay(): Locator { + return this.getPage().locator('.rc-popover.rc-popover--sidebar-item'); + } + + // Opens a channel via rooms list + public async openChannel(channelName: any): Promise { + await this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).scrollIntoViewIfNeeded(); + await this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).click(); + await expect(this.getPage().locator('.rcx-room-header')).toContainText(channelName); + } + + // Opens a channel via spotlight search + public async searchChannel(channelName: string): Promise { + await expect(this.spotlightSearch()).toBeVisible(); + + await this.spotlightSearch().click(); + + await expect(this.spotlightSearch()).toBeFocused(); + await this.spotlightSearch().type(channelName); + + await expect(this.getPage().locator('.rcx-room-header')).toContainText(channelName); + } + + public async searchChannelAndOpen(channelName: string): Promise { + await this.searchChannel(channelName); + } + + // Gets a channel from the rooms list + public getChannelFromList(channelName: any): Locator { + return this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }); + } + + public async createChannel(channelName: any, isPrivate: any /* isReadOnly*/): Promise { + await this.newChannelBtnToolbar().click(); + + await this.newChannelBtn().click(); + + if (!isPrivate) { + await this.channelType().click(); + } + + await this.channelName().type(channelName); + + await expect(this.saveChannelBtn()).toBeEnabled(); + + // if (isReadOnly) { + // this.channelReadOnly.click(); + // } + + await this.saveChannelBtn().click(); + await expect(this.channelType()).not.toBeVisible(); + // mainContent.messageInput().should('be.focused'); + } +} + +export default SideNav; diff --git a/tests/e2e/utils/pageobjects/wizard.page.ts b/tests/e2e/utils/pageobjects/wizard.page.ts new file mode 100644 index 000000000000..7e2f9de2db5b --- /dev/null +++ b/tests/e2e/utils/pageobjects/wizard.page.ts @@ -0,0 +1,200 @@ +import { expect, Locator } from '@playwright/test'; + +import BasePage from './BasePage'; +import { reason, INVALID_EMAIL_WITHOUT_MAIL_PROVIDER } from '../mocks/userAndPasswordMock'; +import { IRegister } from '../interfaces/Login'; +import { BACKSPACE } from '../mocks/keyboardKeyMock'; + +class SetupWizard extends BasePage { + private nextStep(): Locator { + return this.getPage().locator('//button[contains(text(), "Next")]'); + } + + private fullName(): Locator { + return this.getPage().locator('[name="fullname"]'); + } + + private userName(): Locator { + return this.getPage().locator('[name="username"]'); + } + + private companyEmail(): Locator { + return this.getPage().locator('[name="companyEmail"]'); + } + + private password(): Locator { + return this.getPage().locator('[name="password"]'); + } + + public goToWorkspace(): Locator { + return this.getPage().locator('//button[contains(text(), "Confirm")]'); + } + + private organizationType(): Locator { + return this.getPage().locator('[name="organizationType"]'); + } + + private organizationTypeSelect(): Locator { + return this.getPage().locator('.rcx-options .rcx-option:first-child'); + } + + private organizationName(): Locator { + return this.getPage().locator('[name="organizationName"]'); + } + + private industry(): Locator { + return this.getPage().locator('[name="organizationIndustry"]'); + } + + private industrySelect(): Locator { + return this.getPage().locator('.rcx-options .rcx-option:first-child'); + } + + private size(): Locator { + return this.getPage().locator('[name="organizationSize"]'); + } + + private sizeSelect(): Locator { + return this.getPage().locator('.rcx-options .rcx-option:first-child'); + } + + private country(): Locator { + return this.getPage().locator('[name="country"]'); + } + + private countrySelect(): Locator { + return this.getPage().locator('.rcx-options .rcx-option:first-child'); + } + + public registeredServer(): Locator { + return this.getPage().locator('input[name=email]'); + } + + public registerButton(): Locator { + return this.getPage().locator('//button[contains(text(), "Register")]'); + } + + public agreementField(): Locator { + return this.getPage().locator('//input[@name="agreement"]/../i[contains(@class, "rcx-check-box")]'); + } + + public standaloneServer(): Locator { + return this.getPage().locator('//button[contains(text(), "Continue as standalone")]'); + } + + public standaloneConfirmText(): Locator { + return this.getPage().locator('//*[contains(text(), "Standalone Server Confirmation")]'); + } + + private fullNameIvalidtext(): Locator { + return this.getPage().locator('//input[@name="fullname"]/../following-sibling::span'); + } + + private userNameInvalidText(): Locator { + return this.getPage().locator('//input[@name="username"]/../following-sibling::span'); + } + + private companyEmailInvalidText(): Locator { + return this.getPage().locator('//input[@name="companyEmail"]/../following-sibling::span'); + } + + private passwordInvalidText(): Locator { + return this.getPage().locator('//input[@name="password"]/../../../span[contains(@class, "rcx-field__error")]'); + } + + private industryInvalidSelect(): Locator { + return this.getPage().locator('//div[@name="organizationIndustry"]/../following-sibling::span'); + } + + private sizeInvalidSelect(): Locator { + return this.getPage().locator('//div[@name="organizationSize"]/../following-sibling::span'); + } + + private countryInvalidSelect(): Locator { + return this.getPage().locator('//div[@name="country"]/../following-sibling::span'); + } + + public async goNext(): Promise { + await this.nextStep().click(); + } + + private stepThreeInputInvalidMail(): Locator { + return this.getPage().locator('//input[@name="email"]/../../span[contains(text(), "This field is required")]'); + } + + public async stepOneSucess(adminCredential: IRegister): Promise { + await this.fullName().type(adminCredential.name); + await this.userName().type(adminCredential.name); + await this.companyEmail().type(adminCredential.email); + await this.password().type(adminCredential.password); + await this.goNext(); + } + + public async stepTwoSucess(): Promise { + await this.organizationName().type(reason); + + await this.organizationType().click(); + await this.organizationTypeSelect().click(); + await expect(this.getPage().locator('.rcx-options')).toHaveCount(0); + + await this.industry().click(); + await this.industrySelect().click(); + await expect(this.getPage().locator('.rcx-options')).toHaveCount(0); + + await this.size().click(); + await this.sizeSelect().click(); + await expect(this.getPage().locator('.rcx-options')).toHaveCount(0); + + await this.country().click(); + await this.countrySelect().click(); + + await this.goNext(); + } + + public async stepThreeSucess(): Promise { + await this.standaloneServer().click(); + } + + public async stepOneFailedBlankFields(): Promise { + await this.goNext(); + + await expect(this.fullNameIvalidtext()).toBeVisible(); + await expect(this.userNameInvalidText()).toBeVisible(); + await expect(this.companyEmailInvalidText()).toBeVisible(); + await expect(this.passwordInvalidText()).toBeVisible(); + } + + public async stepOneFailedWithInvalidEmail(adminCredentials: IRegister): Promise { + await this.fullName().type(adminCredentials.name); + await this.userName().type(adminCredentials.name); + await this.companyEmail().type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); + await this.password().type(adminCredentials.password); + + await this.goNext(); + + await expect(this.companyEmail()).toBeFocused(); + } + + public async stepTwoFailedWithBlankFields(): Promise { + await this.goNext(); + + await expect(this.organizationName()).toBeVisible(); + await expect(this.industryInvalidSelect()).toBeVisible(); + await expect(this.sizeInvalidSelect()).toBeVisible(); + await expect(this.countryInvalidSelect()).toBeVisible(); + } + + public async stepThreeFailedWithInvalidField(): Promise { + await this.registeredServer().type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); + await this.registeredServer().click({ clickCount: 3 }); + await this.keyboardPress(BACKSPACE); + + await expect(this.stepThreeInputInvalidMail()).toBeVisible(); + } + + async goToHome(): Promise { + await this.goToWorkspace().click(); + } +} + +export default SetupWizard;