From d70690446e8935a159b93890ef9f5a93eafcd662 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 11 Apr 2019 11:32:32 +0300 Subject: [PATCH 01/57] Install basic tools Signed-off-by: Ihor Okhrimenko --- .gitignore | 5 + typescript-selenium/package-lock.json | 915 ++++++++++++++++++++++++++ typescript-selenium/package.json | 21 + typescript-selenium/tsconfig.json | 15 + 4 files changed, 956 insertions(+) create mode 100644 typescript-selenium/package-lock.json create mode 100644 typescript-selenium/package.json create mode 100644 typescript-selenium/tsconfig.json diff --git a/.gitignore b/.gitignore index 6ad2c310821..92665b69208 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,8 @@ requirements.lock */.grunt */.lock-wscript */cypress-tests/dist + + +# typescript-selenium module +*/typescript-selenium/dist/ +*/typescript-selenium/node_modules/ diff --git a/typescript-selenium/package-lock.json b/typescript-selenium/package-lock.json new file mode 100644 index 00000000000..49920e8d346 --- /dev/null +++ b/typescript-selenium/package-lock.json @@ -0,0 +1,915 @@ +{ + "name": "typescript-selenium", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/selenium-webdriver": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", + "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chromedriver": { + "version": "2.46.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.46.0.tgz", + "integrity": "sha512-dLtKIJW3y/PuFrPmcw6Mb8Nh+HwSqgVrK1rWgTARXhHfWvV822X2VRkx2meU/tg2+YQL6/nNgT6n5qWwIDHbwg==", + "dev": true, + "requires": { + "del": "^3.0.0", + "extract-zip": "^1.6.7", + "mkdirp": "^0.5.1", + "request": "^2.88.0", + "tcp-port-used": "^1.0.1" + } + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inversify": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", + "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "is2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", + "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "ip-regex": "^2.1.0", + "is-url": "^1.2.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz", + "integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "tcp-port-used": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", + "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", + "dev": true, + "requires": { + "debug": "4.1.0", + "is2": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", + "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + } + } +} diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json new file mode 100644 index 00000000000..28697362a94 --- /dev/null +++ b/typescript-selenium/package.json @@ -0,0 +1,21 @@ +{ + "name": "typescript-selenium", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Ihor Okhrimenko (iokhrime@redhat.com)", + "license": "ISC", + "devDependencies": { + "@types/selenium-webdriver": "^3.0.16", + "chromedriver": "^2.46.0", + "selenium-webdriver": "^3.6.0", + "typescript": "^3.4.3" + }, + "dependencies": { + "inversify": "^5.0.1", + "reflect-metadata": "^0.1.13" + } +} diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json new file mode 100644 index 00000000000..4147bf03bc7 --- /dev/null +++ b/typescript-selenium/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + + "outDir": "dist", + "lib": ["es6", "dom"], + "types": ["reflect-metadata"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true + + } +} From e4afc5b235a58b983f1b603c89d3b4df54237738 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 11 Apr 2019 11:44:51 +0300 Subject: [PATCH 02/57] Add 'Driver' definition Signed-off-by: Ihor Okhrimenko --- .../dist/driver/ChromeDriver.js | 35 +++++++++++++++++++ typescript-selenium/dist/driver/Driver.js | 2 ++ typescript-selenium/dist/inversify.config.js | 8 +++++ typescript-selenium/dist/spec.js | 11 ++++++ typescript-selenium/dist/types.js | 6 ++++ typescript-selenium/driver/ChromeDriver.ts | 26 ++++++++++++++ typescript-selenium/driver/Driver.ts | 5 +++ typescript-selenium/inversify.config.ts | 11 ++++++ typescript-selenium/spec.ts | 15 ++++++++ typescript-selenium/types.ts | 6 ++++ 10 files changed, 125 insertions(+) create mode 100644 typescript-selenium/dist/driver/ChromeDriver.js create mode 100644 typescript-selenium/dist/driver/Driver.js create mode 100644 typescript-selenium/dist/inversify.config.js create mode 100644 typescript-selenium/dist/spec.js create mode 100644 typescript-selenium/dist/types.js create mode 100644 typescript-selenium/driver/ChromeDriver.ts create mode 100644 typescript-selenium/driver/Driver.ts create mode 100644 typescript-selenium/inversify.config.ts create mode 100644 typescript-selenium/spec.ts create mode 100644 typescript-selenium/types.ts diff --git a/typescript-selenium/dist/driver/ChromeDriver.js b/typescript-selenium/dist/driver/ChromeDriver.js new file mode 100644 index 00000000000..b7828ebadbb --- /dev/null +++ b/typescript-selenium/dist/driver/ChromeDriver.js @@ -0,0 +1,35 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +require("chromedriver"); +require("reflect-metadata"); +var inversify_1 = require("inversify"); +var selenium_webdriver_1 = require("selenium-webdriver"); +var ChromeDriver = /** @class */ (function () { + function ChromeDriver() { + this.driver = new selenium_webdriver_1.Builder() + .forBrowser('chrome') + .build(); + this.driver + .manage() + .window() + .setSize(1920, 1080); + } + ChromeDriver.prototype.get = function () { + return this.driver; + }; + ChromeDriver = __decorate([ + inversify_1.injectable(), + __metadata("design:paramtypes", []) + ], ChromeDriver); + return ChromeDriver; +}()); +exports.ChromeDriver = ChromeDriver; diff --git a/typescript-selenium/dist/driver/Driver.js b/typescript-selenium/dist/driver/Driver.js new file mode 100644 index 00000000000..c8ad2e549bd --- /dev/null +++ b/typescript-selenium/dist/driver/Driver.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/typescript-selenium/dist/inversify.config.js b/typescript-selenium/dist/inversify.config.js new file mode 100644 index 00000000000..4c73dda91e6 --- /dev/null +++ b/typescript-selenium/dist/inversify.config.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var inversify_1 = require("inversify"); +var types_1 = require("./types"); +var ChromeDriver_1 = require("./driver/ChromeDriver"); +var e2eContainer = new inversify_1.Container(); +exports.e2eContainer = e2eContainer; +e2eContainer.bind(types_1.TYPES.Driver).to(ChromeDriver_1.ChromeDriver); diff --git a/typescript-selenium/dist/spec.js b/typescript-selenium/dist/spec.js new file mode 100644 index 00000000000..87c600e6739 --- /dev/null +++ b/typescript-selenium/dist/spec.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var inversify_config_1 = require("./inversify.config"); +var types_1 = require("./types"); +var driver = inversify_config_1.e2eContainer.get(types_1.TYPES.Driver); +function doNavigation() { + driver.get() + .navigate() + .to("https://google.com"); +} +doNavigation(); diff --git a/typescript-selenium/dist/types.js b/typescript-selenium/dist/types.js new file mode 100644 index 00000000000..2d9ae04c41b --- /dev/null +++ b/typescript-selenium/dist/types.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var TYPES = { + Driver: Symbol.for("Driver") +}; +exports.TYPES = TYPES; diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts new file mode 100644 index 00000000000..c6ede02f776 --- /dev/null +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -0,0 +1,26 @@ +import 'chromedriver'; +import 'reflect-metadata'; +import { injectable, inject } from "inversify"; +import { ThenableWebDriver, Builder } from "selenium-webdriver"; +import { Driver } from './Driver'; + +@injectable() +export class ChromeDriver implements Driver { + private readonly driver: ThenableWebDriver; + + constructor() { + this.driver = new Builder() + .forBrowser('chrome') + .build(); + + this.driver + .manage() + .window() + .setSize(1920, 1080) + } + + get(): ThenableWebDriver{ + return this.driver + } + +} diff --git a/typescript-selenium/driver/Driver.ts b/typescript-selenium/driver/Driver.ts new file mode 100644 index 00000000000..e724428e440 --- /dev/null +++ b/typescript-selenium/driver/Driver.ts @@ -0,0 +1,5 @@ +import { ThenableWebDriver } from "selenium-webdriver"; + +export interface Driver { + get(): ThenableWebDriver +} diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts new file mode 100644 index 00000000000..24684ef946e --- /dev/null +++ b/typescript-selenium/inversify.config.ts @@ -0,0 +1,11 @@ +import { Container } from "inversify"; +import { Driver } from "./driver/Driver"; +import { TYPES } from "./types"; +import { ChromeDriver } from "./driver/ChromeDriver"; + +const e2eContainer = new Container(); + +e2eContainer.bind(TYPES.Driver).to(ChromeDriver).inSingletonScope(); + + +export { e2eContainer } diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts new file mode 100644 index 00000000000..7a7f177f552 --- /dev/null +++ b/typescript-selenium/spec.ts @@ -0,0 +1,15 @@ +import { e2eContainer } from "./inversify.config"; +import { Driver } from "./driver/Driver"; +import { TYPES } from "./types"; + + +const driver: Driver = e2eContainer.get(TYPES.Driver) + +function doNavigation() { + driver.get() + .navigate() + .to("https://google.com") +} + +doNavigation() + diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts new file mode 100644 index 00000000000..f4cc6860ed1 --- /dev/null +++ b/typescript-selenium/types.ts @@ -0,0 +1,6 @@ +const TYPES = { + Driver: Symbol.for("Driver") + +} + +export { TYPES }; From 3cb248ec46020954c7b4669cbd821b485b610737 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 11 Apr 2019 15:35:22 +0300 Subject: [PATCH 03/57] Add first pageobject Signed-off-by: Ihor Okhrimenko --- typescript-selenium/dist/inversify.config.js | 4 +- .../dist/pageobjects/dashboard/Dashboard.js | 42 +++++++ typescript-selenium/dist/spec.js | 64 +++++++++- .../dist/utils/DriverHelper.js | 118 ++++++++++++++++++ typescript-selenium/inversify.config.ts | 3 +- .../pageobjects/dashboard/Dashboard.ts | 33 +++++ typescript-selenium/spec.ts | 17 ++- typescript-selenium/utils/DriverHelper.ts | 60 +++++++++ 8 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 typescript-selenium/dist/pageobjects/dashboard/Dashboard.js create mode 100644 typescript-selenium/dist/utils/DriverHelper.js create mode 100644 typescript-selenium/pageobjects/dashboard/Dashboard.ts create mode 100644 typescript-selenium/utils/DriverHelper.ts diff --git a/typescript-selenium/dist/inversify.config.js b/typescript-selenium/dist/inversify.config.js index 4c73dda91e6..4df7d719650 100644 --- a/typescript-selenium/dist/inversify.config.js +++ b/typescript-selenium/dist/inversify.config.js @@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); var inversify_1 = require("inversify"); var types_1 = require("./types"); var ChromeDriver_1 = require("./driver/ChromeDriver"); +var DriverHelper_1 = require("./utils/DriverHelper"); var e2eContainer = new inversify_1.Container(); exports.e2eContainer = e2eContainer; -e2eContainer.bind(types_1.TYPES.Driver).to(ChromeDriver_1.ChromeDriver); +e2eContainer.bind(types_1.TYPES.Driver).to(ChromeDriver_1.ChromeDriver).inSingletonScope(); +e2eContainer.bind('DriverHelper').to(DriverHelper_1.DriverHelper); diff --git a/typescript-selenium/dist/pageobjects/dashboard/Dashboard.js b/typescript-selenium/dist/pageobjects/dashboard/Dashboard.js new file mode 100644 index 00000000000..ed5f40bf86c --- /dev/null +++ b/typescript-selenium/dist/pageobjects/dashboard/Dashboard.js @@ -0,0 +1,42 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var inversify_1 = require("inversify"); +require("reflect-metadata"); +var types_1 = require("../../types"); +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +var Dashboard = /** @class */ (function () { + function Dashboard(driver) { + this.driver = driver; + } + Dashboard.DASHBOARD_BUTTON = "#dashboard-item"; + Dashboard.WORKSPACES_BUTTON = "#workspaces-item"; + Dashboard.STACKS_BUTTON = "#stacks-item"; + Dashboard.FACTORIES_BUTTON = "#factories-item"; + Dashboard.LOADER_PAGE = ".main-page-loader"; + Dashboard = __decorate([ + __param(0, inversify_1.inject(types_1.TYPES.Driver)), + __metadata("design:paramtypes", [Object]) + ], Dashboard); + return Dashboard; +}()); +exports.Dashboard = Dashboard; diff --git a/typescript-selenium/dist/spec.js b/typescript-selenium/dist/spec.js index 87c600e6739..a5fa8263b38 100644 --- a/typescript-selenium/dist/spec.js +++ b/typescript-selenium/dist/spec.js @@ -1,11 +1,69 @@ "use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; Object.defineProperty(exports, "__esModule", { value: true }); var inversify_config_1 = require("./inversify.config"); var types_1 = require("./types"); var driver = inversify_config_1.e2eContainer.get(types_1.TYPES.Driver); +var driverHelper = inversify_config_1.e2eContainer.get('DriverHelper'); function doNavigation() { - driver.get() - .navigate() - .to("https://google.com"); + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, driver.get() + .navigate() + .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace")]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); +} +function ttt() { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, driverHelper.waitVisibility("sssss")]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); } doNavigation(); +ttt(); diff --git a/typescript-selenium/dist/utils/DriverHelper.js b/typescript-selenium/dist/utils/DriverHelper.js new file mode 100644 index 00000000000..9b732a304d5 --- /dev/null +++ b/typescript-selenium/dist/utils/DriverHelper.js @@ -0,0 +1,118 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var inversify_1 = require("inversify"); +var types_1 = require("../types"); +require("selenium-webdriver"); +require("reflect-metadata"); +var selenium_webdriver_1 = require("selenium-webdriver"); +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +var DriverHelper = /** @class */ (function () { + function DriverHelper(driver) { + this.driver = driver.get(); + } + DriverHelper.prototype.findElement = function (locator) { + return this.driver.findElement(locator); + }; + DriverHelper.prototype.isVisible = function (locator) { + try { + return this.findElement(locator).isDisplayed(); + } + catch (err) { + return new selenium_webdriver_1.promise.Promise(function (resolve) { resolve(false); }); + } + }; + DriverHelper.prototype.wait = function (miliseconds) { + return new selenium_webdriver_1.promise.Promise(function (resolve) { setTimeout(resolve, miliseconds); }); + }; + DriverHelper.prototype.waitVisibility = function (locator) { + return __awaiter(this, void 0, void 0, function () { + var i; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + i = 0; + _a.label = 1; + case 1: + if (!(i < 10)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.isVisible(locator)]; + case 2: + if (_a.sent()) { + console.log("===>>>> visible"); + return [2 /*return*/]; + } + console.log("===>>>> wait"); + return [4 /*yield*/, this.wait(5000)]; + case 3: + _a.sent(); + _a.label = 4; + case 4: + i++; + return [3 /*break*/, 1]; + case 5: return [2 /*return*/]; + } + }); + }); + }; + DriverHelper = __decorate([ + inversify_1.injectable(), + __param(0, inversify_1.inject(types_1.TYPES.Driver)), + __metadata("design:paramtypes", [Object]) + ], DriverHelper); + return DriverHelper; +}()); +exports.DriverHelper = DriverHelper; diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 24684ef946e..2117baba9f6 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -2,10 +2,11 @@ import { Container } from "inversify"; import { Driver } from "./driver/Driver"; import { TYPES } from "./types"; import { ChromeDriver } from "./driver/ChromeDriver"; +import { DriverHelper } from "./utils/DriverHelper"; const e2eContainer = new Container(); e2eContainer.bind(TYPES.Driver).to(ChromeDriver).inSingletonScope(); - +e2eContainer.bind('DriverHelper').to(DriverHelper) export { e2eContainer } diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts new file mode 100644 index 00000000000..94155447d16 --- /dev/null +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -0,0 +1,33 @@ +import { inject } from "inversify"; +import "reflect-metadata"; +import { TYPES } from "../../types"; +import { Driver } from "../../driver/Driver"; +import { WebElementCondition } from "selenium-webdriver"; + +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + + export class Dashboard { + private readonly driver: Driver; + + private static readonly DASHBOARD_BUTTON: string = "#dashboard-item"; + private static readonly WORKSPACES_BUTTON: string = "#workspaces-item"; + private static readonly STACKS_BUTTON: string = "#stacks-item"; + private static readonly FACTORIES_BUTTON: string = "#factories-item"; + private static readonly LOADER_PAGE: string = ".main-page-loader" + + constructor( + @inject(TYPES.Driver) driver: Driver + ){ + this.driver = driver; + } + + + } \ No newline at end of file diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 7a7f177f552..68ca4c1e57e 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -1,15 +1,24 @@ import { e2eContainer } from "./inversify.config"; import { Driver } from "./driver/Driver"; import { TYPES } from "./types"; +import { DriverHelper } from "./utils/DriverHelper"; +import { By } from "selenium-webdriver"; -const driver: Driver = e2eContainer.get(TYPES.Driver) +const driver: Driver = e2eContainer.get(TYPES.Driver); +const driverHelper: DriverHelper = e2eContainer.get('DriverHelper'); -function doNavigation() { - driver.get() +async function doNavigation() { + await driver.get() .navigate() - .to("https://google.com") + .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") +} + +async function ttt(){ + await driverHelper.waitVisibility(By.css("#dashboard-item")); } doNavigation() +ttt() + diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts new file mode 100644 index 00000000000..a6e66a13737 --- /dev/null +++ b/typescript-selenium/utils/DriverHelper.ts @@ -0,0 +1,60 @@ +import { Driver } from "../driver/Driver"; +import { inject, injectable } from "inversify"; +import { TYPES } from "../types"; +import 'selenium-webdriver'; +import 'reflect-metadata'; +import { WebElementPromise, ThenableWebDriver, By, promise } from "selenium-webdriver"; + +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +@injectable() +export class DriverHelper { + private readonly driver: ThenableWebDriver; + + constructor( + @inject(TYPES.Driver) driver: Driver + ) { + this.driver = driver.get(); + } + + public findElement(locator: By): WebElementPromise{ + return this.driver.findElement(locator); + } + + public isVisible(locator: By): promise.Promise{ + try{ + return this.findElement(locator).isDisplayed(); + }catch(err){ + return new promise.Promise(resolve =>{resolve(false)}) + } + } + + public wait(miliseconds: number): promise.Promise{ + return new promise.Promise(resolve => {setTimeout(resolve, miliseconds)}) + } + + public async waitVisibility(locator: By){ + for(let i = 0; i < 10; i++){ + if (await this.isVisible(locator)){ + console.log("===>>>> visible") + return; + } + + console.log("===>>>> wait") + await this.wait(5000); + } + + } + + + + +} \ No newline at end of file From ca8b301c483f169d23aff643caec20939b61abcc Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 12 Apr 2019 14:53:32 +0300 Subject: [PATCH 04/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/package-lock.json | 973 ++++++++++++++++++++++ typescript-selenium/package.json | 7 +- typescript-selenium/spec.ts | 30 +- typescript-selenium/tsconfig.json | 2 +- typescript-selenium/utils/DriverHelper.ts | 40 +- 5 files changed, 1028 insertions(+), 24 deletions(-) diff --git a/typescript-selenium/package-lock.json b/typescript-selenium/package-lock.json index 49920e8d346..7eb1a782d31 100644 --- a/typescript-selenium/package-lock.json +++ b/typescript-selenium/package-lock.json @@ -4,6 +4,18 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, + "@types/mocha": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", + "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", + "dev": true + }, "@types/selenium-webdriver": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", @@ -22,6 +34,42 @@ "uri-js": "^4.2.2" } }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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" + } + }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -52,6 +100,12 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -95,18 +149,72 @@ "concat-map": "0.0.1" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "chromedriver": { "version": "2.46.0", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.46.0.tgz", @@ -120,6 +228,38 @@ "tcp-port-used": "^1.0.1" } }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -153,6 +293,19 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -171,12 +324,36 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -197,6 +374,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -207,6 +390,73 @@ "safer-buffer": "^2.1.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -252,6 +502,24 @@ "pend": "~1.2.0" } }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -275,6 +543,33 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -319,6 +614,12 @@ } } }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -335,6 +636,33 @@ "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -373,12 +701,42 @@ "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -403,6 +761,30 @@ "path-is-inside": "^1.0.1" } }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -432,12 +814,28 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -486,6 +884,15 @@ "set-immediate-shim": "~1.0.1" } }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, "lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -495,6 +902,57 @@ "immediate": "~3.0.5" } }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", @@ -510,6 +968,12 @@ "mime-db": "~1.38.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -534,12 +998,91 @@ "minimist": "0.0.8" } }, + "mocha": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz", + "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.0", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -552,6 +1095,34 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -561,24 +1132,83 @@ "wrappy": "1" } }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, "p-map": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", "dev": true }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "pako": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", "dev": true }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -591,6 +1221,18 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -636,6 +1278,16 @@ "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -696,6 +1348,18 @@ "uuid": "^3.3.2" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -735,12 +1399,67 @@ "xml2js": "^0.4.17" } }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -758,6 +1477,16 @@ "tweetnacl": "~0.14.0" } }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -767,6 +1496,36 @@ "safe-buffer": "~5.1.0" } }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, "tcp-port-used": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", @@ -821,6 +1580,19 @@ } } }, + "ts-node": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.3.tgz", + "integrity": "sha512-2qayBA4vdtVRuDo11DEFSsD/SFsBXQBRZZhbRGSIkmYmVkWjULn/GGMdG10KVqkaGndljfaTD8dKjWgcejO8YA==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -836,6 +1608,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -880,6 +1658,77 @@ "extsprintf": "^1.2.0" } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -902,6 +1751,124 @@ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yauzl": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", @@ -910,6 +1877,12 @@ "requires": { "fd-slicer": "~1.0.1" } + }, + "yn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", + "dev": true } } } diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 28697362a94..f0584c6b9ad 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,14 +4,19 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha --require ts-node/register --timeout 150000 spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", "license": "ISC", "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.6", "@types/selenium-webdriver": "^3.0.16", + "chai": "^4.2.0", "chromedriver": "^2.46.0", + "mocha": "^6.1.3", "selenium-webdriver": "^3.6.0", + "ts-node": "^8.0.3", "typescript": "^3.4.3" }, "dependencies": { diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 68ca4c1e57e..dfed09b2a10 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -2,23 +2,43 @@ import { e2eContainer } from "./inversify.config"; import { Driver } from "./driver/Driver"; import { TYPES } from "./types"; import { DriverHelper } from "./utils/DriverHelper"; -import { By } from "selenium-webdriver"; +import { By, WebElementCondition, Condition } from "selenium-webdriver"; +import { describe } from "mocha"; +import { expect } from "chai"; +import { until } from "selenium-webdriver" const driver: Driver = e2eContainer.get(TYPES.Driver); const driverHelper: DriverHelper = e2eContainer.get('DriverHelper'); -async function doNavigation() { +async function doNavigation(): Promise { await driver.get() .navigate() .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") } -async function ttt(){ +async function waitDashboardButton(): Promise { await driverHelper.waitVisibility(By.css("#dashboard-item")); } -doNavigation() -ttt() +describe("E2E", async () => { + it("navigate", async () => { + await doNavigation() + }) + + + it("wait dashboard button", async () => { + await waitDashboardButton() + }) + + + it("wait dashboard button by until condition", async () => { + await driver.get().wait(until.elementLocated(By.css("#dashboard-item")), 10000) + expect(true).to.be.true + await driver.get().quit() + }) + + +}) diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json index 4147bf03bc7..1d324b88b86 100644 --- a/typescript-selenium/tsconfig.json +++ b/typescript-selenium/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "dist", "lib": ["es6", "dom"], - "types": ["reflect-metadata"], + "types": ["reflect-metadata", "@types/mocha"], "experimentalDecorators": true, "emitDecoratorMetadata": true diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index a6e66a13737..2b9d6e962e8 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -15,7 +15,7 @@ import { WebElementPromise, ThenableWebDriver, By, promise } from "selenium-webd * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -@injectable() +@injectable() export class DriverHelper { private readonly driver: ThenableWebDriver; @@ -25,33 +25,39 @@ export class DriverHelper { this.driver = driver.get(); } - public findElement(locator: By): WebElementPromise{ + public findElement(locator: By): WebElementPromise { return this.driver.findElement(locator); } - public isVisible(locator: By): promise.Promise{ - try{ - return this.findElement(locator).isDisplayed(); - }catch(err){ - return new promise.Promise(resolve =>{resolve(false)}) - } + public isVisible(locator: By): promise.Promise { + // try{ + // return this.findElement(locator).isDisplayed(); + // }catch(err){ + // return new promise.Promise(resolve =>{resolve(false)}) + // } + + return this.findElement(locator).isDisplayed().catch(err => { return false}) + + + + + } - public wait(miliseconds: number): promise.Promise{ - return new promise.Promise(resolve => {setTimeout(resolve, miliseconds)}) + public wait(miliseconds: number): promise.Promise { + return new promise.Promise(resolve => { setTimeout(resolve, miliseconds) }) } - public async waitVisibility(locator: By){ - for(let i = 0; i < 10; i++){ - if (await this.isVisible(locator)){ - console.log("===>>>> visible") - return; + public async waitVisibility(locator: By): Promise { + for (let i = 0; i < 10; i++) { + if (await this.isVisible(locator)) { + return true; } - console.log("===>>>> wait") await this.wait(5000); } - + + return false; } From e307ce4b9e498108dcd35b6c6ec18a63554df17e Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 12 Apr 2019 18:00:35 +0300 Subject: [PATCH 05/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/inversify.config.ts | 9 ++- .../pageobjects/dashboard/Dashboard.ts | 49 ++++++++---- typescript-selenium/spec.ts | 33 ++++---- typescript-selenium/types.ts | 9 ++- typescript-selenium/utils/DriverHelper.ts | 77 +++++++++++++++---- 5 files changed, 128 insertions(+), 49 deletions(-) diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 2117baba9f6..01c2a6e7c87 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -3,10 +3,17 @@ import { Driver } from "./driver/Driver"; import { TYPES } from "./types"; import { ChromeDriver } from "./driver/ChromeDriver"; import { DriverHelper } from "./utils/DriverHelper"; +import { LoginPage } from "./pageobjects/login/LoginPage"; +import { SingleUserLoginPage } from "./pageobjects/login/SingleUserLoginPage"; +import { Dashboard } from "./pageobjects/dashboard/Dashboard"; const e2eContainer = new Container(); e2eContainer.bind(TYPES.Driver).to(ChromeDriver).inSingletonScope(); -e2eContainer.bind('DriverHelper').to(DriverHelper) +e2eContainer.bind(TYPES.LoginPage).to(SingleUserLoginPage).inSingletonScope(); + +e2eContainer.bind('DriverHelper').to(DriverHelper).inSingletonScope(); +e2eContainer.bind('Dashboard').to(Dashboard).inSingletonScope(); + export { e2eContainer } diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 94155447d16..2ab6a5dc651 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -1,8 +1,9 @@ -import { inject } from "inversify"; +import { inject, injectable } from "inversify"; import "reflect-metadata"; -import { TYPES } from "../../types"; +import { TYPES, CLASSES } from "../../types"; import { Driver } from "../../driver/Driver"; -import { WebElementCondition } from "selenium-webdriver"; +import { WebElementCondition, By } from "selenium-webdriver"; +import { DriverHelper } from "../../utils/DriverHelper"; /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. @@ -14,20 +15,40 @@ import { WebElementCondition } from "selenium-webdriver"; * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ - export class Dashboard { - private readonly driver: Driver; +@injectable() +export class Dashboard { + private readonly driverHelper: DriverHelper; - private static readonly DASHBOARD_BUTTON: string = "#dashboard-item"; - private static readonly WORKSPACES_BUTTON: string = "#workspaces-item"; - private static readonly STACKS_BUTTON: string = "#stacks-item"; - private static readonly FACTORIES_BUTTON: string = "#factories-item"; - private static readonly LOADER_PAGE: string = ".main-page-loader" + private static readonly DASHBOARD_BUTTON_CSS: string = "#dashboard-item"; + private static readonly WORKSPACES_BUTTON_CSS: string = "#workspaces-item"; + private static readonly STACKS_BUTTON_CSS: string = "#stacks-item"; + private static readonly FACTORIES_BUTTON_CSS: string = "#factories-item"; + private static readonly LOADER_PAGE_CSS: string = ".main-page-loader" constructor( - @inject(TYPES.Driver) driver: Driver - ){ - this.driver = driver; + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + waitPage(timeout: number) { + it('Wait Dashboard page', async () => { + await this.driverHelper + .waitAllVisibility([ + By.css(Dashboard.DASHBOARD_BUTTON_CSS), + By.css(Dashboard.WORKSPACES_BUTTON_CSS), + By.css(Dashboard.STACKS_BUTTON_CSS), + By.css(Dashboard.FACTORIES_BUTTON_CSS), + By.css(Dashboard.LOADER_PAGE_CSS) + ], timeout) + }) + } + + clickDashboardButton(timeout = DriverHelper.DEFAULT_TIMEOUT) { + it("click 'Dashboard' button", async () => { + await this.driverHelper.click(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) + }) } - } \ No newline at end of file +} \ No newline at end of file diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index dfed09b2a10..0ca11a5e8ec 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -1,15 +1,18 @@ import { e2eContainer } from "./inversify.config"; import { Driver } from "./driver/Driver"; -import { TYPES } from "./types"; +import { TYPES, CLASSES } from "./types"; import { DriverHelper } from "./utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; -import { describe } from "mocha"; -import { expect } from "chai"; -import { until } from "selenium-webdriver" +import { describe, after } from "mocha"; +import { LoginPage } from "./pageobjects/login/LoginPage"; +import { Dashboard } from "./pageobjects/dashboard/Dashboard"; + const driver: Driver = e2eContainer.get(TYPES.Driver); -const driverHelper: DriverHelper = e2eContainer.get('DriverHelper'); +const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); +const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); +const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) async function doNavigation(): Promise { await driver.get() @@ -18,27 +21,25 @@ async function doNavigation(): Promise { } async function waitDashboardButton(): Promise { - await driverHelper.waitVisibility(By.css("#dashboard-item")); + await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item")); } describe("E2E", async () => { - it("navigate", async () => { - await doNavigation() - }) - + loginPage.login() - it("wait dashboard button", async () => { - await waitDashboardButton() - }) + dashboard.waitPage(100000) + dashboard.clickDashboardButton() it("wait dashboard button by until condition", async () => { - await driver.get().wait(until.elementLocated(By.css("#dashboard-item")), 10000) - expect(true).to.be.true - await driver.get().quit() + await driverHelper.click(By.css("#workspaces-item")) }) + after("close browser", async () => { + await driver.get().quit() + }) }) + diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index f4cc6860ed1..56ccc59e63b 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -1,6 +1,11 @@ const TYPES = { - Driver: Symbol.for("Driver") + Driver: Symbol.for("Driver"), + LoginPage: Symbol.for("LoginPage") +} +const CLASSES = { + DriverHelper: "DriverHelper", + Dashboard: "Dashboard" } -export { TYPES }; +export { TYPES, CLASSES }; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 2b9d6e962e8..9cd8bd34cfe 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -3,7 +3,7 @@ import { inject, injectable } from "inversify"; import { TYPES } from "../types"; import 'selenium-webdriver'; import 'reflect-metadata'; -import { WebElementPromise, ThenableWebDriver, By, promise } from "selenium-webdriver"; +import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement } from "selenium-webdriver"; /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. @@ -17,6 +17,9 @@ import { WebElementPromise, ThenableWebDriver, By, promise } from "selenium-webd @injectable() export class DriverHelper { + public static readonly DEFAULT_TIMEOUT: number = 20000; + public static readonly DEFAULT_ATTEMPTS: number = 20; + public static readonly DEFAULT_POLLING: number = 1000; private readonly driver: ThenableWebDriver; constructor( @@ -30,36 +33,78 @@ export class DriverHelper { } public isVisible(locator: By): promise.Promise { - // try{ - // return this.findElement(locator).isDisplayed(); - // }catch(err){ - // return new promise.Promise(resolve =>{resolve(false)}) - // } - - return this.findElement(locator).isDisplayed().catch(err => { return false}) - - - - - + return this.findElement(locator) + .isDisplayed() + .catch(err => { + return false + }) } public wait(miliseconds: number): promise.Promise { return new promise.Promise(resolve => { setTimeout(resolve, miliseconds) }) } - public async waitVisibility(locator: By): Promise { - for (let i = 0; i < 10; i++) { + public async waitVisibilityBoolean(locator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { + for (let i = 0; i < attempts; i++) { if (await this.isVisible(locator)) { return true; } - await this.wait(5000); + await this.wait(polling); + } + + return false; + } + + public async waitDisappearanceBoolean(locator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { + for (let i = 0; i < attempts; i++) { + if (await !this.isVisible(locator)) { + return true; + } + + await this.wait(polling); } return false; } + public waitVisibility(elementLocator: By, timeout = DriverHelper.DEFAULT_TIMEOUT): promise.Promise { + return new promise.Promise((resolve, reject) => { + this.driver + .wait(until.elementLocated(elementLocator), timeout) + .then(webElement => { + resolve(this.driver.wait(until.elementIsVisible(webElement), timeout)) + }) + }) + } + + public async waitAllVisibility(locators: Array, timeout = DriverHelper.DEFAULT_TIMEOUT): Promise { + await locators.forEach(async elementLocator => { + await this.waitVisibility(elementLocator, timeout) + }) + } + + public async waitDisappearance(elementLocator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { + await this.waitDisappearanceBoolean(elementLocator, attempts, polling) + .then(isVisible => { + if (isVisible) { + throw new Error(`Waiting attempts exceeded, element '${elementLocator}' is still visible`) + } + }) + } + + public async waitAllDisappearance(locators: Array, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING ): Promise { + await locators.forEach(async elementLocator => { + await this.waitDisappearance(elementLocator, attempts, polling) + }) + } + + public click(elementLocator: By, timeout = DriverHelper.DEFAULT_TIMEOUT): promise.Promise { + return this.waitVisibility(elementLocator, timeout).then(element => { element.click() }) + } + + + From 8c2f2f5ee65a43d23e82b7dfdddca4429609896e Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 12 Apr 2019 18:00:52 +0300 Subject: [PATCH 06/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- .../pageobjects/login/LoginPage.ts | 3 +++ .../pageobjects/login/SingleUserLoginPage.ts | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 typescript-selenium/pageobjects/login/LoginPage.ts create mode 100644 typescript-selenium/pageobjects/login/SingleUserLoginPage.ts diff --git a/typescript-selenium/pageobjects/login/LoginPage.ts b/typescript-selenium/pageobjects/login/LoginPage.ts new file mode 100644 index 00000000000..d5372aa419e --- /dev/null +++ b/typescript-selenium/pageobjects/login/LoginPage.ts @@ -0,0 +1,3 @@ +export interface LoginPage { + login(): void +} diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts new file mode 100644 index 00000000000..cd0dd38a11a --- /dev/null +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -0,0 +1,27 @@ +import "reflect-metadata"; +import { LoginPage } from "./LoginPage"; +import { injectable, inject } from "inversify"; +import { ThenableWebDriver } from "selenium-webdriver"; +import { TYPES } from "../../types"; +import { Driver } from "../../driver/Driver"; + +@injectable() +export class SingleUserLoginPage implements LoginPage { + private readonly driver: ThenableWebDriver; + + constructor( + @inject(TYPES.Driver) driver: Driver + ) { + this.driver = driver.get(); + } + + + login(){ + it("Open dashboard", async () => { + await this.driver + .navigate() + .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") + }) + } + +} \ No newline at end of file From 13b64e305057b4b78a1b822698207afae30528e4 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 15 Apr 2019 14:25:11 +0300 Subject: [PATCH 07/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/package.json | 2 +- .../pageobjects/dashboard/Dashboard.ts | 26 ++++++-------- .../pageobjects/login/SingleUserLoginPage.ts | 10 +++--- typescript-selenium/spec.ts | 34 +++++++++---------- typescript-selenium/utils/DriverHelper.ts | 20 +++++++---- 5 files changed, 45 insertions(+), 47 deletions(-) diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index f0584c6b9ad..2eda97e09a5 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --require ts-node/register --timeout 150000 spec.ts" + "test": "mocha --require ts-node/register --timeout 150000 -u tdd spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", "license": "ISC", diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 2ab6a5dc651..6ca698d4bf0 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -31,23 +31,19 @@ export class Dashboard { this.driverHelper = driverHelper; } - waitPage(timeout: number) { - it('Wait Dashboard page', async () => { - await this.driverHelper - .waitAllVisibility([ - By.css(Dashboard.DASHBOARD_BUTTON_CSS), - By.css(Dashboard.WORKSPACES_BUTTON_CSS), - By.css(Dashboard.STACKS_BUTTON_CSS), - By.css(Dashboard.FACTORIES_BUTTON_CSS), - By.css(Dashboard.LOADER_PAGE_CSS) - ], timeout) - }) + async waitPage(timeout: number) { + await this.driverHelper.waitVisibility(By.css(Dashboard.DASHBOARD_BUTTON_CSS)) + await this.driverHelper.waitVisibility(By.css(Dashboard.WORKSPACES_BUTTON_CSS)) + await this.driverHelper.waitVisibility(By.css(Dashboard.STACKS_BUTTON_CSS)) + await this.driverHelper.waitVisibility(By.css(Dashboard.FACTORIES_BUTTON_CSS)) } - clickDashboardButton(timeout = DriverHelper.DEFAULT_TIMEOUT) { - it("click 'Dashboard' button", async () => { - await this.driverHelper.click(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) - }) + async clickDashboardButton(timeout = DriverHelper.DEFAULT_TIMEOUT) { + await this.driverHelper.click(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) + } + + async waitLoaderInvisibility() { + await this.driverHelper.waitDisappearance(By.css(Dashboard.STACKS_BUTTON_CSS)) } diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index cd0dd38a11a..6ced883b93a 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -16,12 +16,10 @@ export class SingleUserLoginPage implements LoginPage { } - login(){ - it("Open dashboard", async () => { - await this.driver - .navigate() - .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") - }) + async login() { + await this.driver + .navigate() + .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") } } \ No newline at end of file diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 0ca11a5e8ec..71124c1f2af 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -14,32 +14,30 @@ const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) -async function doNavigation(): Promise { - await driver.get() - .navigate() - .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") -} -async function waitDashboardButton(): Promise { - await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item")); -} +suite("E2E", async () => { -describe("E2E", async () => { + test("test", async ()=>{ + await loginPage.login() - loginPage.login() + await dashboard.waitPage(100000) + + }) - dashboard.waitPage(100000) - dashboard.clickDashboardButton() + suiteTeardown("close browser", async () => { + + setTimeout(()=>{ + driver.get().quit() + }, 3000) - it("wait dashboard button by until condition", async () => { - await driverHelper.click(By.css("#workspaces-item")) - }) - after("close browser", async () => { - await driver.get().quit() - }) + + }) + }) + + diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 9cd8bd34cfe..5b508302fa6 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -73,27 +73,33 @@ export class DriverHelper { this.driver .wait(until.elementLocated(elementLocator), timeout) .then(webElement => { - resolve(this.driver.wait(until.elementIsVisible(webElement), timeout)) + this.driver.wait(until.elementIsVisible(webElement), timeout) + .then(webElement => { resolve(webElement) }) }) }) } - public async waitAllVisibility(locators: Array, timeout = DriverHelper.DEFAULT_TIMEOUT): Promise { - await locators.forEach(async elementLocator => { - await this.waitVisibility(elementLocator, timeout) - }) - } + // public async waitAllVisibility(locators: Array, timeout = DriverHelper.DEFAULT_TIMEOUT): Promise { + // return new Promise(async resolve => { + // resolve(await locators + // .forEach(async elementLocator => { + // await this.waitVisibility(elementLocator, timeout) + // })) + // }) + // } public async waitDisappearance(elementLocator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { await this.waitDisappearanceBoolean(elementLocator, attempts, polling) .then(isVisible => { + + console.log("===>>> ", isVisible) if (isVisible) { throw new Error(`Waiting attempts exceeded, element '${elementLocator}' is still visible`) } }) } - public async waitAllDisappearance(locators: Array, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING ): Promise { + public async waitAllDisappearance(locators: Array, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { await locators.forEach(async elementLocator => { await this.waitDisappearance(elementLocator, attempts, polling) }) From 269617f43881557b27a3419939579b55d7658524 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 15 Apr 2019 17:57:31 +0300 Subject: [PATCH 08/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 16 +++ typescript-selenium/driver/Driver.ts | 10 ++ typescript-selenium/inversify.config.ts | 8 +- .../pageobjects/dashboard/Dashboard.ts | 36 ++++-- .../pageobjects/dashboard/Workspaces.ts | 95 ++++++++++++++ typescript-selenium/spec.ts | 116 ++++++++++++++++-- typescript-selenium/types.ts | 13 +- typescript-selenium/utils/DriverHelper.ts | 71 ++++++----- 8 files changed, 308 insertions(+), 57 deletions(-) create mode 100644 typescript-selenium/TestConstants.ts create mode 100644 typescript-selenium/pageobjects/dashboard/Workspaces.ts diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts new file mode 100644 index 00000000000..f83a5c13eae --- /dev/null +++ b/typescript-selenium/TestConstants.ts @@ -0,0 +1,16 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +export const TestConstants = { + START_STOP_WORKSPACE_TIMEOUT: 240000, + DEFAULT_TIMEOUT: 20000, + DEFAULT_ATTEMPTS: 5, + DEFAULT_POLLING: 1000 +} \ No newline at end of file diff --git a/typescript-selenium/driver/Driver.ts b/typescript-selenium/driver/Driver.ts index e724428e440..3dceb71553b 100644 --- a/typescript-selenium/driver/Driver.ts +++ b/typescript-selenium/driver/Driver.ts @@ -1,3 +1,13 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + import { ThenableWebDriver } from "selenium-webdriver"; export interface Driver { diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 01c2a6e7c87..fdc3cb21be7 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -1,19 +1,21 @@ import { Container } from "inversify"; import { Driver } from "./driver/Driver"; -import { TYPES } from "./types"; +import { TYPES, CLASSES } from "./types"; import { ChromeDriver } from "./driver/ChromeDriver"; import { DriverHelper } from "./utils/DriverHelper"; import { LoginPage } from "./pageobjects/login/LoginPage"; import { SingleUserLoginPage } from "./pageobjects/login/SingleUserLoginPage"; import { Dashboard } from "./pageobjects/dashboard/Dashboard"; +import { Workspaces } from "./pageobjects/dashboard/Workspaces"; const e2eContainer = new Container(); e2eContainer.bind(TYPES.Driver).to(ChromeDriver).inSingletonScope(); e2eContainer.bind(TYPES.LoginPage).to(SingleUserLoginPage).inSingletonScope(); -e2eContainer.bind('DriverHelper').to(DriverHelper).inSingletonScope(); -e2eContainer.bind('Dashboard').to(Dashboard).inSingletonScope(); +e2eContainer.bind(CLASSES.DriverHelper).to(DriverHelper).inSingletonScope(); +e2eContainer.bind(CLASSES.Dashboard).to(Dashboard).inSingletonScope(); +e2eContainer.bind(CLASSES.Workspaces).to(Workspaces).inSingletonScope(); export { e2eContainer } diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 6ca698d4bf0..8813147e9e1 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -4,6 +4,7 @@ import { TYPES, CLASSES } from "../../types"; import { Driver } from "../../driver/Driver"; import { WebElementCondition, By } from "selenium-webdriver"; import { DriverHelper } from "../../utils/DriverHelper"; +import { TestConstants } from "../../TestConstants"; /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. @@ -31,20 +32,35 @@ export class Dashboard { this.driverHelper = driverHelper; } - async waitPage(timeout: number) { - await this.driverHelper.waitVisibility(By.css(Dashboard.DASHBOARD_BUTTON_CSS)) - await this.driverHelper.waitVisibility(By.css(Dashboard.WORKSPACES_BUTTON_CSS)) - await this.driverHelper.waitVisibility(By.css(Dashboard.STACKS_BUTTON_CSS)) - await this.driverHelper.waitVisibility(By.css(Dashboard.FACTORIES_BUTTON_CSS)) + async waitPage(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) + await this.driverHelper.waitVisibility(By.css(Dashboard.WORKSPACES_BUTTON_CSS), timeout) + await this.driverHelper.waitVisibility(By.css(Dashboard.STACKS_BUTTON_CSS), timeout) + await this.driverHelper.waitVisibility(By.css(Dashboard.FACTORIES_BUTTON_CSS), timeout) } - async clickDashboardButton(timeout = DriverHelper.DEFAULT_TIMEOUT) { - await this.driverHelper.click(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) + async clickDashboardButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) } - async waitLoaderInvisibility() { - await this.driverHelper.waitDisappearance(By.css(Dashboard.STACKS_BUTTON_CSS)) + async clickWorkspacesButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(Dashboard.WORKSPACES_BUTTON_CSS), timeout) } + async clickStacksdButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(Dashboard.STACKS_BUTTON_CSS), timeout) + } + + async clickFactoriesButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(Dashboard.FACTORIES_BUTTON_CSS), timeout) + } + + async waitLoader(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(Dashboard.LOADER_PAGE_CSS), timeout) + } + + async waitLoaderDisappearance(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitDisappearance(By.css(Dashboard.LOADER_PAGE_CSS), timeout) + } -} \ No newline at end of file +} diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/typescript-selenium/pageobjects/dashboard/Workspaces.ts new file mode 100644 index 00000000000..22fa11e99e5 --- /dev/null +++ b/typescript-selenium/pageobjects/dashboard/Workspaces.ts @@ -0,0 +1,95 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { TestConstants } from "../../TestConstants"; +import { injectable, inject } from "inversify"; +import { DriverHelper } from "../../utils/DriverHelper"; +import { CLASSES } from "../../types"; +import { By } from "selenium-webdriver"; + + +@injectable() +export class Workspaces { + private readonly driverHelper: DriverHelper; + private static readonly TITLE: string = ".che-toolbar-title-label"; + private static readonly ADD_WORKSPACE_BUTTON_CSS: string = "#add-item-button"; + private static readonly START_STOP_WORKSPACE_TIMEOUT: number = TestConstants.START_STOP_WORKSPACE_TIMEOUT + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + + + private getWorkspaceListItemLocator(workspaceName: string): string { + return `#ws-name-${workspaceName}` + } + + private getWorkspaceStatusLocator(workspaceName: string, workspaceStatus: string){ + return `#ws-name-${workspaceName}[data-ws-status='${workspaceStatus}']` + } + + async waitPage(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(Workspaces.ADD_WORKSPACE_BUTTON_CSS), timeout) + } + + async clickAddWorkspaceButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(Workspaces.ADD_WORKSPACE_BUTTON_CSS), timeout) + } + + async waitWorkspaceListItem(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const workspaceListItemLocator: By = await By.css(this.getWorkspaceListItemLocator(workspaceName)); + + await this.driverHelper.waitVisibility(workspaceListItemLocator, timeout) + } + + async clickOnStopWorkspaceButton(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const stopWorkspaceButtonLocator: By = By.css(`#ws-name-${workspaceName} .workspace-status[uib-tooltip="Stop workspace"]`) + + await this.driverHelper.waitAndClick(stopWorkspaceButtonLocator, timeout) + } + + async waitWorkspaceWithRunningStatus(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + const runningStatusLocator: By = await this.getWorkspaceStatusLocator(workspaceName, 'RUNNING') + + await this.driverHelper.waitVisibility(runningStatusLocator, timeout) + } + + async waitWorkspaceWithStoppedStatus(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + const stoppedStatusLocator: By = await this.getWorkspaceStatusLocator(workspaceName, 'STOPPED') + + await this.driverHelper.waitVisibility(stoppedStatusLocator, timeout) + } + + async clickWorkspaceListItem(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + const workspaceListItemLocator: By = By.css(`div[id='ws-full-name-che/${workspaceName}']`) + + await this.driverHelper.waitAndClick(workspaceListItemLocator, timeout) + } + + async clickDeleteButtonOnWorkspaceDetails(timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + const deleteButtonOnWorkspaceDetailsLocator: By = By.css("che-button-danger[che-button-title='Delete']") + + await this.driverHelper.waitAndClick(deleteButtonOnWorkspaceDetailsLocator, timeout) + } + + async waitWorkspaceListItemAbcence(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + const workspaceListItemLocator: By = By.css(`div[id='ws-full-name-che/${workspaceName}']`) + + await this.driverHelper.waitDisappearance(workspaceListItemLocator, timeout) + } + + async clickConfirmDeletionButton(timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css('#ok-dialog-button'), timeout) + } + +} diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 71124c1f2af..00862bfd669 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -1,3 +1,13 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + import { e2eContainer } from "./inversify.config"; import { Driver } from "./driver/Driver"; import { TYPES, CLASSES } from "./types"; @@ -6,6 +16,8 @@ import { By, WebElementCondition, Condition } from "selenium-webdriver"; import { describe, after } from "mocha"; import { LoginPage } from "./pageobjects/login/LoginPage"; import { Dashboard } from "./pageobjects/dashboard/Dashboard"; +import { expect, assert } from 'chai' +import { Workspaces } from "./pageobjects/dashboard/Workspaces"; @@ -13,31 +25,113 @@ const driver: Driver = e2eContainer.get(TYPES.Driver); const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) +const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces) suite("E2E", async () => { - test("test", async ()=>{ - await loginPage.login() + suite("Login and wait dashboard", async () => { + test("login", async () => { + await loginPage.login() + }) - await dashboard.waitPage(100000) - + test("wait dashboard", async () => { + await dashboard.waitLoader() + await dashboard.waitLoaderDisappearance() + await dashboard.waitPage() + }) }) + suite("Create workspace and open IDE", async () => { + test("Go to 'New Workspace' page", async () => { + await dashboard.clickWorkspacesButton(); + await workspaces.clickAddWorkspaceButton(); + }) + + - suiteTeardown("close browser", async () => { - - setTimeout(()=>{ - driver.get().quit() - }, 3000) + }) - - }) +}) +suiteTeardown("close browser", async () => { + driver.get().quit() }) + +// suite("Test of 'DriverHelper' methods", async () => { +// test("login", async () => { +// await loginPage.login() +// }) + +// test("waitAllVisibility", async () => { +// await driverHelper.waitAllVisibility([By.css("#dashboard-item"), By.css("#workspaces-item"), By.css("#stacks-item")], 20000) +// }) + +// test("isVisible", async () => { +// const isVisible = await driverHelper.isVisible(By.css("#dashboard-item")) +// expect(isVisible).to.be.true +// }) + +// test("isVisible", async () => { +// const isVisible = await driverHelper.isVisible(By.css("#dashboard-item aaaa")) +// expect(isVisible).to.be.false +// }) + +// test("waitVisibilityBoolean", async () => { +// const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item")) +// expect(isVisible).to.be.true +// }) + +// test("waitVisibilityBoolean", async () => { +// const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item aaaa")) +// expect(isVisible).to.be.false +// }) + +// test("waitDisappearanceBoolean", async () => { +// const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item")) +// expect(isDisappeared).to.be.false +// }) + +// test("waitDisappearanceBoolean", async () => { +// const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item aaaa")) +// expect(isDisappeared).to.be.true +// }) + +// test("waitDisappearance", async () => { +// await driverHelper.waitDisappearance(By.css("#dashboard-item aaaa")) +// }) + +// test("waitDisappearance", async () => { + +// setTimeout(() => { +// driver.get().quit() +// }, 3000) + +// test("click dashboard button", async () => { +// await driverHelper.click(By.css("#dashboard-item")) +// }) + +// test("waitAllDisappearance", async () => { +// await driverHelper.waitAllDisappearance([By.css("#dashboard-item aaa"), By.css("#workspaces-item aaa"), By.css("#stacks-item aaa")], 5, 1000) +// }) + +// test("waitAllDisappearance", async () => { +// await driverHelper.waitAllDisappearance([By.css("#dashboard-item"), By.css("#workspaces-item"), By.css("#stacks-item")], 5, 1000) +// }) + +// suiteTeardown("close browser", async () => { +// setTimeout(() => { +// driver.get().quit() +// }, 3000) +// }) + +// }) + +// }) + diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index 56ccc59e63b..2870b1d1955 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -1,3 +1,13 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + const TYPES = { Driver: Symbol.for("Driver"), LoginPage: Symbol.for("LoginPage") @@ -5,7 +15,8 @@ const TYPES = { const CLASSES = { DriverHelper: "DriverHelper", - Dashboard: "Dashboard" + Dashboard: "Dashboard", + Workspaces: "Workspaces" } export { TYPES, CLASSES }; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 5b508302fa6..dba63d5d453 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -4,6 +4,7 @@ import { TYPES } from "../types"; import 'selenium-webdriver'; import 'reflect-metadata'; import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement } from "selenium-webdriver"; +import { TestConstants } from "../TestConstants"; /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. @@ -17,9 +18,6 @@ import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement } @injectable() export class DriverHelper { - public static readonly DEFAULT_TIMEOUT: number = 20000; - public static readonly DEFAULT_ATTEMPTS: number = 20; - public static readonly DEFAULT_POLLING: number = 1000; private readonly driver: ThenableWebDriver; constructor( @@ -44,9 +42,11 @@ export class DriverHelper { return new promise.Promise(resolve => { setTimeout(resolve, miliseconds) }) } - public async waitVisibilityBoolean(locator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { + public async waitVisibilityBoolean(locator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { for (let i = 0; i < attempts; i++) { - if (await this.isVisible(locator)) { + const isVisible: boolean = await this.isVisible(locator); + + if (isVisible) { return true; } @@ -56,9 +56,11 @@ export class DriverHelper { return false; } - public async waitDisappearanceBoolean(locator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { + public async waitDisappearanceBoolean(locator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { for (let i = 0; i < attempts; i++) { - if (await !this.isVisible(locator)) { + const isVisible: boolean = await this.isVisible(locator) + + if (!isVisible) { return true; } @@ -68,7 +70,7 @@ export class DriverHelper { return false; } - public waitVisibility(elementLocator: By, timeout = DriverHelper.DEFAULT_TIMEOUT): promise.Promise { + public waitVisibility(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): promise.Promise { return new promise.Promise((resolve, reject) => { this.driver .wait(until.elementLocated(elementLocator), timeout) @@ -79,36 +81,41 @@ export class DriverHelper { }) } - // public async waitAllVisibility(locators: Array, timeout = DriverHelper.DEFAULT_TIMEOUT): Promise { - // return new Promise(async resolve => { - // resolve(await locators - // .forEach(async elementLocator => { - // await this.waitVisibility(elementLocator, timeout) - // })) - // }) - // } - - public async waitDisappearance(elementLocator: By, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { - await this.waitDisappearanceBoolean(elementLocator, attempts, polling) - .then(isVisible => { - - console.log("===>>> ", isVisible) - if (isVisible) { - throw new Error(`Waiting attempts exceeded, element '${elementLocator}' is still visible`) - } - }) + public async waitAllVisibility(locators: Array, timeout = TestConstants.DEFAULT_TIMEOUT) { + for (const elementLocator of locators) { + await this.waitVisibility(elementLocator, timeout) + } } - public async waitAllDisappearance(locators: Array, attempts = DriverHelper.DEFAULT_ATTEMPTS, polling = DriverHelper.DEFAULT_POLLING): Promise { - await locators.forEach(async elementLocator => { - await this.waitDisappearance(elementLocator, attempts, polling) - }) + public async waitDisappearance(elementLocator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + const isDisappeared = await this.waitDisappearanceBoolean(elementLocator, attempts, polling) + + if (!isDisappeared) { + throw new Error(`Waiting attempts exceeded, element '${elementLocator}' is still visible`) + } } - public click(elementLocator: By, timeout = DriverHelper.DEFAULT_TIMEOUT): promise.Promise { - return this.waitVisibility(elementLocator, timeout).then(element => { element.click() }) + public async waitAllDisappearance(locators: Array, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { + for (const elementLocator of locators) { + await this.waitDisappearance(elementLocator, attempts, polling) + } } + public async waitAndClick(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { + for (let i = 0; i < 5; i++) { + const element: WebElement = await this.waitVisibility(elementLocator, timeout) + + try { + await element.click(); + return; + } catch (err) { + continue; + } + } + + throw new Error(`Exceeded maximum clicking attempts, the '${elementLocator}' element is not clickable`) + + } From 79b5a0943813ec28aff4a3da6d943caf3c45be7e Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 16 Apr 2019 16:26:12 +0300 Subject: [PATCH 09/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 1 + .../driverHelperUnitTests.spec.ts | 101 ++++++++++++++ typescript-selenium/inversify.config.ts | 13 ++ typescript-selenium/package-lock.json | 6 + typescript-selenium/package.json | 4 +- .../pageobjects/dashboard/NewWorkspace.ts | 110 +++++++++++++++ .../workspace-details/WorkspaceDetails.ts | 121 ++++++++++++++++ .../WorkspaceDetailsPlugins.ts | 60 ++++++++ typescript-selenium/pageobjects/ide/Ide.ts | 132 ++++++++++++++++++ typescript-selenium/spec.ts | 122 +++++++--------- typescript-selenium/tsconfig.json | 2 +- typescript-selenium/types.ts | 8 +- typescript-selenium/utils/DriverHelper.ts | 91 +++++++++++- typescript-selenium/utils/NameGenerator.ts | 26 ++++ typescript-selenium/utils/RequestFactory.ts | 15 ++ .../utils/workspace/TestWorkspaceUtil.ts | 17 +++ 16 files changed, 755 insertions(+), 74 deletions(-) create mode 100644 typescript-selenium/driverHelperUnitTests.spec.ts create mode 100644 typescript-selenium/pageobjects/dashboard/NewWorkspace.ts create mode 100644 typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts create mode 100644 typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts create mode 100644 typescript-selenium/pageobjects/ide/Ide.ts create mode 100644 typescript-selenium/utils/NameGenerator.ts create mode 100644 typescript-selenium/utils/RequestFactory.ts create mode 100644 typescript-selenium/utils/workspace/TestWorkspaceUtil.ts diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index f83a5c13eae..16062fbf47d 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -10,6 +10,7 @@ export const TestConstants = { START_STOP_WORKSPACE_TIMEOUT: 240000, + LOAD_PAGE_TIMEOUT: 120000, DEFAULT_TIMEOUT: 20000, DEFAULT_ATTEMPTS: 5, DEFAULT_POLLING: 1000 diff --git a/typescript-selenium/driverHelperUnitTests.spec.ts b/typescript-selenium/driverHelperUnitTests.spec.ts new file mode 100644 index 00000000000..a7b7a566649 --- /dev/null +++ b/typescript-selenium/driverHelperUnitTests.spec.ts @@ -0,0 +1,101 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { e2eContainer } from "./inversify.config"; +import { Driver } from "./driver/Driver"; +import { TYPES, CLASSES } from "./types"; +import { DriverHelper } from "./utils/DriverHelper"; +import { By, WebElementCondition, Condition } from "selenium-webdriver"; +import { describe, after } from "mocha"; +import { LoginPage } from "./pageobjects/login/LoginPage"; +import { Dashboard } from "./pageobjects/dashboard/Dashboard"; +import { expect, assert } from 'chai' +import { Workspaces } from "./pageobjects/dashboard/Workspaces"; + + + +const driver: Driver = e2eContainer.get(TYPES.Driver); +const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); +const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); +const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) +const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces) + + +suite("Test of 'DriverHelper' methods", async () => { + test("login", async () => { + await loginPage.login() + }) + + test("waitAllVisibility", async () => { + await driverHelper.waitAllVisibility([By.css("#dashboard-item"), By.css("#workspaces-item"), By.css("#stacks-item")], 20000) + }) + + test("isVisible", async () => { + const isVisible = await driverHelper.isVisible(By.css("#dashboard-item")) + expect(isVisible).to.be.true + }) + + test("isVisible", async () => { + const isVisible = await driverHelper.isVisible(By.css("#dashboard-item aaaa")) + expect(isVisible).to.be.false + }) + + test("waitVisibilityBoolean", async () => { + const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item")) + expect(isVisible).to.be.true + }) + + test("waitVisibilityBoolean", async () => { + const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item aaaa")) + expect(isVisible).to.be.false + }) + + test("waitDisappearanceBoolean", async () => { + const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item")) + expect(isDisappeared).to.be.false + }) + + test("waitDisappearanceBoolean", async () => { + const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item aaaa")) + expect(isDisappeared).to.be.true + }) + + test("waitDisappearance", async () => { + await driverHelper.waitDisappearance(By.css("#dashboard-item aaaa")) + }) + + + test("click dashboard button", async () => { + await driverHelper.waitAndClick(By.css("#dashboard-item")) + }) + + test("waitAllDisappearance", async () => { + await driverHelper.waitAllDisappearance([By.css("#dashboard-item aaa"), By.css("#workspaces-item aaa"), By.css("#stacks-item aaa")], 5, 1000) + }) + + //// + + test("getElementAttribute", async () => { + const attributValue: string = await driverHelper.getElementAttribute(By.css("#dashboard-item"), 'id') + expect(attributValue).to.be.equal('dashboard-item') + }) + + test("waitAttributeValue", async ()=>{ + await driverHelper.waitAttributeValue(By.css("#dashboard-item"), 'id', 'dashboard-item') + }) + + + + +}) + +suiteTeardown("close browser", async () => { + driver.get().quit() +}) diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index fdc3cb21be7..58bf109e525 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -7,6 +7,12 @@ import { LoginPage } from "./pageobjects/login/LoginPage"; import { SingleUserLoginPage } from "./pageobjects/login/SingleUserLoginPage"; import { Dashboard } from "./pageobjects/dashboard/Dashboard"; import { Workspaces } from "./pageobjects/dashboard/Workspaces"; +import { NewWorkspace } from "./pageobjects/dashboard/NewWorkspace"; +import { WorkspaceDetails } from "./pageobjects/dashboard/workspace-details/WorkspaceDetails"; +import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; +import { Ide } from "./pageobjects/ide/Ide"; +import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; +import { RequestFactory } from "./utils/RequestFactory"; const e2eContainer = new Container(); @@ -16,6 +22,13 @@ e2eContainer.bind(TYPES.LoginPage).to(SingleUserLoginPage).inSingleto e2eContainer.bind(CLASSES.DriverHelper).to(DriverHelper).inSingletonScope(); e2eContainer.bind(CLASSES.Dashboard).to(Dashboard).inSingletonScope(); e2eContainer.bind(CLASSES.Workspaces).to(Workspaces).inSingletonScope(); +e2eContainer.bind(CLASSES.NewWorkspace).to(NewWorkspace).inSingletonScope(); +e2eContainer.bind(CLASSES.WorkspaceDetails).to(WorkspaceDetails).inSingletonScope(); +e2eContainer.bind(CLASSES.WorkspaceDetailsPlugins).to(WorkspaceDetailsPlugins).inSingletonScope(); +e2eContainer.bind(CLASSES.Ide).to(Ide).inSingletonScope(); +e2eContainer.bind(CLASSES.TestWorkspaceUtil).to(TestWorkspaceUtil).inSingletonScope(); +e2eContainer.bind(CLASSES.RequestFactory).to(RequestFactory).inSingletonScope(); + export { e2eContainer } diff --git a/typescript-selenium/package-lock.json b/typescript-selenium/package-lock.json index 7eb1a782d31..94317b7dba2 100644 --- a/typescript-selenium/package-lock.json +++ b/typescript-selenium/package-lock.json @@ -16,6 +16,12 @@ "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", "dev": true }, + "@types/node": { + "version": "11.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", + "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", + "dev": true + }, "@types/selenium-webdriver": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 2eda97e09a5..b28be377e16 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,13 +4,15 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --require ts-node/register --timeout 150000 -u tdd spec.ts" + "test": "mocha --require ts-node/register --timeout 150000 -u tdd spec.ts", + "units": "mocha --require ts-node/register --timeout 150000 -u tdd driverHelperUnitTests.spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", "license": "ISC", "devDependencies": { "@types/chai": "^4.1.7", "@types/mocha": "^5.2.6", + "@types/node": "^11.13.4", "@types/selenium-webdriver": "^3.0.16", "chai": "^4.2.0", "chromedriver": "^2.46.0", diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts new file mode 100644 index 00000000000..f92b1aadae4 --- /dev/null +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -0,0 +1,110 @@ +import { injectable, inject } from "inversify"; +import { DriverHelper } from "../../utils/DriverHelper"; +import { CLASSES } from "../../types"; +import { TestConstants } from "../../TestConstants"; +import { By } from "selenium-webdriver"; +import 'reflect-metadata'; + + +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +@injectable() +export class NewWorkspace { + private readonly driverHelper: DriverHelper; + + private static readonly CHE_7_STACK_CSS: string = "div[data-stack-id='che7-preview']"; + private static readonly SELECTED_CHE_7_STACK_CSS: string = ".stack-selector-item-selected[data-stack-id='che7-preview']" + private static readonly CREATE_AND_OPEN_BUTTON_XPATH: string = "(//che-button-save-flat[@che-button-title='Create & Open']/button)[1]" + private static readonly CREATE_AND_EDIT_BUTTON_CSS: string = "#dropdown-toggle button[name='dropdown-toggle']" + private static readonly ADD_OR_IMPORT_PROJECT_BUTTON_CSS: string = ".add-import-project-toggle-button"; + private static readonly ADD_BUTTON_CSS: string = "button[aria-disabled='false'][name='addButton']"; + private static readonly NAME_FIELD_CSS: string = "#workspace-name-input"; + + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + async selectCreateWorkspaceAndProceedEditing(timeout = TestConstants.DEFAULT_TIMEOUT) { + const createAndProceedEditingButtonLocator: By = By.xpath("//span[text()='Create & Proceed Editing']") + + // open drop down list + await this.driverHelper.waitAndClick(By.css(NewWorkspace.CREATE_AND_EDIT_BUTTON_CSS), timeout) + + // click on "Create & Proceed Editing" item in the drop down list + await this.driverHelper.waitAndClick(createAndProceedEditingButtonLocator, timeout) + } + + async typeWorkspaceName(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const workspaceNameFieldLocator: By = By.css(NewWorkspace.NAME_FIELD_CSS) + + await this.driverHelper.enterValue(workspaceNameFieldLocator, workspaceName, timeout) + } + + async clickOnChe7Stack(timeout = TestConstants.DEFAULT_TIMEOUT) { + const che7StackLocator: By = By.css(NewWorkspace.CHE_7_STACK_CSS) + + await this.driverHelper.waitAndClick(che7StackLocator) + } + + async waitChe7StackSelected(timeout = TestConstants.DEFAULT_TIMEOUT) { + const che7SelectedStackLocator: By = By.css(NewWorkspace.SELECTED_CHE_7_STACK_CSS) + + await this.driverHelper.waitVisibility(che7SelectedStackLocator, timeout) + } + + async clickOnCreateAndOpenButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + const ideFrameLocator: By = By.xpath("//ide-iframe[@id='ide-iframe-window' and @aria-hidden='false']") + + await this.driverHelper.waitAndClick(By.xpath(NewWorkspace.CREATE_AND_OPEN_BUTTON_XPATH), timeout) + + //check that the workspace has started to boot + await this.driverHelper.waitVisibility(ideFrameLocator, timeout) + } + + async clickOnAddOrImportProjectButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + const addOrImportProjectButtonLocator: By = By.css(NewWorkspace.ADD_OR_IMPORT_PROJECT_BUTTON_CSS) + + await this.driverHelper.waitAndClick(addOrImportProjectButtonLocator, timeout) + } + + async waitSampleCheckboxEnabling(sampleName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const enabledSampleCheckboxLocator: By = By.css(`#sample-${sampleName}>md-checkbox[aria-checked='true']`) + + await this.driverHelper.waitVisibility(enabledSampleCheckboxLocator, timeout) + } + + async enableSampleCheckbox(sampleName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const sampleCheckboxLocator: By = By.xpath(`(//*[@id='sample-${sampleName}']//md-checkbox//div)[1]`) + + await this.driverHelper.waitAndClick(sampleCheckboxLocator, timeout) + await this.waitSampleCheckboxEnabling(sampleName, timeout) + } + + async waitProjectAdding(projectName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const addedProjectLocator: By = By.css(`#project-source-selector toggle-single-button#${projectName}`) + + await this.driverHelper.waitVisibility(addedProjectLocator, timeout) + } + + async waitProjectAbsence(projectName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const addedProjectLocator: By = By.css(`#project-source-selector toggle-single-button#${projectName}`) + + await this.driverHelper.waitDisappearance(addedProjectLocator, timeout) + } + + async clickOnAddButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(NewWorkspace.ADD_BUTTON_CSS), timeout) + } + +} diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts new file mode 100644 index 00000000000..0c567fcb818 --- /dev/null +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -0,0 +1,121 @@ +import { DriverHelper } from "../../../utils/DriverHelper"; +import { injectable, inject } from "inversify"; +import { CLASSES } from "../../../types"; +import 'reflect-metadata'; +import { TestConstants } from "../../../TestConstants"; +import { By } from "selenium-webdriver"; + + +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + + +@injectable() +export class WorkspaceDetails { + + private readonly driverHelper: DriverHelper; + + private static readonly RUN_BUTTON_CSS: string = "#run-workspace-button[che-button-title='Run']"; + private static readonly OPEN_BUTTON_CSS: string = "#open-in-ide-button[che-button-title='Open']"; + private static readonly SELECTED_TAB_BUTTON_XPATH: string = "md-tabs-canvas[role='tablist'] md-tab-item[aria-selected='true']"; + private static readonly SAVE_BUTTON_CSS: string = "button[name='save-button']"; + private static readonly ENABLED_SAVE_BUTTON_CSS: string = "button[name='save-button'][aria-disabled='false']"; + private static readonly WORKSPACE_DETAILS_LOADER_CSS: string = "workspace-details-overview md-progress-linear"; + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + + private getWorkspaceTitleCssLocator(workspaceName: string): string { + return `che-row-toolbar[che-title='${workspaceName}']` + } + + private getTabXpathLocator(tabTitle: string): string { + return `//md-tabs-canvas//md-tab-item//span[text()='${tabTitle}']` + } + + private getSelectedTabXpathLocator(tabTitle: string): string { + return `//md-tabs-canvas[@role='tablist']//md-tab-item[@aria-selected='true']//span[text()='${tabTitle}']` + } + + async waitLoaderDisappearance(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.css(WorkspaceDetails.WORKSPACE_DETAILS_LOADER_CSS), attempts, polling) + } + + async waitSaveButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.ENABLED_SAVE_BUTTON_CSS), timeout) + } + + async waitSaveButtonDisappearance(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.css(WorkspaceDetails.SAVE_BUTTON_CSS), attempts, polling) + } + + async clickOnSaveButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.ENABLED_SAVE_BUTTON_CSS), timeout) + } + + async waitPage(workspaceName: string, timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + await this.waitWorkspaceTitle(workspaceName, timeout); + await this.waitOpenButton(timeout); + await this.waitRunButton(timeout); + await this.waitTabsPresence(timeout); + await this.waitLoaderDisappearance(timeout); + } + + async waitWorkspaceTitle(workspaceName: string, timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + const workspaceTitleLocator: By = By.css(this.getWorkspaceTitleCssLocator(workspaceName)) + + await this.driverHelper.waitVisibility(workspaceTitleLocator, timeout) + } + + async waitRunButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.RUN_BUTTON_CSS), timeout) + } + + async clickOnRunButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.RUN_BUTTON_CSS), timeout) + } + + async waitOpenButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.OPEN_BUTTON_CSS), timeout) + } + + async clickOnOpenButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.OPEN_BUTTON_CSS), timeout) + } + + async waitTabsPresence(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + const workspaceDetailsTabs: Array = ["Overview", "Projects", "Machines", "Installers", "Servers", + "Env Variables", "Volumes", "Config", "SSH", "Tools", "Plugins"]; + + for (const tabTitle of workspaceDetailsTabs) { + const workspaceDetailsTabLocator: By = By.xpath(this.getTabXpathLocator(tabTitle)) + + await this.driverHelper.waitVisibility(workspaceDetailsTabLocator, timeout) + } + } + + async clickOnTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const workspaceDetailsTabLocator: By = By.xpath(this.getTabXpathLocator(tabTitle)) + + + await this.driverHelper.waitAndClick(workspaceDetailsTabLocator, timeout) + } + + async waitTabSelected(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const selectedTabLocator: By = By.xpath(this.getSelectedTabXpathLocator(tabTitle)) + + await this.driverHelper.waitVisibility(selectedTabLocator, timeout) + } + +} diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts new file mode 100644 index 00000000000..62b9f3bda40 --- /dev/null +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -0,0 +1,60 @@ +import { DriverHelper } from "../../../utils/DriverHelper"; +import { injectable, inject } from "inversify"; +import 'reflect-metadata'; +import { CLASSES } from "../../../types"; +import { TestConstants } from "../../../TestConstants"; +import { By } from "selenium-webdriver"; + +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +@injectable() +export class WorkspaceDetailsPlugins { + private readonly driverHelper: DriverHelper; + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + private getPluginListItemCssLocator(pluginName: string): string { + return `.plugin-item div[plugin-item-name='${pluginName}']` + } + + private getPluginListItemSwitcherCssLocator(pluginName: string): string { + return `${this.getPluginListItemCssLocator(pluginName)} md-switch` + } + + async waitPluginListItem(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const pluginListItemLocator: By = By.css(this.getPluginListItemCssLocator(pluginName)) + + await this.driverHelper.waitVisibility(pluginListItemLocator, timeout) + } + + async clickOnPluginListItemSwitcher(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const pluginListItemSwitcherLocator = By.css(this.getPluginListItemSwitcherCssLocator(pluginName)) + + await this.driverHelper.waitAndClick(pluginListItemSwitcherLocator, timeout) + } + + async waitPluginEnabling(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const enabledPluginSwitcherLocator: By = By.css(`${this.getPluginListItemCssLocator(pluginName)} md-switch[aria-checked='true']`) + + await this.driverHelper.waitVisibility(enabledPluginSwitcherLocator, timeout) + } + + async waitPluginDisabling(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const disabledPluginSwitcherLocator: By = By.css(`${this.getPluginListItemCssLocator(pluginName)} md-switch[aria-checked='false']`) + + await this.driverHelper.waitVisibility(disabledPluginSwitcherLocator, timeout) + } + +} diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts new file mode 100644 index 00000000000..1c1fa3c9890 --- /dev/null +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -0,0 +1,132 @@ +import { DriverHelper } from "../../utils/DriverHelper"; +import { injectable, inject } from "inversify"; +import { CLASSES } from "../../types"; +import { TestConstants } from "../../TestConstants"; +import { By } from "selenium-webdriver"; + +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +@injectable() +export class Ide { + + private readonly driverHelper: DriverHelper; + + private static readonly TOP_MENU_PANEL: string = "#theia-app-shell #theia-top-panel .p-MenuBar-content"; + private static readonly LEFT_CONTENT_PANEL: string = "#theia-left-content-panel"; + public static readonly EXPLORER_BUTTON: string = ".theia-app-left .p-TabBar-content li[title='Explorer']"; + private static readonly PRELOADER: string = ".theia-preload"; + private static readonly IDE_IFRAME_CSS: string = "iframe#ide-application-iframe"; + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + async waitAndSwitchToIdeFrame(timeout = TestConstants.LOAD_PAGE_TIMEOUT){ + await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) + } + + async waitNotification(notificationMessage: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const notificationLocator: By = By.css(`div[id='notification-container-3-${notificationMessage}-|']`) + + await this.driverHelper.waitVisibility(notificationLocator, timeout) + } + + async waitNotificationDisappearance(notificationMessage: string, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + const notificationLocator: By = By.css(`div[id='notification-container-3-${notificationMessage}-|']`) + + await this.driverHelper.waitDisappearance(notificationLocator, attempts, polling) + } + + + waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string) { + cy.log("**=> Ide.waitWorkspaceAndIde**") + .then(() => { + this.testWorkspaceUtil.waitWorkspaceRunning(workspaceNamespace, workspaceName) + }) + .then(() => { + [Ide.TOP_MENU_PANEL, Ide.LEFT_CONTENT_PANEL, Ide.EXPLORER_BUTTON] + .forEach(idePart => { + cy.get(idePart, { timeout: Ide.LOAD_PAGE_TIMEOUT }) + .should('be.visible') + }) + }); + } + + waitIde() { + [Ide.TOP_MENU_PANEL, Ide.LEFT_CONTENT_PANEL, Ide.EXPLORER_BUTTON] + .forEach(idePart => { + cy.get(idePart, { timeout: Ide.LOAD_PAGE_TIMEOUT }) + .should('be.visible') + }) + } + + openIdeWithoutFrames(workspaceName: string) { + cy.log("**=> Ide.openIdeWithoutFrames**") + .then(() => { + let workspaceUrl: string = `/che/${workspaceName}` + + cy.visit(workspaceUrl); + }) + } + + waitExplorerButton() { + cy.get(Ide.EXPLORER_BUTTON) + .should('be.visible'); + } + + clickOnExplorerButton() { + cy.get(Ide.EXPLORER_BUTTON) + .first() + .click(); + } + + waitTopMenuPanel() { + cy.get(Ide.TOP_MENU_PANEL) + .should('be.visible'); + } + + waitLeftContentPanel() { + cy.get(Ide.LEFT_CONTENT_PANEL) + .should('be.visible'); + } + + waitPreloaderAbsent() { + cy.get(Ide.PRELOADER) + .should('not.be.visible'); + } + + waitStatusBarContains(expectedText: string) { + cy.get("div[id='theia-statusBar']", { timeout: Ide.LANGUAGE_SERVER_INITIALIZATION_TIMEOUT }) + .should(elem => { + let elementText: string = elem[0].innerText.toString(); + + expect(elementText).contain(expectedText); + }) + + } + + waitStatusBarTextAbcence(expectedText: string) { + cy.get("div[id='theia-statusBar']", { timeout: Ide.LANGUAGE_SERVER_INITIALIZATION_TIMEOUT }) + .should(elem => { + let elementText: string = elem[0].innerText.toString(); + + expect(elementText).not.contain(expectedText); + }) + } + + + + + + +} \ No newline at end of file diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 00862bfd669..2b357cd6af4 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -18,20 +18,31 @@ import { LoginPage } from "./pageobjects/login/LoginPage"; import { Dashboard } from "./pageobjects/dashboard/Dashboard"; import { expect, assert } from 'chai' import { Workspaces } from "./pageobjects/dashboard/Workspaces"; +import { NameGenerator } from "./utils/NameGenerator"; +import { NewWorkspace } from "./pageobjects/dashboard/NewWorkspace"; +import { WorkspaceDetails } from "./pageobjects/dashboard/workspace-details/WorkspaceDetails"; +import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; +import { Request, post, get } from "selenium-webdriver/http"; - +const workspaceName: string = NameGenerator.generate("wksp-test-", 5); +const namespace: string = "che"; +const sampleName: string = "console-java-simple"; const driver: Driver = e2eContainer.get(TYPES.Driver); const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); -const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) -const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces) +const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard); +const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces); +const newWorkspace: NewWorkspace = e2eContainer.get(CLASSES.NewWorkspace); +const workspaceDetails: WorkspaceDetails = e2eContainer.get(CLASSES.WorkspaceDetails); +const workspaceDetailsPlugins: WorkspaceDetailsPlugins = e2eContainer.get(CLASSES.WorkspaceDetailsPlugins) suite("E2E", async () => { suite("Login and wait dashboard", async () => { + test("login", async () => { await loginPage.login() }) @@ -44,94 +55,67 @@ suite("E2E", async () => { }) suite("Create workspace and open IDE", async () => { + test("Go to 'New Workspace' page", async () => { - await dashboard.clickWorkspacesButton(); - await workspaces.clickAddWorkspaceButton(); + await dashboard.clickWorkspacesButton() + await workspaces.clickAddWorkspaceButton() }) + test(`Create a '${workspaceName}' workspace`, async () => { + await newWorkspace.typeWorkspaceName(workspaceName) + await newWorkspace.clickOnChe7Stack() + await newWorkspace.waitChe7StackSelected() + await newWorkspace.clickOnAddOrImportProjectButton() + await newWorkspace.enableSampleCheckbox(sampleName) + await newWorkspace.clickOnAddButton() + await newWorkspace.waitProjectAdding(sampleName) + await newWorkspace.selectCreateWorkspaceAndProceedEditing() + }) - }) - - - -}) - -suiteTeardown("close browser", async () => { - driver.get().quit() -}) - + test("Add 'Java Language Support' plugin to workspace", async () => { + const javaPluginName: string = "Language Support for Java(TM)"; + const execPlugin: string = "Che machine-exec Service"; + await workspaceDetails.waitPage(workspaceName); + await workspaceDetails.waitTabSelected('Overview') + await workspaceDetails.clickOnTab('Plugins') + await workspaceDetails.waitTabSelected('Plugins') -// suite("Test of 'DriverHelper' methods", async () => { -// test("login", async () => { -// await loginPage.login() -// }) + await workspaceDetailsPlugins.waitPluginEnabling(execPlugin) + await workspaceDetailsPlugins.waitPluginDisabling(javaPluginName) + await workspaceDetailsPlugins.clickOnPluginListItemSwitcher(javaPluginName) + await workspaceDetailsPlugins.waitPluginEnabling(javaPluginName) -// test("waitAllVisibility", async () => { -// await driverHelper.waitAllVisibility([By.css("#dashboard-item"), By.css("#workspaces-item"), By.css("#stacks-item")], 20000) -// }) + await workspaceDetails.waitSaveButton() + await workspaceDetails.clickOnSaveButton() + await workspaceDetails.waitSaveButtonDisappearance() -// test("isVisible", async () => { -// const isVisible = await driverHelper.isVisible(By.css("#dashboard-item")) -// expect(isVisible).to.be.true -// }) + await workspaceDetails.clickOnOpenButton() + }) -// test("isVisible", async () => { -// const isVisible = await driverHelper.isVisible(By.css("#dashboard-item aaaa")) -// expect(isVisible).to.be.false -// }) + test("Wait IDE availability", async () => { + ide.waitWorkspaceAndIdeInIframe(namespace, workspaceName); + ide.openIdeWithoutFrames(workspaceName); + ide.waitWorkspaceAndIde(namespace, workspaceName); + }) -// test("waitVisibilityBoolean", async () => { -// const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item")) -// expect(isVisible).to.be.true -// }) -// test("waitVisibilityBoolean", async () => { -// const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item aaaa")) -// expect(isVisible).to.be.false -// }) -// test("waitDisappearanceBoolean", async () => { -// const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item")) -// expect(isDisappeared).to.be.false -// }) -// test("waitDisappearanceBoolean", async () => { -// const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item aaaa")) -// expect(isDisappeared).to.be.true -// }) + }) -// test("waitDisappearance", async () => { -// await driverHelper.waitDisappearance(By.css("#dashboard-item aaaa")) -// }) -// test("waitDisappearance", async () => { -// setTimeout(() => { -// driver.get().quit() -// }, 3000) +}) -// test("click dashboard button", async () => { -// await driverHelper.click(By.css("#dashboard-item")) -// }) +// suiteTeardown("close browser", async () => { +// driver.get().quit() +// }) -// test("waitAllDisappearance", async () => { -// await driverHelper.waitAllDisappearance([By.css("#dashboard-item aaa"), By.css("#workspaces-item aaa"), By.css("#stacks-item aaa")], 5, 1000) -// }) -// test("waitAllDisappearance", async () => { -// await driverHelper.waitAllDisappearance([By.css("#dashboard-item"), By.css("#workspaces-item"), By.css("#stacks-item")], 5, 1000) -// }) -// suiteTeardown("close browser", async () => { -// setTimeout(() => { -// driver.get().quit() -// }, 3000) -// }) -// }) -// }) diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json index 1d324b88b86..aaff4189c3d 100644 --- a/typescript-selenium/tsconfig.json +++ b/typescript-selenium/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "dist", "lib": ["es6", "dom"], - "types": ["reflect-metadata", "@types/mocha"], + "types": ["reflect-metadata", "@types/mocha", "@types/request"], "experimentalDecorators": true, "emitDecoratorMetadata": true diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index 2870b1d1955..a86340f850f 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -16,7 +16,13 @@ const TYPES = { const CLASSES = { DriverHelper: "DriverHelper", Dashboard: "Dashboard", - Workspaces: "Workspaces" + Workspaces: "Workspaces", + NewWorkspace: "NewWorkspace", + WorkspaceDetails: "WorkspaceDetails", + WorkspaceDetailsPlugins: "WorkspaceDetailsPlugins", + Ide: "Ide", + TestWorkspaceUtil: "TestWorkspaceUtil", + RequestFactory: "RequestFactory" } export { TYPES, CLASSES }; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index dba63d5d453..24abd0dc853 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -3,7 +3,7 @@ import { inject, injectable } from "inversify"; import { TYPES } from "../types"; import 'selenium-webdriver'; import 'reflect-metadata'; -import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement } from "selenium-webdriver"; +import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement, WebElementCondition } from "selenium-webdriver"; import { TestConstants } from "../TestConstants"; /********************************************************************* @@ -102,13 +102,17 @@ export class DriverHelper { } public async waitAndClick(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { - for (let i = 0; i < 5; i++) { + const attempts: number = TestConstants.DEFAULT_ATTEMPTS + const polling: number = TestConstants.DEFAULT_POLLING + + for (let i = 0; i < attempts; i++) { const element: WebElement = await this.waitVisibility(elementLocator, timeout) try { await element.click(); return; } catch (err) { + await this.wait(polling) continue; } } @@ -117,6 +121,89 @@ export class DriverHelper { } + public async getElementAttribute(elementLocator: By, attribute: string, visibilityTimeout = TestConstants.DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.DEFAULT_ATTEMPTS + const polling: number = TestConstants.DEFAULT_POLLING + + + for (let i = 0; i < attempts; i++) { + const element: WebElement = await this.waitVisibility(elementLocator, visibilityTimeout); + + try { + return await element.getAttribute(attribute) + } catch (err) { + await this.wait(polling) + continue + } + } + + throw new Error(`Exceeded maximum gettin of the '${attribute}' attribute attempts, from the '${elementLocator}' element`) + } + + public async waitAttributeValue(elementLocator: By, attribute: string, expectedValue: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driver.wait(async () => { + const attributeValue: string = await this.getElementAttribute(elementLocator, attribute, timeout) + + return expectedValue === attributeValue + }, + timeout, + `The '${attribute}' attribute value doesn't match with expected value '${expectedValue}'`) + + } + + public async type(elementLocator: By, text: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const attempts: number = TestConstants.DEFAULT_ATTEMPTS + const polling: number = TestConstants.DEFAULT_POLLING + + + for (let i = 0; i < attempts; i++) { + const element: WebElement = await this.waitVisibility(elementLocator, timeout); + + try { + await element.sendKeys(text) + return + } catch (err) { + await this.wait(polling) + continue + } + } + + throw new Error(`Exceeded maximum typing attempts, to the '${elementLocator}' element`) + } + + public async clear(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { + const attempts: number = TestConstants.DEFAULT_ATTEMPTS + const polling: number = TestConstants.DEFAULT_POLLING + + + for (let i = 0; i < attempts; i++) { + const element: WebElement = await this.waitVisibility(elementLocator, timeout); + + try { + await element.clear() + return + } catch (err) { + await this.wait(polling) + continue + } + } + + throw new Error(`Exceeded maximum clearing attempts, to the '${elementLocator}' element`) + } + + public async enterValue(elementLocator: By, text: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.waitVisibility(elementLocator, timeout) + await this.clear(elementLocator, timeout) + await this.waitAttributeValue(elementLocator, "value", "", timeout) + await this.type(elementLocator, text, timeout) + await this.waitAttributeValue(elementLocator, "value", text, timeout) + } + + public async waitAndSwitchToFrame(iframeLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT){ + this.driver.wait(until.ableToSwitchToFrame(iframeLocator), timeout) + } + + diff --git a/typescript-selenium/utils/NameGenerator.ts b/typescript-selenium/utils/NameGenerator.ts new file mode 100644 index 00000000000..8c8d099f2c1 --- /dev/null +++ b/typescript-selenium/utils/NameGenerator.ts @@ -0,0 +1,26 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +export class NameGenerator { + public static generate(prefix: string, randomLength: number): string { + let possibleCharacters: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + let i: number; + let randomPart: string = ""; + + for (i = 0; i < randomLength; i++) { + let currentRandomIndex: number = Math.floor(Math.random() * Math.floor(52)); + + randomPart += possibleCharacters[currentRandomIndex]; + } + + return prefix + randomPart; + } + +} diff --git a/typescript-selenium/utils/RequestFactory.ts b/typescript-selenium/utils/RequestFactory.ts new file mode 100644 index 00000000000..90070297943 --- /dev/null +++ b/typescript-selenium/utils/RequestFactory.ts @@ -0,0 +1,15 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +export class RequestFactory { + + + +} \ No newline at end of file diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts new file mode 100644 index 00000000000..bd76628b577 --- /dev/null +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -0,0 +1,17 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + + +export class TestWorkspaceUtil { + + + + +} \ No newline at end of file From aa98cff178bbf918abe4d506d0f146a7308d171d Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Wed, 17 Apr 2019 15:56:59 +0300 Subject: [PATCH 10/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 10 +- .../driverHelperUnitTests.spec.ts | 2 +- typescript-selenium/inversify.config.ts | 2 - typescript-selenium/package-lock.json | 226 +++++++++++------- typescript-selenium/package.json | 9 +- .../workspace-details/WorkspaceDetails.ts | 2 +- typescript-selenium/pageobjects/ide/Ide.ts | 110 ++++----- typescript-selenium/spec.ts | 45 ++-- typescript-selenium/tsconfig.json | 2 +- typescript-selenium/types.ts | 3 +- typescript-selenium/utils/DriverHelper.ts | 68 ++++-- typescript-selenium/utils/RequestFactory.ts | 15 -- .../utils/workspace/TestWorkspaceUtil.ts | 60 ++++- 13 files changed, 347 insertions(+), 207 deletions(-) delete mode 100644 typescript-selenium/utils/RequestFactory.ts diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 16062fbf47d..325db0fda88 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -11,7 +11,15 @@ export const TestConstants = { START_STOP_WORKSPACE_TIMEOUT: 240000, LOAD_PAGE_TIMEOUT: 120000, + LANGUAGE_SERVER_INITIALIZATION_TIMEOUT: 180000, + DEFAULT_TIMEOUT: 20000, DEFAULT_ATTEMPTS: 5, - DEFAULT_POLLING: 1000 + DEFAULT_POLLING: 1000, + + WORKSPACE_STATUS_ATTEMPTS: 90, + WORKSPACE_STATUS_POLLING: 10000, + + + BASE_URL: "http://che-che.192.168.99.100.nip.io" } \ No newline at end of file diff --git a/typescript-selenium/driverHelperUnitTests.spec.ts b/typescript-selenium/driverHelperUnitTests.spec.ts index a7b7a566649..d268a41596a 100644 --- a/typescript-selenium/driverHelperUnitTests.spec.ts +++ b/typescript-selenium/driverHelperUnitTests.spec.ts @@ -83,7 +83,7 @@ suite("Test of 'DriverHelper' methods", async () => { //// test("getElementAttribute", async () => { - const attributValue: string = await driverHelper.getElementAttribute(By.css("#dashboard-item"), 'id') + const attributValue: string = await driverHelper.waitAndGetElementAttribute(By.css("#dashboard-item"), 'id') expect(attributValue).to.be.equal('dashboard-item') }) diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 58bf109e525..6ad647bbca7 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -12,7 +12,6 @@ import { WorkspaceDetails } from "./pageobjects/dashboard/workspace-details/Work import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; import { Ide } from "./pageobjects/ide/Ide"; import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; -import { RequestFactory } from "./utils/RequestFactory"; const e2eContainer = new Container(); @@ -27,7 +26,6 @@ e2eContainer.bind(CLASSES.WorkspaceDetails).to(WorkspaceDetail e2eContainer.bind(CLASSES.WorkspaceDetailsPlugins).to(WorkspaceDetailsPlugins).inSingletonScope(); e2eContainer.bind(CLASSES.Ide).to(Ide).inSingletonScope(); e2eContainer.bind(CLASSES.TestWorkspaceUtil).to(TestWorkspaceUtil).inSingletonScope(); -e2eContainer.bind(CLASSES.RequestFactory).to(RequestFactory).inSingletonScope(); diff --git a/typescript-selenium/package-lock.json b/typescript-selenium/package-lock.json index 94317b7dba2..7298c23a6f1 100644 --- a/typescript-selenium/package-lock.json +++ b/typescript-selenium/package-lock.json @@ -4,12 +4,30 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/bluebird": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.26.tgz", + "integrity": "sha512-aj2mrBLn5ky0GmAg6IPXrQjnN0iB/ulozuJ+oZdrHRAzRbXjGmu4UXsNCjFvPbSaaPZmniocdOzsM392qLOlmQ==" + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", "dev": true }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "*" + } + }, "@types/mocha": { "version": "5.2.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", @@ -19,8 +37,35 @@ "@types/node": { "version": "11.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", - "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", - "dev": true + "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==" + }, + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/request-promise": { + "version": "4.1.42", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.42.tgz", + "integrity": "sha512-b8li55sEZ00BXZstZ3d8WOi48dnapTqB1VufEG9Qox0nVI2JVnTVT1Mw4JbBa1j+1sGVX/qJ0R4WDv4v2GjT0w==", + "requires": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, + "@types/request-promise-native": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.15.tgz", + "integrity": "sha512-uYPjTChD9TpjlvbBjNpZfNc64TBejBS52u7pbxhQLnlxw+5Em7wLb6DU2wdJVhJ2Mou7v50N0qgL4Gia5mmRYg==", + "requires": { + "@types/request": "*" + } }, "@types/selenium-webdriver": { "version": "3.0.16", @@ -28,11 +73,15 @@ "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", "dev": true }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -95,7 +144,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -103,8 +151,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -115,20 +162,17 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "balanced-match": { "version": "1.0.0", @@ -140,11 +184,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, + "bluebird": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -176,8 +224,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { "version": "4.2.0", @@ -270,7 +317,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -296,8 +342,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cross-spawn": { "version": "6.0.5", @@ -316,7 +361,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -377,8 +421,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "diff": { "version": "3.5.0", @@ -390,7 +433,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -466,8 +508,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extract-zip": { "version": "1.6.7", @@ -484,20 +525,17 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fd-slicer": { "version": "1.0.1", @@ -529,14 +567,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -580,7 +616,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -629,14 +664,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -673,7 +706,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -794,8 +826,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-url": { "version": "1.2.4", @@ -829,8 +860,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "js-yaml": { "version": "3.13.0", @@ -845,32 +875,27 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -921,8 +946,7 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "log-symbols": { "version": "2.2.0", @@ -962,14 +986,12 @@ "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" }, "mime-types": { "version": "2.1.22", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, "requires": { "mime-db": "~1.38.0" } @@ -1092,8 +1114,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -1248,8 +1269,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "3.0.0", @@ -1281,8 +1301,7 @@ "psl": { "version": "1.1.31", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", - "dev": true + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, "pump": { "version": "3.0.0", @@ -1297,14 +1316,12 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "readable-stream": { "version": "2.3.6", @@ -1330,7 +1347,6 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -1354,6 +1370,35 @@ "uuid": "^3.3.2" } }, + "request-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", + "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1378,14 +1423,12 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.2.4", @@ -1470,7 +1513,6 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -1483,6 +1525,11 @@ "tweetnacl": "~0.14.0" } }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1572,7 +1619,6 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -1581,8 +1627,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, @@ -1599,11 +1644,16 @@ "yn": "^3.0.0" } }, + "tunnel": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", + "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -1611,8 +1661,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-detect": { "version": "4.0.8", @@ -1620,6 +1669,16 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "typed-rest-client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", + "integrity": "sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw==", + "dev": true, + "requires": { + "tunnel": "0.0.4", + "underscore": "1.8.3" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -1632,11 +1691,16 @@ "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -1650,14 +1714,12 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index b28be377e16..261966022a8 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -19,10 +19,17 @@ "mocha": "^6.1.3", "selenium-webdriver": "^3.6.0", "ts-node": "^8.0.3", + "typed-rest-client": "^1.2.0", "typescript": "^3.4.3" }, "dependencies": { + "@types/request": "^2.48.1", + "@types/request-promise": "^4.1.42", + "@types/request-promise-native": "^1.0.15", "inversify": "^5.0.1", - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "request": "^2.88.0", + "request-promise": "^4.2.4", + "request-promise-native": "^1.0.7" } } diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index 0c567fcb818..c70fd6792bc 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -95,7 +95,7 @@ export class WorkspaceDetails { } async waitTabsPresence(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { - const workspaceDetailsTabs: Array = ["Overview", "Projects", "Machines", "Installers", "Servers", + const workspaceDetailsTabs: Array = ["Overview", "Projects", "Containers", "Servers", "Env Variables", "Volumes", "Config", "SSH", "Tools", "Plugins"]; for (const tabTitle of workspaceDetailsTabs) { diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index 1c1fa3c9890..45827679524 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -3,6 +3,7 @@ import { injectable, inject } from "inversify"; import { CLASSES } from "../../types"; import { TestConstants } from "../../TestConstants"; import { By } from "selenium-webdriver"; +import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. @@ -18,20 +19,23 @@ import { By } from "selenium-webdriver"; export class Ide { private readonly driverHelper: DriverHelper; + private readonly testWorkspaceUtil: TestWorkspaceUtil; - private static readonly TOP_MENU_PANEL: string = "#theia-app-shell #theia-top-panel .p-MenuBar-content"; - private static readonly LEFT_CONTENT_PANEL: string = "#theia-left-content-panel"; - public static readonly EXPLORER_BUTTON: string = ".theia-app-left .p-TabBar-content li[title='Explorer']"; - private static readonly PRELOADER: string = ".theia-preload"; + private static readonly TOP_MENU_PANEL_CSS: string = "#theia-app-shell #theia-top-panel .p-MenuBar-content"; + private static readonly LEFT_CONTENT_PANEL_CSS: string = "#theia-left-content-panel"; + public static readonly EXPLORER_BUTTON_XPATH: string = "(//*[@id='theia-left-content-panel']//ul[@class='p-TabBar-content']//li[@title='Explorer'])[1]"; + private static readonly PRELOADER_CSS: string = ".theia-preload"; private static readonly IDE_IFRAME_CSS: string = "iframe#ide-application-iframe"; constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper, + @inject(CLASSES.TestWorkspaceUtil) testWorkspaceUtil: TestWorkspaceUtil ) { this.driverHelper = driverHelper; + this.testWorkspaceUtil = testWorkspaceUtil; } - async waitAndSwitchToIdeFrame(timeout = TestConstants.LOAD_PAGE_TIMEOUT){ + async waitAndSwitchToIdeFrame(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) } @@ -47,86 +51,64 @@ export class Ide { await this.driverHelper.waitDisappearance(notificationLocator, attempts, polling) } - - waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string) { - cy.log("**=> Ide.waitWorkspaceAndIde**") - .then(() => { - this.testWorkspaceUtil.waitWorkspaceRunning(workspaceNamespace, workspaceName) - }) - .then(() => { - [Ide.TOP_MENU_PANEL, Ide.LEFT_CONTENT_PANEL, Ide.EXPLORER_BUTTON] - .forEach(idePart => { - cy.get(idePart, { timeout: Ide.LOAD_PAGE_TIMEOUT }) - .should('be.visible') - }) - }); - } - - waitIde() { - [Ide.TOP_MENU_PANEL, Ide.LEFT_CONTENT_PANEL, Ide.EXPLORER_BUTTON] - .forEach(idePart => { - cy.get(idePart, { timeout: Ide.LOAD_PAGE_TIMEOUT }) - .should('be.visible') - }) + async waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string, timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + await this.testWorkspaceUtil.waitRunningStatus(workspaceNamespace, workspaceName) + await this.waitIde(timeout) } - openIdeWithoutFrames(workspaceName: string) { - cy.log("**=> Ide.openIdeWithoutFrames**") - .then(() => { - let workspaceUrl: string = `/che/${workspaceName}` + async waitIde(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + const mainIdeParts: Array = [By.css(Ide.TOP_MENU_PANEL_CSS), By.css(Ide.LEFT_CONTENT_PANEL_CSS), By.xpath(Ide.EXPLORER_BUTTON_XPATH)] - cy.visit(workspaceUrl); - }) + for (const idePartLocator of mainIdeParts) { + await this.driverHelper.waitVisibility(idePartLocator, timeout) + } } - waitExplorerButton() { - cy.get(Ide.EXPLORER_BUTTON) - .should('be.visible'); + async waitExplorerButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.xpath(Ide.EXPLORER_BUTTON_XPATH), timeout) } - clickOnExplorerButton() { - cy.get(Ide.EXPLORER_BUTTON) - .first() - .click(); + async clickOnExplorerButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.xpath(Ide.EXPLORER_BUTTON_XPATH), timeout) } - waitTopMenuPanel() { - cy.get(Ide.TOP_MENU_PANEL) - .should('be.visible'); + async waitTopMenuPanel(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(Ide.TOP_MENU_PANEL_CSS), timeout) } - waitLeftContentPanel() { - cy.get(Ide.LEFT_CONTENT_PANEL) - .should('be.visible'); + async waitLeftContentPanel(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(Ide.LEFT_CONTENT_PANEL_CSS)) } - waitPreloaderAbsent() { - cy.get(Ide.PRELOADER) - .should('not.be.visible'); + async waitPreloaderAbsent(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.css(Ide.PRELOADER_CSS), attempts, polling) } - waitStatusBarContains(expectedText: string) { - cy.get("div[id='theia-statusBar']", { timeout: Ide.LANGUAGE_SERVER_INITIALIZATION_TIMEOUT }) - .should(elem => { - let elementText: string = elem[0].innerText.toString(); - - expect(elementText).contain(expectedText); - }) + async waitStatusBarContains(expectedText: string, timeout = TestConstants.LANGUAGE_SERVER_INITIALIZATION_TIMEOUT) { + const statusBarLocator: By = By.css("div[id='theia-statusBar']") - } + await this.driverHelper.waitUntilTrue(async () => { + const elementText: string = await this.driverHelper.waitAndGetText(statusBarLocator, timeout) - waitStatusBarTextAbcence(expectedText: string) { - cy.get("div[id='theia-statusBar']", { timeout: Ide.LANGUAGE_SERVER_INITIALIZATION_TIMEOUT }) - .should(elem => { - let elementText: string = elem[0].innerText.toString(); + return elementText.search(expectedText) > 0 - expect(elementText).not.contain(expectedText); - }) + }, timeout) } + async waitStatusBarTextAbcence(expectedText: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const statusBarLocator: By = By.css("div[id='theia-statusBar']") + + await this.driverHelper.waitUntilTrue(async () => { + const elementText: string = await this.driverHelper.waitAndGetText(statusBarLocator, timeout) + return elementText.search(expectedText) === 0 + }, timeout) + } + async waitIdeFrameAndSwitchOnIt(timeout = TestConstants.LOAD_PAGE_TIMEOUT){ + await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) + } -} \ No newline at end of file +} diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 2b357cd6af4..f47535b7f8c 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -23,6 +23,8 @@ import { NewWorkspace } from "./pageobjects/dashboard/NewWorkspace"; import { WorkspaceDetails } from "./pageobjects/dashboard/workspace-details/WorkspaceDetails"; import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; import { Request, post, get } from "selenium-webdriver/http"; +import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; +import { Ide } from "./pageobjects/ide/Ide"; const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; @@ -36,13 +38,15 @@ const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces); const newWorkspace: NewWorkspace = e2eContainer.get(CLASSES.NewWorkspace); const workspaceDetails: WorkspaceDetails = e2eContainer.get(CLASSES.WorkspaceDetails); const workspaceDetailsPlugins: WorkspaceDetailsPlugins = e2eContainer.get(CLASSES.WorkspaceDetailsPlugins) +const testWorkspaceUtil: TestWorkspaceUtil = e2eContainer.get(CLASSES.TestWorkspaceUtil) +const ide: Ide = e2eContainer.get(CLASSES.Ide) + suite("E2E", async () => { suite("Login and wait dashboard", async () => { - test("login", async () => { await loginPage.login() }) @@ -70,35 +74,34 @@ suite("E2E", async () => { await newWorkspace.clickOnAddButton() await newWorkspace.waitProjectAdding(sampleName) - await newWorkspace.selectCreateWorkspaceAndProceedEditing() + await newWorkspace.clickOnCreateAndOpenButton() }) - test("Add 'Java Language Support' plugin to workspace", async () => { - const javaPluginName: string = "Language Support for Java(TM)"; - const execPlugin: string = "Che machine-exec Service"; + // test("Add 'Java Language Support' plugin to workspace", async () => { + // const javaPluginName: string = "Language Support for Java(TM)"; + // const execPlugin: string = "Che machine-exec Service"; - await workspaceDetails.waitPage(workspaceName); - await workspaceDetails.waitTabSelected('Overview') - await workspaceDetails.clickOnTab('Plugins') - await workspaceDetails.waitTabSelected('Plugins') + // await workspaceDetails.waitPage(workspaceName); + // await workspaceDetails.waitTabSelected('Overview') + // await workspaceDetails.clickOnTab('Plugins') + // await workspaceDetails.waitTabSelected('Plugins') - await workspaceDetailsPlugins.waitPluginEnabling(execPlugin) - await workspaceDetailsPlugins.waitPluginDisabling(javaPluginName) - await workspaceDetailsPlugins.clickOnPluginListItemSwitcher(javaPluginName) - await workspaceDetailsPlugins.waitPluginEnabling(javaPluginName) + // await workspaceDetailsPlugins.waitPluginEnabling(execPlugin) + // await workspaceDetailsPlugins.waitPluginDisabling(javaPluginName) + // await workspaceDetailsPlugins.clickOnPluginListItemSwitcher(javaPluginName) + // await workspaceDetailsPlugins.waitPluginEnabling(javaPluginName) - await workspaceDetails.waitSaveButton() - await workspaceDetails.clickOnSaveButton() - await workspaceDetails.waitSaveButtonDisappearance() + // await workspaceDetails.waitSaveButton() + // await workspaceDetails.clickOnSaveButton() + // await workspaceDetails.waitSaveButtonDisappearance() - await workspaceDetails.clickOnOpenButton() - }) + // await workspaceDetails.clickOnOpenButton() + // }) test("Wait IDE availability", async () => { - ide.waitWorkspaceAndIdeInIframe(namespace, workspaceName); - ide.openIdeWithoutFrames(workspaceName); - ide.waitWorkspaceAndIde(namespace, workspaceName); + await ide.waitAndSwitchToIdeFrame() + await ide.waitWorkspaceAndIde(namespace, workspaceName); }) diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json index aaff4189c3d..b43a4f2181d 100644 --- a/typescript-selenium/tsconfig.json +++ b/typescript-selenium/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "dist", "lib": ["es6", "dom"], - "types": ["reflect-metadata", "@types/mocha", "@types/request"], + "types": ["reflect-metadata", "@types/mocha", "@types/request-promise-native", "@types/request", "@types/request-promise"], "experimentalDecorators": true, "emitDecoratorMetadata": true diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index a86340f850f..02d31c79a1a 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -21,8 +21,7 @@ const CLASSES = { WorkspaceDetails: "WorkspaceDetails", WorkspaceDetailsPlugins: "WorkspaceDetailsPlugins", Ide: "Ide", - TestWorkspaceUtil: "TestWorkspaceUtil", - RequestFactory: "RequestFactory" + TestWorkspaceUtil: "TestWorkspaceUtil" } export { TYPES, CLASSES }; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 24abd0dc853..7ee1b940936 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -1,7 +1,7 @@ import { Driver } from "../driver/Driver"; import { inject, injectable } from "inversify"; import { TYPES } from "../types"; -import 'selenium-webdriver'; +import { error } from 'selenium-webdriver'; import 'reflect-metadata'; import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement, WebElementCondition } from "selenium-webdriver"; import { TestConstants } from "../TestConstants"; @@ -70,15 +70,29 @@ export class DriverHelper { return false; } - public waitVisibility(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): promise.Promise { - return new promise.Promise((resolve, reject) => { - this.driver - .wait(until.elementLocated(elementLocator), timeout) - .then(webElement => { - this.driver.wait(until.elementIsVisible(webElement), timeout) - .then(webElement => { resolve(webElement) }) - }) - }) + public async waitVisibility(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.DEFAULT_ATTEMPTS + const polling: number = TestConstants.DEFAULT_POLLING + + for (let i = 0; i < attempts; i++) { + const webElement: WebElement = await this.driver.wait(until.elementLocated(elementLocator), timeout) + + try { + return await this.driver.wait(until.elementIsVisible(webElement), timeout) + } catch (err) { + if (err instanceof error.StaleElementReferenceError) { + + console.log("==>>> 'waitVisibility' catched exception") + + await this.wait(polling) + continue; + } + + throw err; + } + } + + throw new Error(`Exceeded maximum visibility checkings attempts, problems with 'StaleElementReferenceError' of '${elementLocator}' element`) } public async waitAllVisibility(locators: Array, timeout = TestConstants.DEFAULT_TIMEOUT) { @@ -121,7 +135,7 @@ export class DriverHelper { } - public async getElementAttribute(elementLocator: By, attribute: string, visibilityTimeout = TestConstants.DEFAULT_TIMEOUT): Promise { + public async waitAndGetElementAttribute(elementLocator: By, attribute: string, visibilityTimeout = TestConstants.DEFAULT_TIMEOUT): Promise { const attempts: number = TestConstants.DEFAULT_ATTEMPTS const polling: number = TestConstants.DEFAULT_POLLING @@ -142,7 +156,7 @@ export class DriverHelper { public async waitAttributeValue(elementLocator: By, attribute: string, expectedValue: string, timeout = TestConstants.DEFAULT_TIMEOUT) { await this.driver.wait(async () => { - const attributeValue: string = await this.getElementAttribute(elementLocator, attribute, timeout) + const attributeValue: string = await this.waitAndGetElementAttribute(elementLocator, attribute, timeout) return expectedValue === attributeValue }, @@ -199,13 +213,39 @@ export class DriverHelper { await this.waitAttributeValue(elementLocator, "value", text, timeout) } - public async waitAndSwitchToFrame(iframeLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT){ + public async waitAndSwitchToFrame(iframeLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { this.driver.wait(until.ableToSwitchToFrame(iframeLocator), timeout) } + public async waitAndGetText(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.DEFAULT_ATTEMPTS + const polling: number = TestConstants.DEFAULT_POLLING + for (let i = 0; i < attempts; i++) { + const element: WebElement = await this.waitVisibility(elementLocator, timeout); + + try { + const innerText: string = await element.getText() + return innerText + } catch (err) { + await this.wait(polling) + continue + } + } + + throw new Error(`Exceeded maximum text obtaining attempts, from the '${elementLocator}' element`) + } + + public async waitAndGetValue(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): Promise { + const elementValue: string = await this.waitAndGetElementAttribute(elementLocator, 'value', timeout) + return elementValue + } + + public async waitUntilTrue(callback: any, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driver.wait(callback(), timeout) + } -} \ No newline at end of file +} diff --git a/typescript-selenium/utils/RequestFactory.ts b/typescript-selenium/utils/RequestFactory.ts deleted file mode 100644 index 90070297943..00000000000 --- a/typescript-selenium/utils/RequestFactory.ts +++ /dev/null @@ -1,15 +0,0 @@ -/********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - **********************************************************************/ - -export class RequestFactory { - - - -} \ No newline at end of file diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index bd76628b577..8e3fe317630 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -7,11 +7,67 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +// import * as request from 'request' +import { TestConstants } from '../../TestConstants'; +import { injectable, inject } from 'inversify'; +import { DriverHelper } from '../DriverHelper'; +import { CLASSES } from '../../types'; +import 'reflect-metadata'; +import * as rm from 'typed-rest-client/RestClient' + +@injectable() export class TestWorkspaceUtil { + private readonly driverHelper: DriverHelper; + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper + ) { + this.driverHelper = driverHelper; + } + + public async waitRunningStatus(workspaceNamespace: string, workspaceName: string) { + const workspaceStatusApiUrl: string = `${TestConstants.BASE_URL}/api/workspace/${workspaceNamespace}:${workspaceName}`; + const attempts: number = TestConstants.WORKSPACE_STATUS_ATTEMPTS; + const polling: number = TestConstants.WORKSPACE_STATUS_POLLING; + const runningWorkspaceStatus: string = 'RUNNING'; + const stoppedWorkspaceStatus: string = 'STOPPED'; + const startingWorkspaceStatus: string = 'STARTING'; + + const rest: rm.RestClient = new rm.RestClient('rest-samples') + + for (let i = 0; i < attempts; i++) { + let isWorkspaceStarting: boolean = false; + + console.log("===>>> waitRunningStatus: ", i) + + + const response: rm.IRestResponse = await rest.get(workspaceStatusApiUrl) + + if (response.statusCode !== 200) { + await this.driverHelper.wait(polling) + continue + } + + const workspaceStatus: string = await response.result.status + + if (workspaceStatus === runningWorkspaceStatus) { + return; + } + + if (workspaceStatus === startingWorkspaceStatus) { + isWorkspaceStarting = true; + } + + if ((workspaceStatus === stoppedWorkspaceStatus) && isWorkspaceStarting) { + throw new Error("Workspace starting process is crushed") + } - + await this.driverHelper.wait(polling) + } + throw new Error('Exceeded the maximum number of checking attempts, workspace has not been run') + } -} \ No newline at end of file +} From 15853ecb1ad7a7965e097fd25de04916930975ed Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Wed, 17 Apr 2019 18:13:46 +0300 Subject: [PATCH 11/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- .../cypress/pageobjects/ide/ProjectTree.ts | 16 +- typescript-selenium/inversify.config.ts | 2 + typescript-selenium/package-lock.json | 204 +++++++----------- typescript-selenium/package.json | 8 +- typescript-selenium/pageobjects/ide/Ide.ts | 3 +- typescript-selenium/spec.ts | 72 +++++-- typescript-selenium/tsconfig.json | 2 +- typescript-selenium/types.ts | 3 +- typescript-selenium/utils/DriverHelper.ts | 4 + .../utils/workspace/TestWorkspaceUtil.ts | 11 +- 10 files changed, 158 insertions(+), 167 deletions(-) diff --git a/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts b/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts index 2de63f422e2..e121c4427d7 100644 --- a/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts +++ b/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts @@ -32,6 +32,14 @@ export class ProjectTree { return `${this.getExpandIconLocator(itemPath)}:not(.theia-mod-collapsed)`; } + private getExpandIconLocator(itemPath: string) { + return `div[data-node-id='/projects:/projects${itemPath}']`; + } + + private getTreeItemLocator(itemPath: string) { + return `.theia-TreeNode[title='/projects${itemPath}']` + } + openProjectTreeContainer() { cy.get(Ide.EXPLORER_BUTTON) .should('be.visible') @@ -92,14 +100,6 @@ export class ProjectTree { cy.get(selectedItemLocator).should('be.visible'); } - private getExpandIconLocator(itemPath: string) { - return `div[data-node-id='/projects:/projects${itemPath}']`; - } - - private getTreeItemLocator(itemPath: string) { - return `.theia-TreeNode[title='/projects${itemPath}']` - } - expandItem(itemPath: string) { let expandIconLocator: string = this.getExpandIconLocator(itemPath); let treeItemLocator: string = this.getTreeItemLocator(itemPath); diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 6ad647bbca7..6bd7d751f15 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -12,6 +12,7 @@ import { WorkspaceDetails } from "./pageobjects/dashboard/workspace-details/Work import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; import { Ide } from "./pageobjects/ide/Ide"; import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; +import { ProjectTree } from "./pageobjects/ide/ProjectTree"; const e2eContainer = new Container(); @@ -26,6 +27,7 @@ e2eContainer.bind(CLASSES.WorkspaceDetails).to(WorkspaceDetail e2eContainer.bind(CLASSES.WorkspaceDetailsPlugins).to(WorkspaceDetailsPlugins).inSingletonScope(); e2eContainer.bind(CLASSES.Ide).to(Ide).inSingletonScope(); e2eContainer.bind(CLASSES.TestWorkspaceUtil).to(TestWorkspaceUtil).inSingletonScope(); +e2eContainer.bind(CLASSES.ProjectTree).to(ProjectTree).inSingletonScope(); diff --git a/typescript-selenium/package-lock.json b/typescript-selenium/package-lock.json index 7298c23a6f1..7052b4a7b66 100644 --- a/typescript-selenium/package-lock.json +++ b/typescript-selenium/package-lock.json @@ -4,30 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/bluebird": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.26.tgz", - "integrity": "sha512-aj2mrBLn5ky0GmAg6IPXrQjnN0iB/ulozuJ+oZdrHRAzRbXjGmu4UXsNCjFvPbSaaPZmniocdOzsM392qLOlmQ==" - }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" - }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", "dev": true }, - "@types/form-data": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", - "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", - "requires": { - "@types/node": "*" - } - }, "@types/mocha": { "version": "5.2.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", @@ -37,35 +19,8 @@ "@types/node": { "version": "11.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", - "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==" - }, - "@types/request": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", - "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", - "requires": { - "@types/caseless": "*", - "@types/form-data": "*", - "@types/node": "*", - "@types/tough-cookie": "*" - } - }, - "@types/request-promise": { - "version": "4.1.42", - "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.42.tgz", - "integrity": "sha512-b8li55sEZ00BXZstZ3d8WOi48dnapTqB1VufEG9Qox0nVI2JVnTVT1Mw4JbBa1j+1sGVX/qJ0R4WDv4v2GjT0w==", - "requires": { - "@types/bluebird": "*", - "@types/request": "*" - } - }, - "@types/request-promise-native": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.15.tgz", - "integrity": "sha512-uYPjTChD9TpjlvbBjNpZfNc64TBejBS52u7pbxhQLnlxw+5Em7wLb6DU2wdJVhJ2Mou7v50N0qgL4Gia5mmRYg==", - "requires": { - "@types/request": "*" - } + "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", + "dev": true }, "@types/selenium-webdriver": { "version": "3.0.16", @@ -73,15 +28,11 @@ "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", "dev": true }, - "@types/tough-cookie": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", - "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" - }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -144,6 +95,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -151,7 +103,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assertion-error": { "version": "1.1.0", @@ -162,17 +115,20 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true }, "balanced-match": { "version": "1.0.0", @@ -184,15 +140,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, - "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -224,7 +176,8 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "chai": { "version": "4.2.0", @@ -317,6 +270,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -342,7 +296,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cross-spawn": { "version": "6.0.5", @@ -361,6 +316,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -421,7 +377,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "diff": { "version": "3.5.0", @@ -433,6 +390,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -508,7 +466,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extract-zip": { "version": "1.6.7", @@ -525,17 +484,20 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fd-slicer": { "version": "1.0.1", @@ -567,12 +529,14 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -616,6 +580,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -664,12 +629,14 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -706,6 +673,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -826,7 +794,8 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, "is-url": { "version": "1.2.4", @@ -860,7 +829,8 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "js-yaml": { "version": "3.13.0", @@ -875,27 +845,32 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -946,7 +921,8 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true }, "log-symbols": { "version": "2.2.0", @@ -986,12 +962,14 @@ "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true }, "mime-types": { "version": "2.1.22", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, "requires": { "mime-db": "~1.38.0" } @@ -1114,7 +1092,8 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1269,7 +1248,8 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true }, "pify": { "version": "3.0.0", @@ -1301,7 +1281,8 @@ "psl": { "version": "1.1.31", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true }, "pump": { "version": "3.0.0", @@ -1316,12 +1297,14 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "readable-stream": { "version": "2.3.6", @@ -1347,6 +1330,7 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -1370,35 +1354,6 @@ "uuid": "^3.3.2" } }, - "request-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", - "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", - "requires": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1423,12 +1378,14 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sax": { "version": "1.2.4", @@ -1513,6 +1470,7 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -1525,11 +1483,6 @@ "tweetnacl": "~0.14.0" } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1619,6 +1572,7 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -1627,7 +1581,8 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true } } }, @@ -1654,6 +1609,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -1661,7 +1617,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type-detect": { "version": "4.0.8", @@ -1701,6 +1658,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -1714,12 +1672,14 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 261966022a8..9766982d4e8 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -23,13 +23,7 @@ "typescript": "^3.4.3" }, "dependencies": { - "@types/request": "^2.48.1", - "@types/request-promise": "^4.1.42", - "@types/request-promise-native": "^1.0.15", "inversify": "^5.0.1", - "reflect-metadata": "^0.1.13", - "request": "^2.88.0", - "request-promise": "^4.2.4", - "request-promise-native": "^1.0.7" + "reflect-metadata": "^0.1.13" } } diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index 45827679524..9ddb37b60a8 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -23,7 +23,8 @@ export class Ide { private static readonly TOP_MENU_PANEL_CSS: string = "#theia-app-shell #theia-top-panel .p-MenuBar-content"; private static readonly LEFT_CONTENT_PANEL_CSS: string = "#theia-left-content-panel"; - public static readonly EXPLORER_BUTTON_XPATH: string = "(//*[@id='theia-left-content-panel']//ul[@class='p-TabBar-content']//li[@title='Explorer'])[1]"; + public static readonly EXPLORER_BUTTON_XPATH: string = "(//ul[@class='p-TabBar-content']//li[@title='Explorer'])[1]"; + public static readonly SELECTED_EXPLORER_BUTTON_XPATH: string = "(//ul[@class='p-TabBar-content']//li[@title='Explorer' and contains(@class, 'p-mod-current')])[1]" private static readonly PRELOADER_CSS: string = ".theia-preload"; private static readonly IDE_IFRAME_CSS: string = "iframe#ide-application-iframe"; diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index f47535b7f8c..02b7cfc460d 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -25,6 +25,7 @@ import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-detai import { Request, post, get } from "selenium-webdriver/http"; import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; import { Ide } from "./pageobjects/ide/Ide"; +import { ProjectTree } from "./pageobjects/ide/ProjectTree"; const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; @@ -40,6 +41,7 @@ const workspaceDetails: WorkspaceDetails = e2eContainer.get(CLASSES.WorkspaceDet const workspaceDetailsPlugins: WorkspaceDetailsPlugins = e2eContainer.get(CLASSES.WorkspaceDetailsPlugins) const testWorkspaceUtil: TestWorkspaceUtil = e2eContainer.get(CLASSES.TestWorkspaceUtil) const ide: Ide = e2eContainer.get(CLASSES.Ide) +const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree) @@ -77,35 +79,67 @@ suite("E2E", async () => { await newWorkspace.clickOnCreateAndOpenButton() }) - // test("Add 'Java Language Support' plugin to workspace", async () => { - // const javaPluginName: string = "Language Support for Java(TM)"; - // const execPlugin: string = "Che machine-exec Service"; + test.skip("Add 'Java Language Support' plugin to workspace", async () => { + const javaPluginName: string = "Language Support for Java(TM)"; + const execPlugin: string = "Che machine-exec Service"; - // await workspaceDetails.waitPage(workspaceName); - // await workspaceDetails.waitTabSelected('Overview') - // await workspaceDetails.clickOnTab('Plugins') - // await workspaceDetails.waitTabSelected('Plugins') + await workspaceDetails.waitPage(workspaceName); + await workspaceDetails.waitTabSelected('Overview') + await workspaceDetails.clickOnTab('Plugins') + await workspaceDetails.waitTabSelected('Plugins') - // await workspaceDetailsPlugins.waitPluginEnabling(execPlugin) - // await workspaceDetailsPlugins.waitPluginDisabling(javaPluginName) - // await workspaceDetailsPlugins.clickOnPluginListItemSwitcher(javaPluginName) - // await workspaceDetailsPlugins.waitPluginEnabling(javaPluginName) + await workspaceDetailsPlugins.waitPluginEnabling(execPlugin) + await workspaceDetailsPlugins.waitPluginDisabling(javaPluginName) + await workspaceDetailsPlugins.clickOnPluginListItemSwitcher(javaPluginName) + await workspaceDetailsPlugins.waitPluginEnabling(javaPluginName) - // await workspaceDetails.waitSaveButton() - // await workspaceDetails.clickOnSaveButton() - // await workspaceDetails.waitSaveButtonDisappearance() + await workspaceDetails.waitSaveButton() + await workspaceDetails.clickOnSaveButton() + await workspaceDetails.waitSaveButtonDisappearance() - // await workspaceDetails.clickOnOpenButton() - // }) + await workspaceDetails.clickOnOpenButton() + }) test("Wait IDE availability", async () => { await ide.waitAndSwitchToIdeFrame() await ide.waitWorkspaceAndIde(namespace, workspaceName); }) + }) + + suite("Work with IDE", async () => { + let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; + let tabTitle: string = "HelloWorld.java"; + let filePath: string = `${fileFolderPath}/${tabTitle}` + + test("Open project tree container", async () => { + await projectTree.openProjectTreeContainer(); + await projectTree.waitProjectTreeContainer(); + await projectTree.waitProjectImported(sampleName, "src") + }) + test("Expand project and open file in editor", async () => { + projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); + }) + //unskip after resolving issue https://github.com/eclipse/che/issues/12904 + test.skip("Check \"Java Language Server\" initialization by statusbar", async () => { + ide.waitStatusBarContains("Starting Java Language Server") + ide.waitStatusBarContains("100% Starting Java Language Server") + ide.waitStatusBarTextAbcence("Starting Java Language Server") + }) + //unskip after resolving issue https://github.com/eclipse/che/issues/12904 + test.skip("Check \"Java Language Server\" initialization by suggestion invoking", async () => { + // editor.waitEditorAvailable(filePath, tabTitle); + // editor.clickOnTab(filePath); + // editor.waitEditorAvailable(filePath, tabTitle); + + // editor.setCursorToLineAndChar(15, 33); + // editor.performControlSpaceCombination(); + // editor.waitSuggestionContainer(); + // editor.waitSuggestion("getContentType()"); + }) }) @@ -113,9 +147,9 @@ suite("E2E", async () => { }) -// suiteTeardown("close browser", async () => { -// driver.get().quit() -// }) +suiteTeardown("close browser", async () => { + driver.get().quit() +}) diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json index b43a4f2181d..1d324b88b86 100644 --- a/typescript-selenium/tsconfig.json +++ b/typescript-selenium/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "dist", "lib": ["es6", "dom"], - "types": ["reflect-metadata", "@types/mocha", "@types/request-promise-native", "@types/request", "@types/request-promise"], + "types": ["reflect-metadata", "@types/mocha"], "experimentalDecorators": true, "emitDecoratorMetadata": true diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index 02d31c79a1a..8d0230d84c1 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -21,7 +21,8 @@ const CLASSES = { WorkspaceDetails: "WorkspaceDetails", WorkspaceDetailsPlugins: "WorkspaceDetailsPlugins", Ide: "Ide", - TestWorkspaceUtil: "TestWorkspaceUtil" + TestWorkspaceUtil: "TestWorkspaceUtil", + ProjectTree: "ProjectTree" } export { TYPES, CLASSES }; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 7ee1b940936..47bd5a77218 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -246,6 +246,10 @@ export class DriverHelper { await this.driver.wait(callback(), timeout) } + public async reloadPage(){ + await this.driver.navigate().refresh(); + } + } diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 8e3fe317630..5d22ce26fc4 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -7,8 +7,8 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -// import * as request from 'request' -import { TestConstants } from '../../TestConstants'; + + import { TestConstants } from '../../TestConstants'; import { injectable, inject } from 'inversify'; import { DriverHelper } from '../DriverHelper'; import { CLASSES } from '../../types'; @@ -21,9 +21,7 @@ import * as rm from 'typed-rest-client/RestClient' export class TestWorkspaceUtil { private readonly driverHelper: DriverHelper; - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper - ) { + constructor(@inject(CLASSES.DriverHelper) driverHelper: DriverHelper) { this.driverHelper = driverHelper; } @@ -40,9 +38,6 @@ export class TestWorkspaceUtil { for (let i = 0; i < attempts; i++) { let isWorkspaceStarting: boolean = false; - console.log("===>>> waitRunningStatus: ", i) - - const response: rm.IRestResponse = await rest.get(workspaceStatusApiUrl) if (response.statusCode !== 200) { From 2dbc438ab50e78d0b66e2d78353082b00e58c89f Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Wed, 17 Apr 2019 18:16:02 +0300 Subject: [PATCH 12/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- .../pageobjects/ide/ProjectTree.ts | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 typescript-selenium/pageobjects/ide/ProjectTree.ts diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts new file mode 100644 index 00000000000..f23fb26437a --- /dev/null +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -0,0 +1,193 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import 'reflect-metadata'; +import { injectable, inject } from 'inversify'; +import { DriverHelper } from '../../utils/DriverHelper'; +import { CLASSES } from '../../types'; +import { Ide } from './Ide'; +import { TestConstants } from '../../TestConstants'; +import { By } from 'selenium-webdriver'; + +@injectable() +export class ProjectTree { + private readonly driverHelper: DriverHelper; + private readonly ide: Ide; + + constructor( + @inject(CLASSES.DriverHelper) driverHelper: DriverHelper, + @inject(CLASSES.Ide) ide: Ide + ) { + this.driverHelper = driverHelper + this.ide = ide + } + + private static readonly PROJECT_TREE_CONTAINER_CSS: string = "#theia-left-side-panel .theia-TreeContainer"; + + private getItemCss(itemPath: string): string { + return `div[id='/projects:/projects/${itemPath}']`; + } + + private getCollapsedItemCssLocator(itemPath: string): string { + return `${this.getExpandIconCssLocator(itemPath)}.theia-mod-collapsed`; + } + + private getExpandedItemCssLocator(itemPath: string): string { + return `${this.getExpandIconCssLocator(itemPath)}:not(.theia-mod-collapsed)`; + } + + private getExpandIconCssLocator(itemPath: string) { + return `div[data-node-id='/projects:/projects${itemPath}']`; + } + + private getTreeItemCssLocator(itemPath: string) { + return `.theia-TreeNode[title='/projects${itemPath}']` + } + + + + async openProjectTreeContainer(timeout = TestConstants.DEFAULT_TIMEOUT) { + const selectedExplorerButtonLocator: By = By.xpath(Ide.SELECTED_EXPLORER_BUTTON_XPATH) + + await this.ide.waitExplorerButton(timeout) + + const isButtonEnabled: boolean = await this.driverHelper.waitVisibilityBoolean(selectedExplorerButtonLocator) + + if (!isButtonEnabled) { + await this.ide.clickOnExplorerButton(); + } + + await this.waitProjectTreeContainer(); + } + + async waitItemExpanded(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const expandedItemLocator: By = By.css(this.getExpandedItemCssLocator(itemPath)) + + await this.driverHelper.waitVisibility(expandedItemLocator, timeout) + } + + async waitItemCollapsed(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const collapsedItemLocator: By = By.css(this.getCollapsedItemCssLocator(itemPath)) + + await this.driverHelper.waitVisibility(collapsedItemLocator, timeout) + } + + async waitProjectTreeContainer(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(ProjectTree.PROJECT_TREE_CONTAINER_CSS), timeout) + } + + async waitProjectTreeContainerClosed(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.css(ProjectTree.PROJECT_TREE_CONTAINER_CSS), attempts, polling) + } + + async waitItemDisappearance(itemPath: string, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.css(this.getItemCss(itemPath)), attempts, polling) + } + + async clickOnItem(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.css(this.getItemCss(itemPath)), timeout) + await this.waitItemSelected(itemPath, timeout); + } + + async waitItemSelected(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const selectedItemLocator: By = By.css(`div[title='/projects/${itemPath}'].theia-mod-selected.theia-mod-focus`); + + await this.driverHelper.waitVisibility(selectedItemLocator, timeout) + } + + async expandItem(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const expandIconLocator: By = By.css(this.getExpandIconCssLocator(itemPath)) + const treeItemLocator: By = By.css(this.getTreeItemCssLocator(itemPath)) + + + const classAttributeValue: string = await this.driverHelper.waitAndGetElementAttribute(expandIconLocator, 'class', timeout) + const isItemCollapsed: boolean = classAttributeValue.search('theia-mod-collapsed') > 0 + + if (isItemCollapsed) { + await this.driverHelper.waitAndClick(treeItemLocator, timeout) + } + + await this.waitItemExpanded(itemPath, timeout); + } + + async collapseItem(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const expandIconLocator: By = By.css(this.getExpandIconCssLocator(itemPath)) + const treeItemLocator: By = By.css(this.getTreeItemCssLocator(itemPath)) + + const classAttributeValue: string = await this.driverHelper.waitAndGetElementAttribute(expandIconLocator, 'class', timeout) + const isItemCollapsed: boolean = classAttributeValue.search('theia-mod-collapsed') > 0 + + if (!isItemCollapsed) { + await this.driverHelper.waitAndClick(treeItemLocator, timeout) + } + + await this.waitItemCollapsed(itemPath, timeout); + } + + async expandPathAndOpenFile(pathToItem: string, fileName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + let currentPath: string = ""; + let paths: Array = new Array(); + + // make direct path for each project tree item + pathToItem.split('/') + .forEach(item => { + currentPath = `${currentPath}/${item}`; + paths.push(currentPath); + }) + + //expand each project tree item + for (const path of paths) { + await this.expandItem(path, timeout) + } + + //open file + await this.clickOnItem(`${pathToItem}/${fileName}`, timeout) + } + + async waitProjectImported(projectName: string, rootSubItem: string, attempts = TestConstants.DEFAULT_ATTEMPTS, + visibilityItemPolling = TestConstants.DEFAULT_POLLING * 5, triesPolling = TestConstants.DEFAULT_POLLING * 30) { + + const rootItem: string = `/${projectName}`; + const rootItemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}`)); + const rootSubitemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}/${rootSubItem}`)); + + // const delayBeforeItemCheck: number = Cypress.env("ProjectTree.delayBeforeItemCheck"); + + for (let i = 0; i < attempts; i++) { + + const isProjectFolderVisible = await this.driverHelper.waitVisibilityBoolean(rootItemLocator, attempts, visibilityItemPolling) + + if (!isProjectFolderVisible) { + await this.driverHelper.reloadPage() + await this.ide.waitIde() + await this.openProjectTreeContainer() + await this.driverHelper.wait(triesPolling) + continue; + } + + await this.expandItem(rootItem) + await this.waitItemExpanded(rootItem) + + const isRootSubItemVisible = await this.driverHelper.waitVisibilityBoolean(rootSubitemLocator, attempts, visibilityItemPolling) + + if (!isProjectFolderVisible) { + await this.driverHelper.reloadPage() + await this.ide.waitIde() + await this.openProjectTreeContainer() + await this.driverHelper.wait(triesPolling) + continue; + } + + return + } + + throw new Error("Exceeded the maximum number of checking attempts, project has not been imported") + } + +} From 10f011f18f0d096e55fb128047f5009c8baee3d3 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 18 Apr 2019 17:31:32 +0300 Subject: [PATCH 13/57] Intermediate 'Selenium' + 'Mocha' + 'Chai' working version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 14 ++- .../driverHelperUnitTests.spec.ts | 3 - typescript-selenium/inversify.config.ts | 2 + typescript-selenium/package.json | 4 +- .../pageobjects/dashboard/Dashboard.ts | 7 +- .../pageobjects/dashboard/Workspaces.ts | 8 +- typescript-selenium/pageobjects/ide/Editor.ts | 85 +++++++++++++++++++ .../pageobjects/ide/ProjectTree.ts | 2 +- typescript-selenium/spec.ts | 49 ++++++----- typescript-selenium/tsconfig.json | 2 +- typescript-selenium/types.ts | 3 +- typescript-selenium/utils/DriverHelper.ts | 32 +++---- .../utils/workspace/TestWorkspaceUtil.ts | 2 +- 13 files changed, 163 insertions(+), 50 deletions(-) create mode 100644 typescript-selenium/pageobjects/ide/Editor.ts diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 325db0fda88..4dc45dbb8f3 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -8,6 +8,17 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +function readAndSetEnv(envProperty: any, defaultValue: string | number): any{ + const propertyValue = envProperty + if(propertyValue === undefined || propertyValue === null){ + return defaultValue; + } + + return propertyValue +} + + + export const TestConstants = { START_STOP_WORKSPACE_TIMEOUT: 240000, LOAD_PAGE_TIMEOUT: 120000, @@ -20,6 +31,5 @@ export const TestConstants = { WORKSPACE_STATUS_ATTEMPTS: 90, WORKSPACE_STATUS_POLLING: 10000, - BASE_URL: "http://che-che.192.168.99.100.nip.io" -} \ No newline at end of file +} diff --git a/typescript-selenium/driverHelperUnitTests.spec.ts b/typescript-selenium/driverHelperUnitTests.spec.ts index d268a41596a..33a3e617223 100644 --- a/typescript-selenium/driverHelperUnitTests.spec.ts +++ b/typescript-selenium/driverHelperUnitTests.spec.ts @@ -91,9 +91,6 @@ suite("Test of 'DriverHelper' methods", async () => { await driverHelper.waitAttributeValue(By.css("#dashboard-item"), 'id', 'dashboard-item') }) - - - }) suiteTeardown("close browser", async () => { diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 6bd7d751f15..844fba7593f 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -13,6 +13,7 @@ import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-detai import { Ide } from "./pageobjects/ide/Ide"; import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; import { ProjectTree } from "./pageobjects/ide/ProjectTree"; +import { Editor } from "./pageobjects/ide/Editor"; const e2eContainer = new Container(); @@ -28,6 +29,7 @@ e2eContainer.bind(CLASSES.WorkspaceDetailsPlugins).to(W e2eContainer.bind(CLASSES.Ide).to(Ide).inSingletonScope(); e2eContainer.bind(CLASSES.TestWorkspaceUtil).to(TestWorkspaceUtil).inSingletonScope(); e2eContainer.bind(CLASSES.ProjectTree).to(ProjectTree).inSingletonScope(); +e2eContainer.bind(CLASSES.Editor).to(Editor).inSingletonScope(); diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 9766982d4e8..9eef2140283 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --require ts-node/register --timeout 150000 -u tdd spec.ts", - "units": "mocha --require ts-node/register --timeout 150000 -u tdd driverHelperUnitTests.spec.ts" + "test": "mocha --require ts-node/register --timeout 1200000 -u tdd spec.ts", + "units": "mocha --require ts-node/register --timeout 1200000 -u tdd driverHelperUnitTests.spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", "license": "ISC", diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 8813147e9e1..206cb9b8aea 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -32,7 +32,12 @@ export class Dashboard { this.driverHelper = driverHelper; } - async waitPage(timeout = TestConstants.DEFAULT_TIMEOUT) { + async openDashboard(timeout = TestConstants.LOAD_PAGE_TIMEOUT){ + await this.driverHelper.navigateTo(TestConstants.BASE_URL) + await this.waitPage(timeout) + } + + async waitPage(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) await this.driverHelper.waitVisibility(By.css(Dashboard.WORKSPACES_BUTTON_CSS), timeout) await this.driverHelper.waitVisibility(By.css(Dashboard.STACKS_BUTTON_CSS), timeout) diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/typescript-selenium/pageobjects/dashboard/Workspaces.ts index 22fa11e99e5..3cd9582debf 100644 --- a/typescript-selenium/pageobjects/dashboard/Workspaces.ts +++ b/typescript-selenium/pageobjects/dashboard/Workspaces.ts @@ -34,7 +34,7 @@ export class Workspaces { return `#ws-name-${workspaceName}` } - private getWorkspaceStatusLocator(workspaceName: string, workspaceStatus: string){ + private getWorkspaceStatusCssLocator(workspaceName: string, workspaceStatus: string): string{ return `#ws-name-${workspaceName}[data-ws-status='${workspaceStatus}']` } @@ -47,7 +47,7 @@ export class Workspaces { } async waitWorkspaceListItem(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { - const workspaceListItemLocator: By = await By.css(this.getWorkspaceListItemLocator(workspaceName)); + const workspaceListItemLocator: By = By.css(this.getWorkspaceListItemLocator(workspaceName)); await this.driverHelper.waitVisibility(workspaceListItemLocator, timeout) } @@ -59,13 +59,13 @@ export class Workspaces { } async waitWorkspaceWithRunningStatus(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { - const runningStatusLocator: By = await this.getWorkspaceStatusLocator(workspaceName, 'RUNNING') + const runningStatusLocator: By = By.css(this.getWorkspaceStatusCssLocator(workspaceName, 'RUNNING')) await this.driverHelper.waitVisibility(runningStatusLocator, timeout) } async waitWorkspaceWithStoppedStatus(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { - const stoppedStatusLocator: By = await this.getWorkspaceStatusLocator(workspaceName, 'STOPPED') + const stoppedStatusLocator: By = By.css(this.getWorkspaceStatusCssLocator(workspaceName, 'STOPPED')) await this.driverHelper.waitVisibility(stoppedStatusLocator, timeout) } diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts new file mode 100644 index 00000000000..ad8ef8741a0 --- /dev/null +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -0,0 +1,85 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import 'reflect-metadata'; +import { injectable, inject } from 'inversify'; +import { DriverHelper } from '../../utils/DriverHelper'; +import { CLASSES } from '../../types'; +import { TestConstants } from '../../TestConstants'; +import { By } from 'selenium-webdriver'; + +@injectable() +export class Editor { + private readonly driverHelper: DriverHelper; + + constructor(@inject(CLASSES.DriverHelper) driverHelper: DriverHelper) { + this.driverHelper = driverHelper + } + + private static readonly EDITOR_LINES_CSS: string = ".lines-content .view-line"; + private static readonly EDITOR_BODY_CSS: string = "#theia-main-content-panel .lines-content"; + private static readonly SUGGESTION_WIDGET_BODY_CSS: string = "div[widgetId='editor.widget.suggestWidget']" + private static readonly SUGGESTION_WIDGET_ROW_CSS: string = "div[widgetId='editor.widget.suggestWidget'] .monaco-list-row"; + + private getEditorLineXpathLocator(lineNumber: number): string{ + return `(//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line'])[${lineNumber}]` + } + + async waitSuggestionContainer(timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(Editor.SUGGESTION_WIDGET_BODY_CSS), timeout) + } + + async waitSuggestionContainerClosed(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.css(Editor.SUGGESTION_WIDGET_BODY_CSS), attempts, polling) + } + + private getTabXpathLocator(tabTitle: string): string { + return `//li[contains(@class, 'p-TabBar-tab')]//div[text()='${tabTitle}']`; + } + + async waitTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.xpath(this.getTabXpathLocator(tabTitle)), timeout) + } + + async waitTabDisappearance(tabTitle: string, attempt = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + await this.driverHelper.waitDisappearance(By.xpath(this.getTabXpathLocator(tabTitle)), attempt, polling) + } + + async clickOnTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.xpath(this.getTabXpathLocator(tabTitle)), timeout) + } + + async waitTabFocused(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const focusedTabLocator: By = By.xpath(`//li[contains(@class, 'p-TabBar-tab') and contains(@class, 'theia-mod-active')]//div[text()='${tabTitle}']`) + + await this.driverHelper.waitVisibility(focusedTabLocator, timeout) + + // wait for increasing stability + await this.driverHelper.wait(2000) + } + + async closeTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + const tabCloseButtonLocator: By = By.xpath(`//div[text()='${tabTitle}']/parent::li//div[contains(@class, 'p-TabBar-tabCloseIcon')]`) + + await this.driverHelper.waitAndClick(tabCloseButtonLocator, timeout) + } + + async waitEditorOpened(timeout = TestConstants.DEFAULT_TIMEOUT) { + const firstEditorLineLocator: By = By.xpath(this.getEditorLineXpathLocator(1)) + + await this.driverHelper.waitVisibility(By.css(Editor.EDITOR_BODY_CSS), timeout) + await this.driverHelper.waitVisibility(firstEditorLineLocator, timeout) + } + + async waitEditorAvailable(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + await this.waitTab(tabTitle, timeout); + await this.waitEditorOpened(timeout); + } + +} diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index f23fb26437a..463b63b9d5e 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -157,8 +157,8 @@ export class ProjectTree { const rootItemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}`)); const rootSubitemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}/${rootSubItem}`)); - // const delayBeforeItemCheck: number = Cypress.env("ProjectTree.delayBeforeItemCheck"); + for (let i = 0; i < attempts; i++) { const isProjectFolderVisible = await this.driverHelper.waitVisibilityBoolean(rootItemLocator, attempts, visibilityItemPolling) diff --git a/typescript-selenium/spec.ts b/typescript-selenium/spec.ts index 02b7cfc460d..05a207afa97 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/spec.ts @@ -26,6 +26,8 @@ import { Request, post, get } from "selenium-webdriver/http"; import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; import { Ide } from "./pageobjects/ide/Ide"; import { ProjectTree } from "./pageobjects/ide/ProjectTree"; +import { Editor } from "./pageobjects/ide/Editor"; +import { TestConstants } from "./TestConstants"; const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; @@ -39,9 +41,9 @@ const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces); const newWorkspace: NewWorkspace = e2eContainer.get(CLASSES.NewWorkspace); const workspaceDetails: WorkspaceDetails = e2eContainer.get(CLASSES.WorkspaceDetails); const workspaceDetailsPlugins: WorkspaceDetailsPlugins = e2eContainer.get(CLASSES.WorkspaceDetailsPlugins) -const testWorkspaceUtil: TestWorkspaceUtil = e2eContainer.get(CLASSES.TestWorkspaceUtil) const ide: Ide = e2eContainer.get(CLASSES.Ide) const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree) +const editor: Editor = e2eContainer.get(CLASSES.Editor) @@ -119,40 +121,49 @@ suite("E2E", async () => { }) test("Expand project and open file in editor", async () => { - projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); + await projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); }) //unskip after resolving issue https://github.com/eclipse/che/issues/12904 test.skip("Check \"Java Language Server\" initialization by statusbar", async () => { - ide.waitStatusBarContains("Starting Java Language Server") - ide.waitStatusBarContains("100% Starting Java Language Server") - ide.waitStatusBarTextAbcence("Starting Java Language Server") + await ide.waitStatusBarContains("Starting Java Language Server") + await ide.waitStatusBarContains("100% Starting Java Language Server") + await ide.waitStatusBarTextAbcence("Starting Java Language Server") }) //unskip after resolving issue https://github.com/eclipse/che/issues/12904 test.skip("Check \"Java Language Server\" initialization by suggestion invoking", async () => { - // editor.waitEditorAvailable(filePath, tabTitle); - // editor.clickOnTab(filePath); - // editor.waitEditorAvailable(filePath, tabTitle); - - // editor.setCursorToLineAndChar(15, 33); - // editor.performControlSpaceCombination(); - // editor.waitSuggestionContainer(); - // editor.waitSuggestion("getContentType()"); + await editor.waitEditorAvailable(tabTitle); + await editor.clickOnTab(tabTitle); + await editor.waitEditorAvailable(tabTitle); }) }) + suite("Stop and remove workspace", async () => { + test("Stop workspace", async () => { + await dashboard.openDashboard() + await dashboard.clickWorkspacesButton() + await workspaces.waitPage() + await workspaces.waitWorkspaceListItem(workspaceName) + await workspaces.waitWorkspaceWithRunningStatus(workspaceName) + await workspaces.clickOnStopWorkspaceButton(workspaceName) + await workspaces.waitWorkspaceWithStoppedStatus(workspaceName) + }) + test("Delete workspace", async () => { + await workspaces.waitPage() + await workspaces.waitWorkspaceListItem(workspaceName) + await workspaces.clickWorkspaceListItem(workspaceName); + await workspaces.clickDeleteButtonOnWorkspaceDetails(); + await workspaces.clickConfirmDeletionButton(); + await workspaces.waitPage() + await workspaces.waitWorkspaceListItemAbcence(workspaceName); + }) + }) }) suiteTeardown("close browser", async () => { driver.get().quit() }) - - - - - - diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json index 1d324b88b86..4edaf08ae43 100644 --- a/typescript-selenium/tsconfig.json +++ b/typescript-selenium/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "dist", "lib": ["es6", "dom"], - "types": ["reflect-metadata", "@types/mocha"], + "types": ["reflect-metadata", "@types/mocha", "@types/node"], "experimentalDecorators": true, "emitDecoratorMetadata": true diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index 8d0230d84c1..b854187d02e 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -22,7 +22,8 @@ const CLASSES = { WorkspaceDetailsPlugins: "WorkspaceDetailsPlugins", Ide: "Ide", TestWorkspaceUtil: "TestWorkspaceUtil", - ProjectTree: "ProjectTree" + ProjectTree: "ProjectTree", + Editor: "Editor" } export { TYPES, CLASSES }; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 47bd5a77218..3dbbcfbf75d 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -26,20 +26,18 @@ export class DriverHelper { this.driver = driver.get(); } - public findElement(locator: By): WebElementPromise { - return this.driver.findElement(locator); - } - - public isVisible(locator: By): promise.Promise { - return this.findElement(locator) - .isDisplayed() - .catch(err => { - return false - }) + public async isVisible(locator: By): Promise { + try { + const element: WebElement = await this.driver.findElement(locator) + const isVisible: boolean = await element.isDisplayed() + return isVisible + } catch{ + return false + } } - public wait(miliseconds: number): promise.Promise { - return new promise.Promise(resolve => { setTimeout(resolve, miliseconds) }) + public async wait(miliseconds: number): Promise { + await this.driver.sleep(miliseconds) } public async waitVisibilityBoolean(locator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { @@ -81,9 +79,9 @@ export class DriverHelper { return await this.driver.wait(until.elementIsVisible(webElement), timeout) } catch (err) { if (err instanceof error.StaleElementReferenceError) { - + console.log("==>>> 'waitVisibility' catched exception") - + await this.wait(polling) continue; } @@ -246,10 +244,14 @@ export class DriverHelper { await this.driver.wait(callback(), timeout) } - public async reloadPage(){ + public async reloadPage() { await this.driver.navigate().refresh(); } + public async navigateTo(url: string) { + await this.driver.navigate().to(url) + } + } diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 5d22ce26fc4..486b65ff5ec 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -37,7 +37,7 @@ export class TestWorkspaceUtil { for (let i = 0; i < attempts; i++) { let isWorkspaceStarting: boolean = false; - + const response: rm.IRestResponse = await rest.get(workspaceStatusApiUrl) if (response.statusCode !== 200) { From e9fa3ff0f593b160d55052ac9a37275a8af3579a Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 18 Apr 2019 18:01:18 +0300 Subject: [PATCH 14/57] Add launch customization possibility by environment variables set Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 20 +++---- .../pageobjects/dashboard/Dashboard.ts | 18 +++--- .../pageobjects/dashboard/NewWorkspace.ts | 22 ++++---- .../pageobjects/dashboard/Workspaces.ts | 34 +++++------ .../workspace-details/WorkspaceDetails.ts | 26 ++++----- .../WorkspaceDetailsPlugins.ts | 12 ++-- typescript-selenium/pageobjects/ide/Editor.ts | 26 ++++----- typescript-selenium/pageobjects/ide/Ide.ts | 28 +++++----- .../pageobjects/ide/ProjectTree.ts | 28 +++++----- typescript-selenium/utils/DriverHelper.ts | 56 +++++++++---------- .../utils/workspace/TestWorkspaceUtil.ts | 10 ++-- 11 files changed, 140 insertions(+), 140 deletions(-) diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 4dc45dbb8f3..b63673d7fac 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -function readAndSetEnv(envProperty: any, defaultValue: string | number): any{ +function readEnvAndSetValue(envProperty: any, defaultValue: string | number): any{ const propertyValue = envProperty if(propertyValue === undefined || propertyValue === null){ return defaultValue; @@ -20,16 +20,16 @@ function readAndSetEnv(envProperty: any, defaultValue: string | number): any{ export const TestConstants = { - START_STOP_WORKSPACE_TIMEOUT: 240000, - LOAD_PAGE_TIMEOUT: 120000, - LANGUAGE_SERVER_INITIALIZATION_TIMEOUT: 180000, + TS_SELENIUM_START_WORKSPACE_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_START_WORKSPACE_TIMEOUT, 240000), + TS_SELENIUM_LOAD_PAGE_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_LOAD_PAGE_TIMEOUT, 120000), + TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT, 180000), - DEFAULT_TIMEOUT: 20000, - DEFAULT_ATTEMPTS: 5, - DEFAULT_POLLING: 1000, + TS_SELENIUM_DEFAULT_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_DEFAULT_TIMEOUT, 20000), + TS_SELENIUM_DEFAULT_ATTEMPTS: readEnvAndSetValue(process.env.TS_SELENIUM_DEFAULT_ATTEMPTS, 5), + TS_SELENIUM_DEFAULT_POLLING: readEnvAndSetValue(process.env.TS_SELENIUM_DEFAULT_POLLING, 1000), - WORKSPACE_STATUS_ATTEMPTS: 90, - WORKSPACE_STATUS_POLLING: 10000, + TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: readEnvAndSetValue(process.env.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS, 90), + TS_SELENIUM_WORKSPACE_STATUS_POLLING: readEnvAndSetValue(process.env.TS_SELENIUM_WORKSPACE_STATUS_POLLING, 10000), - BASE_URL: "http://che-che.192.168.99.100.nip.io" + TS_SELENIUM_BASE_URL: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, "http://che-che.192.168.99.100.nip.io") } diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 206cb9b8aea..78b05654fc1 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -32,39 +32,39 @@ export class Dashboard { this.driverHelper = driverHelper; } - async openDashboard(timeout = TestConstants.LOAD_PAGE_TIMEOUT){ - await this.driverHelper.navigateTo(TestConstants.BASE_URL) + async openDashboard(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + await this.driverHelper.navigateTo(TestConstants.TS_SELENIUM_BASE_URL) await this.waitPage(timeout) } - async waitPage(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitPage(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) await this.driverHelper.waitVisibility(By.css(Dashboard.WORKSPACES_BUTTON_CSS), timeout) await this.driverHelper.waitVisibility(By.css(Dashboard.STACKS_BUTTON_CSS), timeout) await this.driverHelper.waitVisibility(By.css(Dashboard.FACTORIES_BUTTON_CSS), timeout) } - async clickDashboardButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickDashboardButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(Dashboard.DASHBOARD_BUTTON_CSS), timeout) } - async clickWorkspacesButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickWorkspacesButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(Dashboard.WORKSPACES_BUTTON_CSS), timeout) } - async clickStacksdButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickStacksdButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(Dashboard.STACKS_BUTTON_CSS), timeout) } - async clickFactoriesButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickFactoriesButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(Dashboard.FACTORIES_BUTTON_CSS), timeout) } - async waitLoader(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitLoader(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Dashboard.LOADER_PAGE_CSS), timeout) } - async waitLoaderDisappearance(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitLoaderDisappearance(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitDisappearance(By.css(Dashboard.LOADER_PAGE_CSS), timeout) } diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index f92b1aadae4..7a67eb0aff9 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -35,7 +35,7 @@ export class NewWorkspace { this.driverHelper = driverHelper; } - async selectCreateWorkspaceAndProceedEditing(timeout = TestConstants.DEFAULT_TIMEOUT) { + async selectCreateWorkspaceAndProceedEditing(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const createAndProceedEditingButtonLocator: By = By.xpath("//span[text()='Create & Proceed Editing']") // open drop down list @@ -45,25 +45,25 @@ export class NewWorkspace { await this.driverHelper.waitAndClick(createAndProceedEditingButtonLocator, timeout) } - async typeWorkspaceName(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async typeWorkspaceName(workspaceName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const workspaceNameFieldLocator: By = By.css(NewWorkspace.NAME_FIELD_CSS) await this.driverHelper.enterValue(workspaceNameFieldLocator, workspaceName, timeout) } - async clickOnChe7Stack(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnChe7Stack(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const che7StackLocator: By = By.css(NewWorkspace.CHE_7_STACK_CSS) await this.driverHelper.waitAndClick(che7StackLocator) } - async waitChe7StackSelected(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitChe7StackSelected(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const che7SelectedStackLocator: By = By.css(NewWorkspace.SELECTED_CHE_7_STACK_CSS) await this.driverHelper.waitVisibility(che7SelectedStackLocator, timeout) } - async clickOnCreateAndOpenButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnCreateAndOpenButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const ideFrameLocator: By = By.xpath("//ide-iframe[@id='ide-iframe-window' and @aria-hidden='false']") await this.driverHelper.waitAndClick(By.xpath(NewWorkspace.CREATE_AND_OPEN_BUTTON_XPATH), timeout) @@ -72,38 +72,38 @@ export class NewWorkspace { await this.driverHelper.waitVisibility(ideFrameLocator, timeout) } - async clickOnAddOrImportProjectButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnAddOrImportProjectButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const addOrImportProjectButtonLocator: By = By.css(NewWorkspace.ADD_OR_IMPORT_PROJECT_BUTTON_CSS) await this.driverHelper.waitAndClick(addOrImportProjectButtonLocator, timeout) } - async waitSampleCheckboxEnabling(sampleName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitSampleCheckboxEnabling(sampleName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const enabledSampleCheckboxLocator: By = By.css(`#sample-${sampleName}>md-checkbox[aria-checked='true']`) await this.driverHelper.waitVisibility(enabledSampleCheckboxLocator, timeout) } - async enableSampleCheckbox(sampleName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async enableSampleCheckbox(sampleName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const sampleCheckboxLocator: By = By.xpath(`(//*[@id='sample-${sampleName}']//md-checkbox//div)[1]`) await this.driverHelper.waitAndClick(sampleCheckboxLocator, timeout) await this.waitSampleCheckboxEnabling(sampleName, timeout) } - async waitProjectAdding(projectName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitProjectAdding(projectName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const addedProjectLocator: By = By.css(`#project-source-selector toggle-single-button#${projectName}`) await this.driverHelper.waitVisibility(addedProjectLocator, timeout) } - async waitProjectAbsence(projectName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitProjectAbsence(projectName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const addedProjectLocator: By = By.css(`#project-source-selector toggle-single-button#${projectName}`) await this.driverHelper.waitDisappearance(addedProjectLocator, timeout) } - async clickOnAddButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnAddButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(NewWorkspace.ADD_BUTTON_CSS), timeout) } diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/typescript-selenium/pageobjects/dashboard/Workspaces.ts index 3cd9582debf..b4d99ecbaac 100644 --- a/typescript-selenium/pageobjects/dashboard/Workspaces.ts +++ b/typescript-selenium/pageobjects/dashboard/Workspaces.ts @@ -20,7 +20,7 @@ export class Workspaces { private readonly driverHelper: DriverHelper; private static readonly TITLE: string = ".che-toolbar-title-label"; private static readonly ADD_WORKSPACE_BUTTON_CSS: string = "#add-item-button"; - private static readonly START_STOP_WORKSPACE_TIMEOUT: number = TestConstants.START_STOP_WORKSPACE_TIMEOUT + private static readonly START_STOP_WORKSPACE_TIMEOUT: number = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT constructor( @inject(CLASSES.DriverHelper) driverHelper: DriverHelper @@ -34,61 +34,61 @@ export class Workspaces { return `#ws-name-${workspaceName}` } - private getWorkspaceStatusCssLocator(workspaceName: string, workspaceStatus: string): string{ + private getWorkspaceStatusCssLocator(workspaceName: string, workspaceStatus: string): string { return `#ws-name-${workspaceName}[data-ws-status='${workspaceStatus}']` } - async waitPage(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitPage(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Workspaces.ADD_WORKSPACE_BUTTON_CSS), timeout) } - async clickAddWorkspaceButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickAddWorkspaceButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(Workspaces.ADD_WORKSPACE_BUTTON_CSS), timeout) } - async waitWorkspaceListItem(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { - const workspaceListItemLocator: By = By.css(this.getWorkspaceListItemLocator(workspaceName)); + async waitWorkspaceListItem(workspaceName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const workspaceListItemLocator: By = By.css(this.getWorkspaceListItemLocator(workspaceName)); await this.driverHelper.waitVisibility(workspaceListItemLocator, timeout) } - async clickOnStopWorkspaceButton(workspaceName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnStopWorkspaceButton(workspaceName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const stopWorkspaceButtonLocator: By = By.css(`#ws-name-${workspaceName} .workspace-status[uib-tooltip="Stop workspace"]`) await this.driverHelper.waitAndClick(stopWorkspaceButtonLocator, timeout) } - async waitWorkspaceWithRunningStatus(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + async waitWorkspaceWithRunningStatus(workspaceName: string, timeout = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT) { const runningStatusLocator: By = By.css(this.getWorkspaceStatusCssLocator(workspaceName, 'RUNNING')) - + await this.driverHelper.waitVisibility(runningStatusLocator, timeout) } - async waitWorkspaceWithStoppedStatus(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + async waitWorkspaceWithStoppedStatus(workspaceName: string, timeout = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT) { const stoppedStatusLocator: By = By.css(this.getWorkspaceStatusCssLocator(workspaceName, 'STOPPED')) - + await this.driverHelper.waitVisibility(stoppedStatusLocator, timeout) } - async clickWorkspaceListItem(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + async clickWorkspaceListItem(workspaceName: string, timeout = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT) { const workspaceListItemLocator: By = By.css(`div[id='ws-full-name-che/${workspaceName}']`) - - await this.driverHelper.waitAndClick(workspaceListItemLocator, timeout) + + await this.driverHelper.waitAndClick(workspaceListItemLocator, timeout) } - async clickDeleteButtonOnWorkspaceDetails(timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + async clickDeleteButtonOnWorkspaceDetails(timeout = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT) { const deleteButtonOnWorkspaceDetailsLocator: By = By.css("che-button-danger[che-button-title='Delete']") await this.driverHelper.waitAndClick(deleteButtonOnWorkspaceDetailsLocator, timeout) } - async waitWorkspaceListItemAbcence(workspaceName: string, timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + async waitWorkspaceListItemAbcence(workspaceName: string, timeout = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT) { const workspaceListItemLocator: By = By.css(`div[id='ws-full-name-che/${workspaceName}']`) await this.driverHelper.waitDisappearance(workspaceListItemLocator, timeout) } - async clickConfirmDeletionButton(timeout = TestConstants.START_STOP_WORKSPACE_TIMEOUT) { + async clickConfirmDeletionButton(timeout = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT) { await this.driverHelper.waitAndClick(By.css('#ok-dialog-button'), timeout) } diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index c70fd6792bc..bac486b47da 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -48,23 +48,23 @@ export class WorkspaceDetails { return `//md-tabs-canvas[@role='tablist']//md-tab-item[@aria-selected='true']//span[text()='${tabTitle}']` } - async waitLoaderDisappearance(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitLoaderDisappearance(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(WorkspaceDetails.WORKSPACE_DETAILS_LOADER_CSS), attempts, polling) } - async waitSaveButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitSaveButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.ENABLED_SAVE_BUTTON_CSS), timeout) } - async waitSaveButtonDisappearance(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitSaveButtonDisappearance(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(WorkspaceDetails.SAVE_BUTTON_CSS), attempts, polling) } - async clickOnSaveButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnSaveButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.ENABLED_SAVE_BUTTON_CSS), timeout) } - async waitPage(workspaceName: string, timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitPage(workspaceName: string, timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.waitWorkspaceTitle(workspaceName, timeout); await this.waitOpenButton(timeout); await this.waitRunButton(timeout); @@ -72,29 +72,29 @@ export class WorkspaceDetails { await this.waitLoaderDisappearance(timeout); } - async waitWorkspaceTitle(workspaceName: string, timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitWorkspaceTitle(workspaceName: string, timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { const workspaceTitleLocator: By = By.css(this.getWorkspaceTitleCssLocator(workspaceName)) await this.driverHelper.waitVisibility(workspaceTitleLocator, timeout) } - async waitRunButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitRunButton(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.RUN_BUTTON_CSS), timeout) } - async clickOnRunButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async clickOnRunButton(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.RUN_BUTTON_CSS), timeout) } - async waitOpenButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitOpenButton(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.OPEN_BUTTON_CSS), timeout) } - async clickOnOpenButton(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async clickOnOpenButton(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.OPEN_BUTTON_CSS), timeout) } - async waitTabsPresence(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitTabsPresence(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { const workspaceDetailsTabs: Array = ["Overview", "Projects", "Containers", "Servers", "Env Variables", "Volumes", "Config", "SSH", "Tools", "Plugins"]; @@ -105,14 +105,14 @@ export class WorkspaceDetails { } } - async clickOnTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const workspaceDetailsTabLocator: By = By.xpath(this.getTabXpathLocator(tabTitle)) await this.driverHelper.waitAndClick(workspaceDetailsTabLocator, timeout) } - async waitTabSelected(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitTabSelected(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const selectedTabLocator: By = By.xpath(this.getSelectedTabXpathLocator(tabTitle)) await this.driverHelper.waitVisibility(selectedTabLocator, timeout) diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts index 62b9f3bda40..3c9a90bf6c3 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -33,25 +33,25 @@ export class WorkspaceDetailsPlugins { return `${this.getPluginListItemCssLocator(pluginName)} md-switch` } - async waitPluginListItem(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitPluginListItem(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const pluginListItemLocator: By = By.css(this.getPluginListItemCssLocator(pluginName)) - + await this.driverHelper.waitVisibility(pluginListItemLocator, timeout) } - async clickOnPluginListItemSwitcher(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnPluginListItemSwitcher(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const pluginListItemSwitcherLocator = By.css(this.getPluginListItemSwitcherCssLocator(pluginName)) await this.driverHelper.waitAndClick(pluginListItemSwitcherLocator, timeout) } - async waitPluginEnabling(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitPluginEnabling(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const enabledPluginSwitcherLocator: By = By.css(`${this.getPluginListItemCssLocator(pluginName)} md-switch[aria-checked='true']`) - + await this.driverHelper.waitVisibility(enabledPluginSwitcherLocator, timeout) } - async waitPluginDisabling(pluginName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitPluginDisabling(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const disabledPluginSwitcherLocator: By = By.css(`${this.getPluginListItemCssLocator(pluginName)} md-switch[aria-checked='false']`) await this.driverHelper.waitVisibility(disabledPluginSwitcherLocator, timeout) diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index ad8ef8741a0..42e07577586 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -26,16 +26,16 @@ export class Editor { private static readonly EDITOR_BODY_CSS: string = "#theia-main-content-panel .lines-content"; private static readonly SUGGESTION_WIDGET_BODY_CSS: string = "div[widgetId='editor.widget.suggestWidget']" private static readonly SUGGESTION_WIDGET_ROW_CSS: string = "div[widgetId='editor.widget.suggestWidget'] .monaco-list-row"; - - private getEditorLineXpathLocator(lineNumber: number): string{ + + private getEditorLineXpathLocator(lineNumber: number): string { return `(//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line'])[${lineNumber}]` } - async waitSuggestionContainer(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitSuggestionContainer(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Editor.SUGGESTION_WIDGET_BODY_CSS), timeout) } - async waitSuggestionContainerClosed(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitSuggestionContainerClosed(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(Editor.SUGGESTION_WIDGET_BODY_CSS), attempts, polling) } @@ -43,41 +43,41 @@ export class Editor { return `//li[contains(@class, 'p-TabBar-tab')]//div[text()='${tabTitle}']`; } - async waitTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.xpath(this.getTabXpathLocator(tabTitle)), timeout) } - async waitTabDisappearance(tabTitle: string, attempt = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitTabDisappearance(tabTitle: string, attempt = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.xpath(this.getTabXpathLocator(tabTitle)), attempt, polling) } - async clickOnTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.xpath(this.getTabXpathLocator(tabTitle)), timeout) } - async waitTabFocused(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitTabFocused(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const focusedTabLocator: By = By.xpath(`//li[contains(@class, 'p-TabBar-tab') and contains(@class, 'theia-mod-active')]//div[text()='${tabTitle}']`) await this.driverHelper.waitVisibility(focusedTabLocator, timeout) - + // wait for increasing stability await this.driverHelper.wait(2000) } - async closeTab(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async closeTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const tabCloseButtonLocator: By = By.xpath(`//div[text()='${tabTitle}']/parent::li//div[contains(@class, 'p-TabBar-tabCloseIcon')]`) await this.driverHelper.waitAndClick(tabCloseButtonLocator, timeout) } - - async waitEditorOpened(timeout = TestConstants.DEFAULT_TIMEOUT) { + + async waitEditorOpened(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const firstEditorLineLocator: By = By.xpath(this.getEditorLineXpathLocator(1)) await this.driverHelper.waitVisibility(By.css(Editor.EDITOR_BODY_CSS), timeout) await this.driverHelper.waitVisibility(firstEditorLineLocator, timeout) } - async waitEditorAvailable(tabTitle: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitEditorAvailable(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.waitTab(tabTitle, timeout); await this.waitEditorOpened(timeout); } diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index 9ddb37b60a8..c4e9bb83439 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -36,28 +36,28 @@ export class Ide { this.testWorkspaceUtil = testWorkspaceUtil; } - async waitAndSwitchToIdeFrame(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitAndSwitchToIdeFrame(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) } - async waitNotification(notificationMessage: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitNotification(notificationMessage: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const notificationLocator: By = By.css(`div[id='notification-container-3-${notificationMessage}-|']`) await this.driverHelper.waitVisibility(notificationLocator, timeout) } - async waitNotificationDisappearance(notificationMessage: string, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitNotificationDisappearance(notificationMessage: string, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { const notificationLocator: By = By.css(`div[id='notification-container-3-${notificationMessage}-|']`) await this.driverHelper.waitDisappearance(notificationLocator, attempts, polling) } - async waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string, timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string, timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.testWorkspaceUtil.waitRunningStatus(workspaceNamespace, workspaceName) await this.waitIde(timeout) } - async waitIde(timeout = TestConstants.LOAD_PAGE_TIMEOUT) { + async waitIde(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { const mainIdeParts: Array = [By.css(Ide.TOP_MENU_PANEL_CSS), By.css(Ide.LEFT_CONTENT_PANEL_CSS), By.xpath(Ide.EXPLORER_BUTTON_XPATH)] for (const idePartLocator of mainIdeParts) { @@ -65,27 +65,27 @@ export class Ide { } } - async waitExplorerButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitExplorerButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.xpath(Ide.EXPLORER_BUTTON_XPATH), timeout) } - async clickOnExplorerButton(timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnExplorerButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.xpath(Ide.EXPLORER_BUTTON_XPATH), timeout) } - async waitTopMenuPanel(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitTopMenuPanel(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Ide.TOP_MENU_PANEL_CSS), timeout) } - async waitLeftContentPanel(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitLeftContentPanel(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Ide.LEFT_CONTENT_PANEL_CSS)) } - async waitPreloaderAbsent(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitPreloaderAbsent(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(Ide.PRELOADER_CSS), attempts, polling) } - async waitStatusBarContains(expectedText: string, timeout = TestConstants.LANGUAGE_SERVER_INITIALIZATION_TIMEOUT) { + async waitStatusBarContains(expectedText: string, timeout = TestConstants.TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT) { const statusBarLocator: By = By.css("div[id='theia-statusBar']") await this.driverHelper.waitUntilTrue(async () => { @@ -96,7 +96,7 @@ export class Ide { }, timeout) } - async waitStatusBarTextAbcence(expectedText: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitStatusBarTextAbcence(expectedText: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const statusBarLocator: By = By.css("div[id='theia-statusBar']") await this.driverHelper.waitUntilTrue(async () => { @@ -108,8 +108,8 @@ export class Ide { } - async waitIdeFrameAndSwitchOnIt(timeout = TestConstants.LOAD_PAGE_TIMEOUT){ - await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) + async waitIdeFrameAndSwitchOnIt(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) } } diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index 463b63b9d5e..9ddc4768544 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -52,7 +52,7 @@ export class ProjectTree { - async openProjectTreeContainer(timeout = TestConstants.DEFAULT_TIMEOUT) { + async openProjectTreeContainer(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const selectedExplorerButtonLocator: By = By.xpath(Ide.SELECTED_EXPLORER_BUTTON_XPATH) await this.ide.waitExplorerButton(timeout) @@ -66,42 +66,42 @@ export class ProjectTree { await this.waitProjectTreeContainer(); } - async waitItemExpanded(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitItemExpanded(itemPath: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const expandedItemLocator: By = By.css(this.getExpandedItemCssLocator(itemPath)) await this.driverHelper.waitVisibility(expandedItemLocator, timeout) } - async waitItemCollapsed(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitItemCollapsed(itemPath: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const collapsedItemLocator: By = By.css(this.getCollapsedItemCssLocator(itemPath)) await this.driverHelper.waitVisibility(collapsedItemLocator, timeout) } - async waitProjectTreeContainer(timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitProjectTreeContainer(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(ProjectTree.PROJECT_TREE_CONTAINER_CSS), timeout) } - async waitProjectTreeContainerClosed(attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitProjectTreeContainerClosed(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(ProjectTree.PROJECT_TREE_CONTAINER_CSS), attempts, polling) } - async waitItemDisappearance(itemPath: string, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + async waitItemDisappearance(itemPath: string, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(this.getItemCss(itemPath)), attempts, polling) } - async clickOnItem(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async clickOnItem(itemPath: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(this.getItemCss(itemPath)), timeout) await this.waitItemSelected(itemPath, timeout); } - async waitItemSelected(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async waitItemSelected(itemPath: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const selectedItemLocator: By = By.css(`div[title='/projects/${itemPath}'].theia-mod-selected.theia-mod-focus`); await this.driverHelper.waitVisibility(selectedItemLocator, timeout) } - async expandItem(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async expandItem(itemPath: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const expandIconLocator: By = By.css(this.getExpandIconCssLocator(itemPath)) const treeItemLocator: By = By.css(this.getTreeItemCssLocator(itemPath)) @@ -116,7 +116,7 @@ export class ProjectTree { await this.waitItemExpanded(itemPath, timeout); } - async collapseItem(itemPath: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async collapseItem(itemPath: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const expandIconLocator: By = By.css(this.getExpandIconCssLocator(itemPath)) const treeItemLocator: By = By.css(this.getTreeItemCssLocator(itemPath)) @@ -130,7 +130,7 @@ export class ProjectTree { await this.waitItemCollapsed(itemPath, timeout); } - async expandPathAndOpenFile(pathToItem: string, fileName: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + async expandPathAndOpenFile(pathToItem: string, fileName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { let currentPath: string = ""; let paths: Array = new Array(); @@ -150,15 +150,15 @@ export class ProjectTree { await this.clickOnItem(`${pathToItem}/${fileName}`, timeout) } - async waitProjectImported(projectName: string, rootSubItem: string, attempts = TestConstants.DEFAULT_ATTEMPTS, - visibilityItemPolling = TestConstants.DEFAULT_POLLING * 5, triesPolling = TestConstants.DEFAULT_POLLING * 30) { + async waitProjectImported(projectName: string, rootSubItem: string, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, + visibilityItemPolling = TestConstants.TS_SELENIUM_DEFAULT_POLLING * 5, triesPolling = TestConstants.TS_SELENIUM_DEFAULT_POLLING * 30) { const rootItem: string = `/${projectName}`; const rootItemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}`)); const rootSubitemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}/${rootSubItem}`)); - + for (let i = 0; i < attempts; i++) { const isProjectFolderVisible = await this.driverHelper.waitVisibilityBoolean(rootItemLocator, attempts, visibilityItemPolling) diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 3dbbcfbf75d..f1532c175bb 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -40,7 +40,7 @@ export class DriverHelper { await this.driver.sleep(miliseconds) } - public async waitVisibilityBoolean(locator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { + public async waitVisibilityBoolean(locator: By, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING): Promise { for (let i = 0; i < attempts; i++) { const isVisible: boolean = await this.isVisible(locator); @@ -54,7 +54,7 @@ export class DriverHelper { return false; } - public async waitDisappearanceBoolean(locator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { + public async waitDisappearanceBoolean(locator: By, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING): Promise { for (let i = 0; i < attempts; i++) { const isVisible: boolean = await this.isVisible(locator) @@ -68,9 +68,9 @@ export class DriverHelper { return false; } - public async waitVisibility(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): Promise { - const attempts: number = TestConstants.DEFAULT_ATTEMPTS - const polling: number = TestConstants.DEFAULT_POLLING + public async waitVisibility(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING for (let i = 0; i < attempts; i++) { const webElement: WebElement = await this.driver.wait(until.elementLocated(elementLocator), timeout) @@ -93,13 +93,13 @@ export class DriverHelper { throw new Error(`Exceeded maximum visibility checkings attempts, problems with 'StaleElementReferenceError' of '${elementLocator}' element`) } - public async waitAllVisibility(locators: Array, timeout = TestConstants.DEFAULT_TIMEOUT) { + public async waitAllVisibility(locators: Array, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { for (const elementLocator of locators) { await this.waitVisibility(elementLocator, timeout) } } - public async waitDisappearance(elementLocator: By, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING) { + public async waitDisappearance(elementLocator: By, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { const isDisappeared = await this.waitDisappearanceBoolean(elementLocator, attempts, polling) if (!isDisappeared) { @@ -107,15 +107,15 @@ export class DriverHelper { } } - public async waitAllDisappearance(locators: Array, attempts = TestConstants.DEFAULT_ATTEMPTS, polling = TestConstants.DEFAULT_POLLING): Promise { + public async waitAllDisappearance(locators: Array, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING): Promise { for (const elementLocator of locators) { await this.waitDisappearance(elementLocator, attempts, polling) } } - public async waitAndClick(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { - const attempts: number = TestConstants.DEFAULT_ATTEMPTS - const polling: number = TestConstants.DEFAULT_POLLING + public async waitAndClick(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING for (let i = 0; i < attempts; i++) { const element: WebElement = await this.waitVisibility(elementLocator, timeout) @@ -133,9 +133,9 @@ export class DriverHelper { } - public async waitAndGetElementAttribute(elementLocator: By, attribute: string, visibilityTimeout = TestConstants.DEFAULT_TIMEOUT): Promise { - const attempts: number = TestConstants.DEFAULT_ATTEMPTS - const polling: number = TestConstants.DEFAULT_POLLING + public async waitAndGetElementAttribute(elementLocator: By, attribute: string, visibilityTimeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING for (let i = 0; i < attempts; i++) { @@ -152,7 +152,7 @@ export class DriverHelper { throw new Error(`Exceeded maximum gettin of the '${attribute}' attribute attempts, from the '${elementLocator}' element`) } - public async waitAttributeValue(elementLocator: By, attribute: string, expectedValue: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + public async waitAttributeValue(elementLocator: By, attribute: string, expectedValue: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driver.wait(async () => { const attributeValue: string = await this.waitAndGetElementAttribute(elementLocator, attribute, timeout) @@ -163,9 +163,9 @@ export class DriverHelper { } - public async type(elementLocator: By, text: string, timeout = TestConstants.DEFAULT_TIMEOUT) { - const attempts: number = TestConstants.DEFAULT_ATTEMPTS - const polling: number = TestConstants.DEFAULT_POLLING + public async type(elementLocator: By, text: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING for (let i = 0; i < attempts; i++) { @@ -183,9 +183,9 @@ export class DriverHelper { throw new Error(`Exceeded maximum typing attempts, to the '${elementLocator}' element`) } - public async clear(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { - const attempts: number = TestConstants.DEFAULT_ATTEMPTS - const polling: number = TestConstants.DEFAULT_POLLING + public async clear(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING for (let i = 0; i < attempts; i++) { @@ -203,7 +203,7 @@ export class DriverHelper { throw new Error(`Exceeded maximum clearing attempts, to the '${elementLocator}' element`) } - public async enterValue(elementLocator: By, text: string, timeout = TestConstants.DEFAULT_TIMEOUT) { + public async enterValue(elementLocator: By, text: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.waitVisibility(elementLocator, timeout) await this.clear(elementLocator, timeout) await this.waitAttributeValue(elementLocator, "value", "", timeout) @@ -211,13 +211,13 @@ export class DriverHelper { await this.waitAttributeValue(elementLocator, "value", text, timeout) } - public async waitAndSwitchToFrame(iframeLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT) { + public async waitAndSwitchToFrame(iframeLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { this.driver.wait(until.ableToSwitchToFrame(iframeLocator), timeout) } - public async waitAndGetText(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): Promise { - const attempts: number = TestConstants.DEFAULT_ATTEMPTS - const polling: number = TestConstants.DEFAULT_POLLING + public async waitAndGetText(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING for (let i = 0; i < attempts; i++) { @@ -235,12 +235,12 @@ export class DriverHelper { throw new Error(`Exceeded maximum text obtaining attempts, from the '${elementLocator}' element`) } - public async waitAndGetValue(elementLocator: By, timeout = TestConstants.DEFAULT_TIMEOUT): Promise { + public async waitAndGetValue(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { const elementValue: string = await this.waitAndGetElementAttribute(elementLocator, 'value', timeout) return elementValue } - public async waitUntilTrue(callback: any, timeout = TestConstants.DEFAULT_TIMEOUT) { + public async waitUntilTrue(callback: any, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driver.wait(callback(), timeout) } diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 486b65ff5ec..94924c75c19 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ - import { TestConstants } from '../../TestConstants'; +import { TestConstants } from '../../TestConstants'; import { injectable, inject } from 'inversify'; import { DriverHelper } from '../DriverHelper'; import { CLASSES } from '../../types'; @@ -26,9 +26,9 @@ export class TestWorkspaceUtil { } public async waitRunningStatus(workspaceNamespace: string, workspaceName: string) { - const workspaceStatusApiUrl: string = `${TestConstants.BASE_URL}/api/workspace/${workspaceNamespace}:${workspaceName}`; - const attempts: number = TestConstants.WORKSPACE_STATUS_ATTEMPTS; - const polling: number = TestConstants.WORKSPACE_STATUS_POLLING; + const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${workspaceNamespace}:${workspaceName}`; + const attempts: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS; + const polling: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING; const runningWorkspaceStatus: string = 'RUNNING'; const stoppedWorkspaceStatus: string = 'STOPPED'; const startingWorkspaceStatus: string = 'STARTING'; @@ -37,7 +37,7 @@ export class TestWorkspaceUtil { for (let i = 0; i < attempts; i++) { let isWorkspaceStarting: boolean = false; - + const response: rm.IRestResponse = await rest.get(workspaceStatusApiUrl) if (response.statusCode !== 200) { From 6f248435364af2b8ab0deaf51e59e06e9919e995 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 19 Apr 2019 10:42:34 +0300 Subject: [PATCH 15/57] Add tests folder and create pattern for launch it Signed-off-by: Ihor Okhrimenko --- typescript-selenium/package.json | 2 +- .../workspace-details/WorkspaceDetails.ts | 2 +- .../{spec.ts => tests/HappyPath.spec.ts} | 36 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) rename typescript-selenium/{spec.ts => tests/HappyPath.spec.ts} (84%) diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 9eef2140283..7bd73a5630e 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --require ts-node/register --timeout 1200000 -u tdd spec.ts", + "test": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec tests/*.spec.ts", "units": "mocha --require ts-node/register --timeout 1200000 -u tdd driverHelperUnitTests.spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index bac486b47da..e54d3536297 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -96,7 +96,7 @@ export class WorkspaceDetails { async waitTabsPresence(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { const workspaceDetailsTabs: Array = ["Overview", "Projects", "Containers", "Servers", - "Env Variables", "Volumes", "Config", "SSH", "Tools", "Plugins"]; + "Env Variables", "Volumes", "Config", "SSH", "Plugins", "Editors"]; for (const tabTitle of workspaceDetailsTabs) { const workspaceDetailsTabLocator: By = By.xpath(this.getTabXpathLocator(tabTitle)) diff --git a/typescript-selenium/spec.ts b/typescript-selenium/tests/HappyPath.spec.ts similarity index 84% rename from typescript-selenium/spec.ts rename to typescript-selenium/tests/HappyPath.spec.ts index 05a207afa97..ca519c637df 100644 --- a/typescript-selenium/spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -8,26 +8,26 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { e2eContainer } from "./inversify.config"; -import { Driver } from "./driver/Driver"; -import { TYPES, CLASSES } from "./types"; -import { DriverHelper } from "./utils/DriverHelper"; +import { e2eContainer } from "../inversify.config"; +import { Driver } from "../driver/Driver"; +import { TYPES, CLASSES } from "../types"; +import { DriverHelper } from "../utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; import { describe, after } from "mocha"; -import { LoginPage } from "./pageobjects/login/LoginPage"; -import { Dashboard } from "./pageobjects/dashboard/Dashboard"; +import { LoginPage } from "../pageobjects/login/LoginPage"; +import { Dashboard } from "../pageobjects/dashboard/Dashboard"; import { expect, assert } from 'chai' -import { Workspaces } from "./pageobjects/dashboard/Workspaces"; -import { NameGenerator } from "./utils/NameGenerator"; -import { NewWorkspace } from "./pageobjects/dashboard/NewWorkspace"; -import { WorkspaceDetails } from "./pageobjects/dashboard/workspace-details/WorkspaceDetails"; -import { WorkspaceDetailsPlugins } from "./pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; +import { Workspaces } from "../pageobjects/dashboard/Workspaces"; +import { NameGenerator } from "../utils/NameGenerator"; +import { NewWorkspace } from "../pageobjects/dashboard/NewWorkspace"; +import { WorkspaceDetails } from "../pageobjects/dashboard/workspace-details/WorkspaceDetails"; +import { WorkspaceDetailsPlugins } from "../pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; import { Request, post, get } from "selenium-webdriver/http"; -import { TestWorkspaceUtil } from "./utils/workspace/TestWorkspaceUtil"; -import { Ide } from "./pageobjects/ide/Ide"; -import { ProjectTree } from "./pageobjects/ide/ProjectTree"; -import { Editor } from "./pageobjects/ide/Editor"; -import { TestConstants } from "./TestConstants"; +import { TestWorkspaceUtil } from "../utils/workspace/TestWorkspaceUtil"; +import { Ide } from "../pageobjects/ide/Ide"; +import { ProjectTree } from "../pageobjects/ide/ProjectTree"; +import { Editor } from "../pageobjects/ide/Editor"; +import { TestConstants } from "../TestConstants"; const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; @@ -78,10 +78,10 @@ suite("E2E", async () => { await newWorkspace.clickOnAddButton() await newWorkspace.waitProjectAdding(sampleName) - await newWorkspace.clickOnCreateAndOpenButton() + await newWorkspace.selectCreateWorkspaceAndProceedEditing() }) - test.skip("Add 'Java Language Support' plugin to workspace", async () => { + test("Add 'Java Language Support' plugin to workspace", async () => { const javaPluginName: string = "Language Support for Java(TM)"; const execPlugin: string = "Che machine-exec Service"; From cc8becadca43f35d65fe2abad6dc8378107aca09 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 19 Apr 2019 15:56:19 +0300 Subject: [PATCH 16/57] Do base URL configurable Signed-off-by: Ihor Okhrimenko --- typescript-selenium/pageobjects/login/SingleUserLoginPage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index 6ced883b93a..9f0bcf1ea8b 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -4,6 +4,7 @@ import { injectable, inject } from "inversify"; import { ThenableWebDriver } from "selenium-webdriver"; import { TYPES } from "../../types"; import { Driver } from "../../driver/Driver"; +import { TestConstants } from "../../TestConstants"; @injectable() export class SingleUserLoginPage implements LoginPage { @@ -19,7 +20,7 @@ export class SingleUserLoginPage implements LoginPage { async login() { await this.driver .navigate() - .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace") + .to(TestConstants.TS_SELENIUM_BASE_URL) } } \ No newline at end of file From 124e0cb2aad165eb6cdb16cf581b29613f7e9d26 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 19 Apr 2019 18:04:23 +0300 Subject: [PATCH 17/57] Update the 'DriverHelper' class Signed-off-by: Ihor Okhrimenko --- typescript-selenium/utils/DriverHelper.ts | 53 +++++++++++++++-------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index f1532c175bb..be7faf13a76 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -76,12 +76,10 @@ export class DriverHelper { const webElement: WebElement = await this.driver.wait(until.elementLocated(elementLocator), timeout) try { - return await this.driver.wait(until.elementIsVisible(webElement), timeout) + const visibleWebElement = await this.driver.wait(until.elementIsVisible(webElement), timeout) + return visibleWebElement } catch (err) { if (err instanceof error.StaleElementReferenceError) { - - console.log("==>>> 'waitVisibility' catched exception") - await this.wait(polling) continue; } @@ -124,8 +122,12 @@ export class DriverHelper { await element.click(); return; } catch (err) { - await this.wait(polling) - continue; + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err } } @@ -142,10 +144,15 @@ export class DriverHelper { const element: WebElement = await this.waitVisibility(elementLocator, visibilityTimeout); try { - return await element.getAttribute(attribute) + const attributeValue = await element.getAttribute(attribute) + return attributeValue } catch (err) { - await this.wait(polling) - continue + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err } } @@ -175,8 +182,12 @@ export class DriverHelper { await element.sendKeys(text) return } catch (err) { - await this.wait(polling) - continue + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err } } @@ -195,8 +206,12 @@ export class DriverHelper { await element.clear() return } catch (err) { - await this.wait(polling) - continue + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err } } @@ -212,7 +227,7 @@ export class DriverHelper { } public async waitAndSwitchToFrame(iframeLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { - this.driver.wait(until.ableToSwitchToFrame(iframeLocator), timeout) + await this.driver.wait(until.ableToSwitchToFrame(iframeLocator), timeout) } public async waitAndGetText(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { @@ -227,8 +242,12 @@ export class DriverHelper { const innerText: string = await element.getText() return innerText } catch (err) { - await this.wait(polling) - continue + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err } } @@ -252,6 +271,4 @@ export class DriverHelper { await this.driver.navigate().to(url) } - - } From cbffa2f261c7ef655f6eef5ea62ff8fe6611b5fd Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 22 Apr 2019 10:39:55 +0300 Subject: [PATCH 18/57] Add 'mocha.opts' supporting Signed-off-by: Ihor Okhrimenko --- .../dist/driver/ChromeDriver.js | 35 ------ typescript-selenium/dist/driver/Driver.js | 2 - typescript-selenium/dist/inversify.config.js | 10 -- .../dist/pageobjects/dashboard/Dashboard.js | 42 ------- typescript-selenium/dist/spec.js | 69 ---------- typescript-selenium/dist/types.js | 6 - .../dist/utils/DriverHelper.js | 118 ------------------ typescript-selenium/mocha.opts | 5 + typescript-selenium/package.json | 2 +- 9 files changed, 6 insertions(+), 283 deletions(-) delete mode 100644 typescript-selenium/dist/driver/ChromeDriver.js delete mode 100644 typescript-selenium/dist/driver/Driver.js delete mode 100644 typescript-selenium/dist/inversify.config.js delete mode 100644 typescript-selenium/dist/pageobjects/dashboard/Dashboard.js delete mode 100644 typescript-selenium/dist/spec.js delete mode 100644 typescript-selenium/dist/types.js delete mode 100644 typescript-selenium/dist/utils/DriverHelper.js create mode 100644 typescript-selenium/mocha.opts diff --git a/typescript-selenium/dist/driver/ChromeDriver.js b/typescript-selenium/dist/driver/ChromeDriver.js deleted file mode 100644 index b7828ebadbb..00000000000 --- a/typescript-selenium/dist/driver/ChromeDriver.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -require("chromedriver"); -require("reflect-metadata"); -var inversify_1 = require("inversify"); -var selenium_webdriver_1 = require("selenium-webdriver"); -var ChromeDriver = /** @class */ (function () { - function ChromeDriver() { - this.driver = new selenium_webdriver_1.Builder() - .forBrowser('chrome') - .build(); - this.driver - .manage() - .window() - .setSize(1920, 1080); - } - ChromeDriver.prototype.get = function () { - return this.driver; - }; - ChromeDriver = __decorate([ - inversify_1.injectable(), - __metadata("design:paramtypes", []) - ], ChromeDriver); - return ChromeDriver; -}()); -exports.ChromeDriver = ChromeDriver; diff --git a/typescript-selenium/dist/driver/Driver.js b/typescript-selenium/dist/driver/Driver.js deleted file mode 100644 index c8ad2e549bd..00000000000 --- a/typescript-selenium/dist/driver/Driver.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/typescript-selenium/dist/inversify.config.js b/typescript-selenium/dist/inversify.config.js deleted file mode 100644 index 4df7d719650..00000000000 --- a/typescript-selenium/dist/inversify.config.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var inversify_1 = require("inversify"); -var types_1 = require("./types"); -var ChromeDriver_1 = require("./driver/ChromeDriver"); -var DriverHelper_1 = require("./utils/DriverHelper"); -var e2eContainer = new inversify_1.Container(); -exports.e2eContainer = e2eContainer; -e2eContainer.bind(types_1.TYPES.Driver).to(ChromeDriver_1.ChromeDriver).inSingletonScope(); -e2eContainer.bind('DriverHelper').to(DriverHelper_1.DriverHelper); diff --git a/typescript-selenium/dist/pageobjects/dashboard/Dashboard.js b/typescript-selenium/dist/pageobjects/dashboard/Dashboard.js deleted file mode 100644 index ed5f40bf86c..00000000000 --- a/typescript-selenium/dist/pageobjects/dashboard/Dashboard.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var inversify_1 = require("inversify"); -require("reflect-metadata"); -var types_1 = require("../../types"); -/********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - **********************************************************************/ -var Dashboard = /** @class */ (function () { - function Dashboard(driver) { - this.driver = driver; - } - Dashboard.DASHBOARD_BUTTON = "#dashboard-item"; - Dashboard.WORKSPACES_BUTTON = "#workspaces-item"; - Dashboard.STACKS_BUTTON = "#stacks-item"; - Dashboard.FACTORIES_BUTTON = "#factories-item"; - Dashboard.LOADER_PAGE = ".main-page-loader"; - Dashboard = __decorate([ - __param(0, inversify_1.inject(types_1.TYPES.Driver)), - __metadata("design:paramtypes", [Object]) - ], Dashboard); - return Dashboard; -}()); -exports.Dashboard = Dashboard; diff --git a/typescript-selenium/dist/spec.js b/typescript-selenium/dist/spec.js deleted file mode 100644 index a5fa8263b38..00000000000 --- a/typescript-selenium/dist/spec.js +++ /dev/null @@ -1,69 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var inversify_config_1 = require("./inversify.config"); -var types_1 = require("./types"); -var driver = inversify_config_1.e2eContainer.get(types_1.TYPES.Driver); -var driverHelper = inversify_config_1.e2eContainer.get('DriverHelper'); -function doNavigation() { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, driver.get() - .navigate() - .to("http://che-che.192.168.99.100.nip.io/dashboard/#/create-workspace")]; - case 1: - _a.sent(); - return [2 /*return*/]; - } - }); - }); -} -function ttt() { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, driverHelper.waitVisibility("sssss")]; - case 1: - _a.sent(); - return [2 /*return*/]; - } - }); - }); -} -doNavigation(); -ttt(); diff --git a/typescript-selenium/dist/types.js b/typescript-selenium/dist/types.js deleted file mode 100644 index 2d9ae04c41b..00000000000 --- a/typescript-selenium/dist/types.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var TYPES = { - Driver: Symbol.for("Driver") -}; -exports.TYPES = TYPES; diff --git a/typescript-selenium/dist/utils/DriverHelper.js b/typescript-selenium/dist/utils/DriverHelper.js deleted file mode 100644 index 9b732a304d5..00000000000 --- a/typescript-selenium/dist/utils/DriverHelper.js +++ /dev/null @@ -1,118 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var inversify_1 = require("inversify"); -var types_1 = require("../types"); -require("selenium-webdriver"); -require("reflect-metadata"); -var selenium_webdriver_1 = require("selenium-webdriver"); -/********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - **********************************************************************/ -var DriverHelper = /** @class */ (function () { - function DriverHelper(driver) { - this.driver = driver.get(); - } - DriverHelper.prototype.findElement = function (locator) { - return this.driver.findElement(locator); - }; - DriverHelper.prototype.isVisible = function (locator) { - try { - return this.findElement(locator).isDisplayed(); - } - catch (err) { - return new selenium_webdriver_1.promise.Promise(function (resolve) { resolve(false); }); - } - }; - DriverHelper.prototype.wait = function (miliseconds) { - return new selenium_webdriver_1.promise.Promise(function (resolve) { setTimeout(resolve, miliseconds); }); - }; - DriverHelper.prototype.waitVisibility = function (locator) { - return __awaiter(this, void 0, void 0, function () { - var i; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - i = 0; - _a.label = 1; - case 1: - if (!(i < 10)) return [3 /*break*/, 5]; - return [4 /*yield*/, this.isVisible(locator)]; - case 2: - if (_a.sent()) { - console.log("===>>>> visible"); - return [2 /*return*/]; - } - console.log("===>>>> wait"); - return [4 /*yield*/, this.wait(5000)]; - case 3: - _a.sent(); - _a.label = 4; - case 4: - i++; - return [3 /*break*/, 1]; - case 5: return [2 /*return*/]; - } - }); - }); - }; - DriverHelper = __decorate([ - inversify_1.injectable(), - __param(0, inversify_1.inject(types_1.TYPES.Driver)), - __metadata("design:paramtypes", [Object]) - ], DriverHelper); - return DriverHelper; -}()); -exports.DriverHelper = DriverHelper; diff --git a/typescript-selenium/mocha.opts b/typescript-selenium/mocha.opts new file mode 100644 index 00000000000..5b1b429d42d --- /dev/null +++ b/typescript-selenium/mocha.opts @@ -0,0 +1,5 @@ +# mocha.opts + --require ts-node/register + --timeout 1200000 + -u tdd + --spec tests/*.spec.ts diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 7bd73a5630e..cd2efc3c54c 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec tests/*.spec.ts", + "test": "mocha --opts ./mocha.opts", "units": "mocha --require ts-node/register --timeout 1200000 -u tdd driverHelperUnitTests.spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", From 683039917230a396600f147c40659c8666740700 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 22 Apr 2019 12:12:18 +0300 Subject: [PATCH 19/57] Add 'headless' mode supporting Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 6 ++++-- typescript-selenium/driver/ChromeDriver.ts | 21 +++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index b63673d7fac..f9aa233acd1 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -8,9 +8,9 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -function readEnvAndSetValue(envProperty: any, defaultValue: string | number): any{ +function readEnvAndSetValue(envProperty: any, defaultValue: string | number | boolean): any { const propertyValue = envProperty - if(propertyValue === undefined || propertyValue === null){ + if (propertyValue === undefined || propertyValue === null) { return defaultValue; } @@ -20,6 +20,8 @@ function readEnvAndSetValue(envProperty: any, defaultValue: string | number): an export const TestConstants = { + TS_SELENIUM_HEADLESS: readEnvAndSetValue(process.env.TS_SELENIUM_HEADLESS, false), + TS_SELENIUM_START_WORKSPACE_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_START_WORKSPACE_TIMEOUT, 240000), TS_SELENIUM_LOAD_PAGE_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_LOAD_PAGE_TIMEOUT, 120000), TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT, 180000), diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts index c6ede02f776..99f3adbef49 100644 --- a/typescript-selenium/driver/ChromeDriver.ts +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -3,15 +3,28 @@ import 'reflect-metadata'; import { injectable, inject } from "inversify"; import { ThenableWebDriver, Builder } from "selenium-webdriver"; import { Driver } from './Driver'; +import { Options } from 'selenium-webdriver/chrome'; +import { TestConstants } from '../TestConstants'; @injectable() export class ChromeDriver implements Driver { private readonly driver: ThenableWebDriver; constructor() { - this.driver = new Builder() - .forBrowser('chrome') - .build(); + const isHeadless: boolean = TestConstants.TS_SELENIUM_HEADLESS; + + + if (isHeadless) { + this.driver = new Builder() + .forBrowser('chrome') + .setChromeOptions(new Options().addArguments('--no-sandbox').addArguments('headless')) + .build(); + } else { + this.driver = new Builder() + .forBrowser('chrome') + .setChromeOptions(new Options().addArguments('--no-sandbox')) + .build(); + } this.driver .manage() @@ -19,7 +32,7 @@ export class ChromeDriver implements Driver { .setSize(1920, 1080) } - get(): ThenableWebDriver{ + get(): ThenableWebDriver { return this.driver } From 3473987458a24c4ce9e1beef670440e8b87b4264 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 22 Apr 2019 13:03:25 +0300 Subject: [PATCH 20/57] Add licence headers Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/ChromeDriver.ts | 9 +++++++++ typescript-selenium/driver/Driver.ts | 1 - typescript-selenium/inversify.config.ts | 9 +++++++++ typescript-selenium/mocha.opts | 10 ++++++++++ .../pageobjects/dashboard/Dashboard.ts | 15 +++++++-------- .../pageobjects/dashboard/NewWorkspace.ts | 15 +++++++-------- .../workspace-details/WorkspaceDetails.ts | 14 ++++++-------- .../workspace-details/WorkspaceDetailsPlugins.ts | 14 +++++++------- typescript-selenium/pageobjects/ide/Ide.ts | 14 +++++++------- .../pageobjects/login/LoginPage.ts | 10 ++++++++++ .../pageobjects/login/SingleUserLoginPage.ts | 10 ++++++++++ typescript-selenium/utils/DriverHelper.ts | 16 ++++++++-------- 12 files changed, 90 insertions(+), 47 deletions(-) diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts index 99f3adbef49..e11b711b42e 100644 --- a/typescript-selenium/driver/ChromeDriver.ts +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -1,3 +1,12 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ import 'chromedriver'; import 'reflect-metadata'; import { injectable, inject } from "inversify"; diff --git a/typescript-selenium/driver/Driver.ts b/typescript-selenium/driver/Driver.ts index 3dceb71553b..ce159f6535a 100644 --- a/typescript-selenium/driver/Driver.ts +++ b/typescript-selenium/driver/Driver.ts @@ -7,7 +7,6 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ - import { ThenableWebDriver } from "selenium-webdriver"; export interface Driver { diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 844fba7593f..c857d96d95e 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -1,3 +1,12 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ import { Container } from "inversify"; import { Driver } from "./driver/Driver"; import { TYPES, CLASSES } from "./types"; diff --git a/typescript-selenium/mocha.opts b/typescript-selenium/mocha.opts index 5b1b429d42d..80f5cbdae00 100644 --- a/typescript-selenium/mocha.opts +++ b/typescript-selenium/mocha.opts @@ -1,3 +1,13 @@ +#********************************************************************* + # Copyright (c) 2018 Red Hat, Inc. + # + # This program and the accompanying materials are made + # available under the terms of the Eclipse Public License 2.0 + # which is available at https://www.eclipse.org/legal/epl-2.0/ + # + # SPDX-License-Identifier: EPL-2.0 +#********************************************************************** + # mocha.opts --require ts-node/register --timeout 1200000 diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 78b05654fc1..4abf3972541 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -1,11 +1,3 @@ -import { inject, injectable } from "inversify"; -import "reflect-metadata"; -import { TYPES, CLASSES } from "../../types"; -import { Driver } from "../../driver/Driver"; -import { WebElementCondition, By } from "selenium-webdriver"; -import { DriverHelper } from "../../utils/DriverHelper"; -import { TestConstants } from "../../TestConstants"; - /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. * @@ -15,6 +7,13 @@ import { TestConstants } from "../../TestConstants"; * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { inject, injectable } from "inversify"; +import "reflect-metadata"; +import { TYPES, CLASSES } from "../../types"; +import { Driver } from "../../driver/Driver"; +import { WebElementCondition, By } from "selenium-webdriver"; +import { DriverHelper } from "../../utils/DriverHelper"; +import { TestConstants } from "../../TestConstants"; @injectable() export class Dashboard { diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index 7a67eb0aff9..86a1aed81c9 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -1,11 +1,3 @@ -import { injectable, inject } from "inversify"; -import { DriverHelper } from "../../utils/DriverHelper"; -import { CLASSES } from "../../types"; -import { TestConstants } from "../../TestConstants"; -import { By } from "selenium-webdriver"; -import 'reflect-metadata'; - - /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. * @@ -15,6 +7,13 @@ import 'reflect-metadata'; * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { injectable, inject } from "inversify"; +import { DriverHelper } from "../../utils/DriverHelper"; +import { CLASSES } from "../../types"; +import { TestConstants } from "../../TestConstants"; +import { By } from "selenium-webdriver"; +import 'reflect-metadata'; + @injectable() export class NewWorkspace { diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index e54d3536297..9e73fa7f721 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -1,11 +1,3 @@ -import { DriverHelper } from "../../../utils/DriverHelper"; -import { injectable, inject } from "inversify"; -import { CLASSES } from "../../../types"; -import 'reflect-metadata'; -import { TestConstants } from "../../../TestConstants"; -import { By } from "selenium-webdriver"; - - /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. * @@ -15,6 +7,12 @@ import { By } from "selenium-webdriver"; * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { DriverHelper } from "../../../utils/DriverHelper"; +import { injectable, inject } from "inversify"; +import { CLASSES } from "../../../types"; +import 'reflect-metadata'; +import { TestConstants } from "../../../TestConstants"; +import { By } from "selenium-webdriver"; @injectable() diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts index 3c9a90bf6c3..acbdf216def 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -1,10 +1,3 @@ -import { DriverHelper } from "../../../utils/DriverHelper"; -import { injectable, inject } from "inversify"; -import 'reflect-metadata'; -import { CLASSES } from "../../../types"; -import { TestConstants } from "../../../TestConstants"; -import { By } from "selenium-webdriver"; - /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. * @@ -14,6 +7,13 @@ import { By } from "selenium-webdriver"; * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { DriverHelper } from "../../../utils/DriverHelper"; +import { injectable, inject } from "inversify"; +import 'reflect-metadata'; +import { CLASSES } from "../../../types"; +import { TestConstants } from "../../../TestConstants"; +import { By } from "selenium-webdriver"; + @injectable() export class WorkspaceDetailsPlugins { diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index c4e9bb83439..f510010a69e 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -1,10 +1,3 @@ -import { DriverHelper } from "../../utils/DriverHelper"; -import { injectable, inject } from "inversify"; -import { CLASSES } from "../../types"; -import { TestConstants } from "../../TestConstants"; -import { By } from "selenium-webdriver"; -import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; - /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. * @@ -14,6 +7,13 @@ import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { DriverHelper } from "../../utils/DriverHelper"; +import { injectable, inject } from "inversify"; +import { CLASSES } from "../../types"; +import { TestConstants } from "../../TestConstants"; +import { By } from "selenium-webdriver"; +import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; + @injectable() export class Ide { diff --git a/typescript-selenium/pageobjects/login/LoginPage.ts b/typescript-selenium/pageobjects/login/LoginPage.ts index d5372aa419e..a06e80a55fa 100644 --- a/typescript-selenium/pageobjects/login/LoginPage.ts +++ b/typescript-selenium/pageobjects/login/LoginPage.ts @@ -1,3 +1,13 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + export interface LoginPage { login(): void } diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index 9f0bcf1ea8b..323ae80d764 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -1,3 +1,12 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ import "reflect-metadata"; import { LoginPage } from "./LoginPage"; import { injectable, inject } from "inversify"; @@ -6,6 +15,7 @@ import { TYPES } from "../../types"; import { Driver } from "../../driver/Driver"; import { TestConstants } from "../../TestConstants"; + @injectable() export class SingleUserLoginPage implements LoginPage { private readonly driver: ThenableWebDriver; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index be7faf13a76..0a67d10b52f 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -1,11 +1,3 @@ -import { Driver } from "../driver/Driver"; -import { inject, injectable } from "inversify"; -import { TYPES } from "../types"; -import { error } from 'selenium-webdriver'; -import 'reflect-metadata'; -import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement, WebElementCondition } from "selenium-webdriver"; -import { TestConstants } from "../TestConstants"; - /********************************************************************* * Copyright (c) 2018 Red Hat, Inc. * @@ -15,6 +7,14 @@ import { TestConstants } from "../TestConstants"; * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { Driver } from "../driver/Driver"; +import { inject, injectable } from "inversify"; +import { TYPES } from "../types"; +import { error } from 'selenium-webdriver'; +import 'reflect-metadata'; +import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement, WebElementCondition } from "selenium-webdriver"; +import { TestConstants } from "../TestConstants"; + @injectable() export class DriverHelper { From 292d32e2215a58e55001ef133db3bd91ed889ad9 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 22 Apr 2019 14:39:40 +0300 Subject: [PATCH 21/57] Remove 'mocha.opts' config file Signed-off-by: Ihor Okhrimenko --- typescript-selenium/mocha.opts | 15 --------------- typescript-selenium/package.json | 4 ++-- .../DriverHelperUnitTests.spec.ts} | 14 +++++++------- 3 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 typescript-selenium/mocha.opts rename typescript-selenium/{driverHelperUnitTests.spec.ts => units/DriverHelperUnitTests.spec.ts} (89%) diff --git a/typescript-selenium/mocha.opts b/typescript-selenium/mocha.opts deleted file mode 100644 index 80f5cbdae00..00000000000 --- a/typescript-selenium/mocha.opts +++ /dev/null @@ -1,15 +0,0 @@ -#********************************************************************* - # Copyright (c) 2018 Red Hat, Inc. - # - # This program and the accompanying materials are made - # available under the terms of the Eclipse Public License 2.0 - # which is available at https://www.eclipse.org/legal/epl-2.0/ - # - # SPDX-License-Identifier: EPL-2.0 -#********************************************************************** - -# mocha.opts - --require ts-node/register - --timeout 1200000 - -u tdd - --spec tests/*.spec.ts diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index cd2efc3c54c..a8fa76d32b9 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --opts ./mocha.opts", - "units": "mocha --require ts-node/register --timeout 1200000 -u tdd driverHelperUnitTests.spec.ts" + "test": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec tests/*.spec.ts", + "units": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec units/*.spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", "license": "ISC", diff --git a/typescript-selenium/driverHelperUnitTests.spec.ts b/typescript-selenium/units/DriverHelperUnitTests.spec.ts similarity index 89% rename from typescript-selenium/driverHelperUnitTests.spec.ts rename to typescript-selenium/units/DriverHelperUnitTests.spec.ts index 33a3e617223..5e0dfcf0f22 100644 --- a/typescript-selenium/driverHelperUnitTests.spec.ts +++ b/typescript-selenium/units/DriverHelperUnitTests.spec.ts @@ -8,16 +8,16 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { e2eContainer } from "./inversify.config"; -import { Driver } from "./driver/Driver"; -import { TYPES, CLASSES } from "./types"; -import { DriverHelper } from "./utils/DriverHelper"; +import { e2eContainer } from "../inversify.config"; +import { Driver } from "../driver/Driver"; +import { TYPES, CLASSES } from "../types"; +import { DriverHelper } from "../utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; import { describe, after } from "mocha"; -import { LoginPage } from "./pageobjects/login/LoginPage"; -import { Dashboard } from "./pageobjects/dashboard/Dashboard"; +import { LoginPage } from "../pageobjects/login/LoginPage"; +import { Dashboard } from "../pageobjects/dashboard/Dashboard"; import { expect, assert } from 'chai' -import { Workspaces } from "./pageobjects/dashboard/Workspaces"; +import { Workspaces } from "../pageobjects/dashboard/Workspaces"; From 103fb746d9cdac135d4efbc95c0dbaa6992b5a3a Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 12:30:26 +0300 Subject: [PATCH 22/57] Intermediate screenshot version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/README.md | 0 typescript-selenium/mocha.opts | 5 ++ typescript-selenium/package.json | 2 +- typescript-selenium/reporter/CheReporter.ts | 65 +++++++++++++++++++ typescript-selenium/tests/HappyPath.spec.ts | 13 ++-- typescript-selenium/types.ts | 2 +- .../units/DriverHelperUnitTests.spec.ts | 7 +- 7 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 typescript-selenium/README.md create mode 100644 typescript-selenium/mocha.opts create mode 100644 typescript-selenium/reporter/CheReporter.ts diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/typescript-selenium/mocha.opts b/typescript-selenium/mocha.opts new file mode 100644 index 00000000000..3b0bd36dbab --- /dev/null +++ b/typescript-selenium/mocha.opts @@ -0,0 +1,5 @@ +--require ts-node/register +--timeout 1200000 +--reporter 'reporter/CheReporter.ts' +-u tdd +--spec tests/*.spec.ts diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index a8fa76d32b9..83148a59539 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec tests/*.spec.ts", + "test": "mocha --opts mocha.opts", "units": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec units/*.spec.ts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", diff --git a/typescript-selenium/reporter/CheReporter.ts b/typescript-selenium/reporter/CheReporter.ts new file mode 100644 index 00000000000..b83f08696e0 --- /dev/null +++ b/typescript-selenium/reporter/CheReporter.ts @@ -0,0 +1,65 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import * as mocha from 'mocha' +import { Driver } from '../driver/Driver'; +import { e2eContainer } from '../inversify.config'; +import { TYPES } from '../types'; +import * as fs from 'fs' +import * as path from 'path' +import { inject, injectable } from 'inversify'; +import { types } from 'util'; +import { ThenableWebDriver } from 'selenium-webdriver'; + +const driver: Driver = e2eContainer.get(TYPES.Driver); + +class CheReporter extends mocha.reporters.Spec { + + constructor(runner: mocha.Runner, options: mocha.MochaOptions) { + super(runner, options); + + runner.on('fail', async function (test) { + const fileName: string = `screenshot-${test.title}.png` + + let screenshot = await driver.get().takeScreenshot().catch(err => { + throw err + }); + + let stream = fs.createWriteStream(fileName) + + stream.write(new Buffer(screenshot, 'base64')) + stream.end() + + console.log("====>>>> The ", fileName, " file saved") + return + }) + } + } + + +// async saveScreenshot(test: mocha.Test): Promise { + +// const fileName: string = `screenshot-${test.title}.png` + +// let screenshot = await this.driver.takeScreenshot().catch(err => { +// throw err +// }); + +// let stream = fs.createWriteStream(fileName) + +// stream.write(new Buffer(screenshot, 'base64')) +// stream.end() + +// console.log("====>>>> The ", fileName, " file saved") +// return +// } + + +module.exports = CheReporter; + diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index ca519c637df..13063a9def9 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -13,7 +13,7 @@ import { Driver } from "../driver/Driver"; import { TYPES, CLASSES } from "../types"; import { DriverHelper } from "../utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; -import { describe, after } from "mocha"; +import { describe, after, test } from "mocha"; import { LoginPage } from "../pageobjects/login/LoginPage"; import { Dashboard } from "../pageobjects/dashboard/Dashboard"; import { expect, assert } from 'chai' @@ -28,6 +28,7 @@ import { Ide } from "../pageobjects/ide/Ide"; import { ProjectTree } from "../pageobjects/ide/ProjectTree"; import { Editor } from "../pageobjects/ide/Editor"; import { TestConstants } from "../TestConstants"; +import * as mocha from 'mocha' const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; @@ -46,8 +47,6 @@ const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree) const editor: Editor = e2eContainer.get(CLASSES.Editor) - - suite("E2E", async () => { suite("Login and wait dashboard", async () => { @@ -59,10 +58,11 @@ suite("E2E", async () => { await dashboard.waitLoader() await dashboard.waitLoaderDisappearance() await dashboard.waitPage() + expect(false).to.be.true }) }) - suite("Create workspace and open IDE", async () => { + suite.skip("Create workspace and open IDE", async () => { test("Go to 'New Workspace' page", async () => { await dashboard.clickWorkspacesButton() @@ -109,7 +109,7 @@ suite("E2E", async () => { }) }) - suite("Work with IDE", async () => { + suite.skip("Work with IDE", async () => { let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; let tabTitle: string = "HelloWorld.java"; let filePath: string = `${fileFolderPath}/${tabTitle}` @@ -140,7 +140,7 @@ suite("E2E", async () => { }) - suite("Stop and remove workspace", async () => { + suite.skip("Stop and remove workspace", async () => { test("Stop workspace", async () => { await dashboard.openDashboard() await dashboard.clickWorkspacesButton() @@ -160,6 +160,7 @@ suite("E2E", async () => { await workspaces.waitPage() await workspaces.waitWorkspaceListItemAbcence(workspaceName); }) + }) }) diff --git a/typescript-selenium/types.ts b/typescript-selenium/types.ts index b854187d02e..5e55a388ca7 100644 --- a/typescript-selenium/types.ts +++ b/typescript-selenium/types.ts @@ -23,7 +23,7 @@ const CLASSES = { Ide: "Ide", TestWorkspaceUtil: "TestWorkspaceUtil", ProjectTree: "ProjectTree", - Editor: "Editor" + Editor: "Editor", } export { TYPES, CLASSES }; diff --git a/typescript-selenium/units/DriverHelperUnitTests.spec.ts b/typescript-selenium/units/DriverHelperUnitTests.spec.ts index 5e0dfcf0f22..9b494ea89f8 100644 --- a/typescript-selenium/units/DriverHelperUnitTests.spec.ts +++ b/typescript-selenium/units/DriverHelperUnitTests.spec.ts @@ -13,13 +13,13 @@ import { Driver } from "../driver/Driver"; import { TYPES, CLASSES } from "../types"; import { DriverHelper } from "../utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; -import { describe, after } from "mocha"; +import { describe, after, Test } from "mocha"; import { LoginPage } from "../pageobjects/login/LoginPage"; import { Dashboard } from "../pageobjects/dashboard/Dashboard"; import { expect, assert } from 'chai' import { Workspaces } from "../pageobjects/dashboard/Workspaces"; - +Test const driver: Driver = e2eContainer.get(TYPES.Driver); const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); @@ -27,7 +27,6 @@ const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces) - suite("Test of 'DriverHelper' methods", async () => { test("login", async () => { await loginPage.login() @@ -80,8 +79,6 @@ suite("Test of 'DriverHelper' methods", async () => { await driverHelper.waitAllDisappearance([By.css("#dashboard-item aaa"), By.css("#workspaces-item aaa"), By.css("#stacks-item aaa")], 5, 1000) }) - //// - test("getElementAttribute", async () => { const attributValue: string = await driverHelper.waitAndGetElementAttribute(By.css("#dashboard-item"), 'id') expect(attributValue).to.be.equal('dashboard-item') From 508831e70144fedafd4946e08d93f7efe1aeb363 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 14:56:16 +0300 Subject: [PATCH 23/57] Intermediate reporter version Signed-off-by: Ihor Okhrimenko --- .gitignore | 1 + .../screenshot-wait_dashboard.png | Bin 0 -> 50855 bytes typescript-selenium/reporter/CheReporter.ts | 70 ++++++++++-------- 3 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png diff --git a/.gitignore b/.gitignore index 92665b69208..91629d7c777 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,4 @@ requirements.lock # typescript-selenium module */typescript-selenium/dist/ */typescript-selenium/node_modules/ +*/typescript-selenium/report/ diff --git a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..a57b901c8bff2245ace703ea0fe6f3e4f1bd2477 GIT binary patch literal 50855 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

MHyMKW{Fh>2t)Li_Y!e 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 { - throw err - }); - - let stream = fs.createWriteStream(fileName) - - stream.write(new Buffer(screenshot, 'base64')) - stream.end() - - console.log("====>>>> The ", fileName, " file saved") - return - }) - } - } -// async saveScreenshot(test: mocha.Test): Promise { + runner.on('fail', async function (test: mocha.Test) { + + const reportDirPath: string = './report' + const testFullTitle: string = test.fullTitle().replace(/\s/g, '_') + const testTitle: string = test.title.replace(/\s/g, '_') -// const fileName: string = `screenshot-${test.title}.png` + const testReportDirPath: string = `${reportDirPath}/${testFullTitle}` + const fileName: string = `${testReportDirPath}/screenshot-${testTitle}.png` -// let screenshot = await this.driver.takeScreenshot().catch(err => { -// throw err -// }); + await fs.exists(reportDirPath, async isDirExist => { + if (!isDirExist) { + await fs.mkdir(reportDirPath, err => { + if (err) { + throw err + } + }) + } + }) -// let stream = fs.createWriteStream(fileName) + await fs.exists(testReportDirPath, async isDirExist => { + if (!isDirExist) { + await fs.mkdir(testReportDirPath, err => { + if (err) { + throw err + } + }) + } + }) + + let screenshot = await driver.get().takeScreenshot().catch(err => { + throw err + }); -// stream.write(new Buffer(screenshot, 'base64')) -// stream.end() + let stream = fs.createWriteStream(fileName) -// console.log("====>>>> The ", fileName, " file saved") -// return -// } + stream.write(new Buffer(screenshot, 'base64')) + stream.end() + }) + } +} module.exports = CheReporter; From 6c9fdcfb1e105b87ae32d81ee33e080c2aa47017 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 15:21:48 +0300 Subject: [PATCH 24/57] Intermediate reporter version Signed-off-by: Ihor Okhrimenko --- .../screenshot-wait_dashboard.png | Bin 50855 -> 0 bytes typescript-selenium/reporter/CheReporter.ts | 19 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) delete mode 100644 typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png diff --git a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png deleted file mode 100644 index a57b901c8bff2245ace703ea0fe6f3e4f1bd2477..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50855 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

MHyMKW{Fh>2t)Li_Y!e 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 { if (!isDirExist) { await fs.mkdir(reportDirPath, err => { @@ -46,6 +47,7 @@ class CheReporter extends mocha.reporters.Spec { } }) + //create dir for collected data if not exist await fs.exists(testReportDirPath, async isDirExist => { if (!isDirExist) { await fs.mkdir(testReportDirPath, err => { @@ -56,14 +58,11 @@ class CheReporter extends mocha.reporters.Spec { } }) - let screenshot = await driver.get().takeScreenshot().catch(err => { - throw err - }); - - let stream = fs.createWriteStream(fileName) - - stream.write(new Buffer(screenshot, 'base64')) - stream.end() + //take screenshot and write to file + const screenshot = await driver.get().takeScreenshot().catch(err => { throw err }); + const screenshotStream = fs.createWriteStream(screenshotFileName) + screenshotStream.write(new Buffer(screenshot, 'base64')) + screenshotStream.end() }) } } From 3297b1fe6f55a0cd1e2c966d4e6f3df1d94cbbdf Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 15:30:28 +0300 Subject: [PATCH 25/57] Intermediate reporter version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/{reporter => driver}/CheReporter.ts | 6 ++++-- typescript-selenium/mocha.opts | 2 +- typescript-selenium/tests/HappyPath.spec.ts | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) rename typescript-selenium/{reporter => driver}/CheReporter.ts (93%) diff --git a/typescript-selenium/reporter/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts similarity index 93% rename from typescript-selenium/reporter/CheReporter.ts rename to typescript-selenium/driver/CheReporter.ts index d19271e33ca..16ef9404a02 100644 --- a/typescript-selenium/reporter/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ import * as mocha from 'mocha'; -import { Driver } from '../driver/Driver'; +import { Driver } from './Driver'; import { e2eContainer } from '../inversify.config'; import { TYPES } from '../types'; import * as fs from 'fs'; @@ -24,7 +24,9 @@ class CheReporter extends mocha.reporters.Spec { constructor(runner: mocha.Runner, options: mocha.MochaOptions) { super(runner, options); - + runner.on('end', async function (test: mocha.Test) { + await driver.get().quit().catch(err => { throw err }) + }) runner.on('fail', async function (test: mocha.Test) { diff --git a/typescript-selenium/mocha.opts b/typescript-selenium/mocha.opts index 3b0bd36dbab..96925abbfd7 100644 --- a/typescript-selenium/mocha.opts +++ b/typescript-selenium/mocha.opts @@ -1,5 +1,5 @@ --require ts-node/register --timeout 1200000 ---reporter 'reporter/CheReporter.ts' +--reporter 'driver/CheReporter.ts' -u tdd --spec tests/*.spec.ts diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 13063a9def9..c65f31bf8b5 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -165,6 +165,6 @@ suite("E2E", async () => { }) -suiteTeardown("close browser", async () => { - driver.get().quit() -}) +// suiteTeardown("close browser", async () => { +// driver.get().quit() +// }) From 0e414fc36835918555107ee0d68c8f673e5b4064 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 15:31:20 +0300 Subject: [PATCH 26/57] Intermediate reporter version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 16ef9404a02..79c489bf193 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -12,10 +12,6 @@ import { Driver } from './Driver'; import { e2eContainer } from '../inversify.config'; import { TYPES } from '../types'; import * as fs from 'fs'; -import * as path from 'path'; -import { inject, injectable } from 'inversify'; -import { types } from 'util'; -import { ThenableWebDriver } from 'selenium-webdriver'; const driver: Driver = e2eContainer.get(TYPES.Driver); From edeff86f151c8c8c019dc5bb154dea3654a648b0 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 15:51:45 +0300 Subject: [PATCH 27/57] Intermediate reporter version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 79c489bf193..fc16bbc1ffd 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -12,6 +12,7 @@ import { Driver } from './Driver'; import { e2eContainer } from '../inversify.config'; import { TYPES } from '../types'; import * as fs from 'fs'; +import { TestConstants } from '../TestConstants'; const driver: Driver = e2eContainer.get(TYPES.Driver); @@ -20,7 +21,31 @@ class CheReporter extends mocha.reporters.Spec { constructor(runner: mocha.Runner, options: mocha.MochaOptions) { super(runner, options); + runner.on('start', async (test: mocha.Test) => { + const launchInformation: string = +`################## Launch Information ################## + + TS_SELENIUM_BASE_URL: ${TestConstants.TS_SELENIUM_BASE_URL} + TS_SELENIUM_HEADLESS: ${TestConstants.TS_SELENIUM_HEADLESS} + + TS_SELENIUM_DEFAULT_ATTEMPTS: ${TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS} + TS_SELENIUM_DEFAULT_POLLING: ${TestConstants.TS_SELENIUM_DEFAULT_POLLING} + TS_SELENIUM_DEFAULT_TIMEOUT: ${TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT} + + TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT: ${TestConstants.TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT} + TS_SELENIUM_LOAD_PAGE_TIMEOUT: ${TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT} + TS_SELENIUM_START_WORKSPACE_TIMEOUT: ${TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT} + TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: ${TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS} + TS_SELENIUM_WORKSPACE_STATUS_POLLING: ${TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING} + +######################################################## + ` + + console.log(launchInformation) + }) + runner.on('end', async function (test: mocha.Test) { + //close driver await driver.get().quit().catch(err => { throw err }) }) From f52e83316befbe339837f8d711e3a25ca2217cc7 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 16:23:33 +0300 Subject: [PATCH 28/57] Intermediate reporter version Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index fc16bbc1ffd..53b2b711879 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -23,7 +23,7 @@ class CheReporter extends mocha.reporters.Spec { runner.on('start', async (test: mocha.Test) => { const launchInformation: string = -`################## Launch Information ################## + `################## Launch Information ################## TS_SELENIUM_BASE_URL: ${TestConstants.TS_SELENIUM_BASE_URL} TS_SELENIUM_HEADLESS: ${TestConstants.TS_SELENIUM_HEADLESS} @@ -45,6 +45,9 @@ class CheReporter extends mocha.reporters.Spec { }) runner.on('end', async function (test: mocha.Test) { + //ensure that fired events done + await driver.get().sleep(5000).catch(err => {throw err}) + //close driver await driver.get().quit().catch(err => { throw err }) }) @@ -58,6 +61,7 @@ class CheReporter extends mocha.reporters.Spec { const testReportDirPath: string = `${reportDirPath}/${testFullTitle}` const screenshotFileName: string = `${testReportDirPath}/screenshot-${testTitle}.png` + const pageSourceFileName: string = `${testReportDirPath}/pagesource-${testTitle}.html` //create reporter dir if not exist await fs.exists(reportDirPath, async isDirExist => { @@ -82,14 +86,19 @@ class CheReporter extends mocha.reporters.Spec { }) //take screenshot and write to file - const screenshot = await driver.get().takeScreenshot().catch(err => { throw err }); + const screenshot: string = await driver.get().takeScreenshot().catch(err => { throw err }); const screenshotStream = fs.createWriteStream(screenshotFileName) screenshotStream.write(new Buffer(screenshot, 'base64')) screenshotStream.end() + + //take pagesource and write to file + const pageSource: string = await driver.get().getPageSource().catch(err => { throw err }) + const pageSourceStream = fs.createWriteStream(pageSourceFileName) + pageSourceStream.write(new Buffer(pageSource)) + pageSourceStream.end() + }) } } - module.exports = CheReporter; - From 2de6d1fbb4b679e86989104242d6c81c5accf350 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 16:27:34 +0300 Subject: [PATCH 29/57] Refactor 'types.ts' Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 2 +- typescript-selenium/inversify.config.ts | 2 +- .../{types.ts => inversify.types.ts} | 0 .../pageobjects/dashboard/Dashboard.ts | 2 +- .../pageobjects/dashboard/NewWorkspace.ts | 2 +- .../pageobjects/dashboard/Workspaces.ts | 2 +- .../workspace-details/WorkspaceDetails.ts | 2 +- .../WorkspaceDetailsPlugins.ts | 2 +- typescript-selenium/pageobjects/ide/Editor.ts | 2 +- typescript-selenium/pageobjects/ide/Ide.ts | 2 +- .../pageobjects/ide/ProjectTree.ts | 2 +- .../pageobjects/login/SingleUserLoginPage.ts | 2 +- .../pagesource-wait_dashboard.html | 58 ++++++++++++++++++ .../screenshot-wait_dashboard.png | Bin 0 -> 108678 bytes typescript-selenium/tests/HappyPath.spec.ts | 2 +- .../units/DriverHelperUnitTests.spec.ts | 2 +- typescript-selenium/utils/DriverHelper.ts | 2 +- .../utils/workspace/TestWorkspaceUtil.ts | 2 +- 18 files changed, 73 insertions(+), 15 deletions(-) rename typescript-selenium/{types.ts => inversify.types.ts} (100%) create mode 100644 typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html create mode 100644 typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 53b2b711879..a16ac70df21 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -10,7 +10,7 @@ import * as mocha from 'mocha'; import { Driver } from './Driver'; import { e2eContainer } from '../inversify.config'; -import { TYPES } from '../types'; +import { TYPES } from '../inversify.types'; import * as fs from 'fs'; import { TestConstants } from '../TestConstants'; diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index c857d96d95e..4d960a16b4a 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { Container } from "inversify"; import { Driver } from "./driver/Driver"; -import { TYPES, CLASSES } from "./types"; +import { TYPES, CLASSES } from "./inversify.types"; import { ChromeDriver } from "./driver/ChromeDriver"; import { DriverHelper } from "./utils/DriverHelper"; import { LoginPage } from "./pageobjects/login/LoginPage"; diff --git a/typescript-selenium/types.ts b/typescript-selenium/inversify.types.ts similarity index 100% rename from typescript-selenium/types.ts rename to typescript-selenium/inversify.types.ts diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 4abf3972541..99634aac406 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { inject, injectable } from "inversify"; import "reflect-metadata"; -import { TYPES, CLASSES } from "../../types"; +import { TYPES, CLASSES } from "../../inversify.types"; import { Driver } from "../../driver/Driver"; import { WebElementCondition, By } from "selenium-webdriver"; import { DriverHelper } from "../../utils/DriverHelper"; diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index 86a1aed81c9..e22db24d82b 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { injectable, inject } from "inversify"; import { DriverHelper } from "../../utils/DriverHelper"; -import { CLASSES } from "../../types"; +import { CLASSES } from "../../inversify.types"; import { TestConstants } from "../../TestConstants"; import { By } from "selenium-webdriver"; import 'reflect-metadata'; diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/typescript-selenium/pageobjects/dashboard/Workspaces.ts index b4d99ecbaac..ed3cee8fb9c 100644 --- a/typescript-selenium/pageobjects/dashboard/Workspaces.ts +++ b/typescript-selenium/pageobjects/dashboard/Workspaces.ts @@ -11,7 +11,7 @@ import { TestConstants } from "../../TestConstants"; import { injectable, inject } from "inversify"; import { DriverHelper } from "../../utils/DriverHelper"; -import { CLASSES } from "../../types"; +import { CLASSES } from "../../inversify.types"; import { By } from "selenium-webdriver"; diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index 9e73fa7f721..4eaa0b3f6a2 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { DriverHelper } from "../../../utils/DriverHelper"; import { injectable, inject } from "inversify"; -import { CLASSES } from "../../../types"; +import { CLASSES } from "../../../inversify.types"; import 'reflect-metadata'; import { TestConstants } from "../../../TestConstants"; import { By } from "selenium-webdriver"; diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts index acbdf216def..19ab3b208a6 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -10,7 +10,7 @@ import { DriverHelper } from "../../../utils/DriverHelper"; import { injectable, inject } from "inversify"; import 'reflect-metadata'; -import { CLASSES } from "../../../types"; +import { CLASSES } from "../../../inversify.types"; import { TestConstants } from "../../../TestConstants"; import { By } from "selenium-webdriver"; diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index 42e07577586..5fa2e3284d8 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -10,7 +10,7 @@ import 'reflect-metadata'; import { injectable, inject } from 'inversify'; import { DriverHelper } from '../../utils/DriverHelper'; -import { CLASSES } from '../../types'; +import { CLASSES } from '../../inversify.types'; import { TestConstants } from '../../TestConstants'; import { By } from 'selenium-webdriver'; diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index f510010a69e..2195b5d173e 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { DriverHelper } from "../../utils/DriverHelper"; import { injectable, inject } from "inversify"; -import { CLASSES } from "../../types"; +import { CLASSES } from "../../inversify.types"; import { TestConstants } from "../../TestConstants"; import { By } from "selenium-webdriver"; import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index 9ddc4768544..1f8c11e3cd3 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -10,7 +10,7 @@ import 'reflect-metadata'; import { injectable, inject } from 'inversify'; import { DriverHelper } from '../../utils/DriverHelper'; -import { CLASSES } from '../../types'; +import { CLASSES } from '../../inversify.types'; import { Ide } from './Ide'; import { TestConstants } from '../../TestConstants'; import { By } from 'selenium-webdriver'; diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index 323ae80d764..f7c52e14a24 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -11,7 +11,7 @@ import "reflect-metadata"; import { LoginPage } from "./LoginPage"; import { injectable, inject } from "inversify"; import { ThenableWebDriver } from "selenium-webdriver"; -import { TYPES } from "../../types"; +import { TYPES } from "../../inversify.types"; import { Driver } from "../../driver/Driver"; import { TestConstants } from "../../TestConstants"; diff --git a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html new file mode 100644 index 00000000000..132a63c7202 --- /dev/null +++ b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html @@ -0,0 +1,58 @@ +Eclipse Che | New Workspace

+
New Workspace
+ +
Name
RAM
Blank
Default Blank Stack.
Ubuntu
JDK
Maven
Tomcat
2 GB
Java
Default Java Stack with JDK 8, Maven and Tomcat.
Ubuntu
JDK
Maven
Tomcat
2 GB
.NET
Default .NET 2.0.0 Stack with .NET Core SDK
Ubuntu
Dotnet
2 GB
Android
Default Android Stack with Java 1.8 and Android SDK
Centos
JDK
Maven
Android API
2 GB
C++
Default C++ Stack with C++, gcc 4.8.4, GNU Make 3.81.
Ubuntu
JDK
G++
GCC
Make
2 GB
Che 7
Workspace.next sidecars and Theia as IDE
Centos
OpenJDK
NodeJS
NPM
0.5 GB
Che 7 Dev
Workspace.next sidecars and Theia as IDE with the tooling for plugin development
Centos
OpenJDK
NodeJS
NPM
0.5 GB
Che 7 Theia dev
Development Theia as IDE, che-theia components, Theia plugins or plugin api.
Centos
NodeJS
NPM
0.5 GB
Eclipse Che
Utilities to build Che in Che with JDK 8 and Maven.
JDK
Maven
7 GB
Go
Default Go Stack with Go 1.10.2
Ubuntu
Go
2 GB
Go with Theia IDE
Default stack with Go 1.12.4 and Theia IDE
Debian
Go
0.5 GB
Java and MySQL with Theia IDE on Kubernetes
Multi Container Workspace - Java with MySQL database in Theia IDE
web/dev
0.5 GB
web/mysql
0.3 GB
Java Gradle
Java Stack with OpenJDK 11 and Gradle 5.2.1
Debian
OpenJDK
Gradle
0.5 GB
Java Maven
Default Java Stack with OpenJDK 11 and Maven 3.6
Debian
OpenJDK
Maven
0.5 GB
Node
Default Node Stack with Node 8
Ubuntu
NodeJS
NPM
Typerscript
2 GB
PHP
Default PHP Stack with PHP 7.0, most popular extensions.
Ubuntu
PHP
Composer
MySQL
Zend Debugger
Apache
2 GB
Python
Default Python Stack with Python 3.5.1, pip 8.1.1.
Ubuntu
Python
PIP
2 GB
Python with Theia IDE
Default stack with Python 3.7 and Theia IDE
CentOS
Python
PIP
0.5 GB
Rails
Default Rails Stack with Ruby 2.4.4, Rails 5.0.1, Bundler 1.16.2.
Ubuntu
Ruby
Rails
Bundler
2 GB
dev-machine
eclipse/ubuntu_jdk8
+
+ + + +
+ + GB +
+ + +
+ +
\ No newline at end of file diff --git a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..259db617f46725947f1565321b4c1aab3b339b58 GIT binary patch literal 108678 zcmd43XIPV27dFa_qmDSjIEV!VW)ws~K&b)(Dgp+im(UUEgbtwu0xBXS2xw^1MS4O) z4K0MIC{;S42c$#jC83kEGw(Yy?{}R)=hr#sxh_hczSmxRm3!SQuXMH5&#-c_vaqn6 zfjoSm&%$!jpM~XE!p|qb9)yDn4-3l=yv`~rx&|<38y1%9Z;@|HH5zo!y;P2hx-$R$ z`}a59sw3C>h5vklKIaAFME{J+`{Ufm#h!ENJZV3D67=NY`y}gmAuaf4K}Uv^%8vp^ z5k8&%or3X=?&p5K?t2k- z@NIF=wA!rX$LvR`u)W(?;<#iuzY3d|$){d+ap-yX4jZWD4$JM3Q9vtB-nB5QDBE9B z{%Cb+cC~zThWpNP!=zCnrtbOfFROxC9CC4et{V>%6UH_x%7{be=eArP(Of(qJ#Bm0 z+&4s?;S0)qFxwlETf)NfI}7B&J;Q*sB}$-4yb+^mz4PXh!k_!NKc8N&UWLV`n7TN- zI7j8?F7L%xju=!r?QfJzx!uB7^m#i=`n2(!J!|pnS>3aocH_y(<9uJP{}7pbg&tyg zA@m!v1G%!QI1j(IS<@-^^3Bs9&i`=z>cPk2GLxOvAHYQYwLOJ}X&GJk`xE$EefzWJ zzaI-u{OteFxh(Iwejh#l&%viIPd)$VO7su^Utj2osr~2WSiXCkA^f7P?yP69&G2BU z`nsw&cx>$kXUxg=@jYMa>&M)->RtxY7$defef}NA0OxZ{i>OIf?fKAGd#S7n=`=~6R>3Ly5D!co-xw$d>$$y^tv%i-XVPf_fRoV5;>&iy7*uSzW(0@Uw zz7`mO5dOV7AMoH^GKzZej3?j122SpSD(TD!QaZx3I<0 zzSN|zz|?m4#Lhy%fmgU2MJTStkevJDKNmtdC&X|<%LG4NqS~2rXSs0M-RgESsRED_ zVfW6Y${2e#RZ>R@sg+0dHIX9!z7-Cq5kc6j58nKM)V{3Kvo$w{+xj{eEMu@rl-4;( z=j*SS0=1>VhYE?);kFIVVaU&Xi4{?yuJk z(7N}nue*$A38O3b)(b5Sm`{H&Z^a`QP;Hp{^@-kn*R*dAVtz)Ve~oX zFg)duG_p5xqvd{krn2VIQN58R8*{%!xiE3geLu!6J~1(?EB!3z(F0XA>z=O)zAjh& z*`W~qyN^$kvC#b8GFFUCyl4i+HprRTS!(mANCpp2tfnr)C50+m6v`=b3_N_zqcUKfojP_X zL-8MvyD)lXHcGtSXnUnVg z*mxqz&yTlBxsNuZ#d3S<>h4U>4Lmw~_GCg*lDN8hg#PkKWq4sh!9!GqO@m)F7hE&> zmfy;#NOrdxkNi*82`PWAbWY6HH;Tw|*R!kg$pN+Kw(Hr(ZhVSI_wZ^__u@mcM94Pv$Q{*00R;nHDZC+En z>HPZn$O*JN#Vm3#=q~A?LQe#GU`8GN{^o8paa)GAGNM{pSvgD;xqq5ufs0KPsSolTB#TWC+W$Zs{W>%wNek@v*`cv>Rv&~d)>^Cx}E3S`-`2aQm$a| zqdCuF1o#u>LkHKmIY(u5E7h_qJw~$q$I~!_S5NgT@$$MOreAs0!oU2U{OHQ);b4LlBhjh#R{GDp5SsfI51(&kpw9-C&Q!$speQrMtaxXKLn8T` z%V>4|i8Ivfvsc|w-A*^iB%RE+T*@I8YOgOA$@!djULJ05ZD}EIb}HwE9M&4`)E{oN zsS!6?PjcOSpPvzWbZcmE@VcNNR5P)trxtErqzT#I>C+vl#7epJwS${K<9FVePO_It zr7e}m&iCqEfHlQ<=H@%LHZ^G~2Kn_IZpS7P2(ZG%+?*V06W_pMtg;{V%PWJ{=4So& zlric^Rcd1_pQXcWIlmjF)_gpKV0M!2vYwSyMjep|)`ua`&v(+l$|gVBqmM|NmCyR~ zC?sB2P*6C2qO!}_>k-}f7H?`;Oujw@oMu|PvsX_pMptE!4dNWb#(l--btxhi!+p?FU#u{7B0+;X%6r%xNPI_jTkV9MsZ#^#3iKz8gH z#Dp4(#_lmzilt=y?MnKcpc>qV;n~ocpnV)~( zk*-qVJ!6)B`)$r1ZPBE2yu>E;wHmj*ot^XQ*rCgSSo83Cu38;bUyaWQYcb!sK7BE< zj^P@~y1sP^VOt1j4~5{bd$@PVsjqom%@1AdD-lIU(d>uj9_m5)|7k_c-FVYK# zdkmIXN;(7=j1dX*HFJ?+mXH*Q8BU%%7qe{|PM}T8)mq!y2D-DGDv&|%8eiAo`z}VX zeWgA-nPHr-{Ry$ra&mfM0S8{x`O|ZI)-%yv`I#N4^73qaC|H%oIE(!`BrP#L9sBw7 z6&h_od46d~inud5mD^}t@;G_+n<-N!PV-$y-f(#LD-MW;hP>?=KQOf!fev|<9^={f z!uIAXM@|R<_oIARVHK-Z5UoO`@qLz_5FSQAVs* z-|^7(X;N#Iz2Ky7T;1>JE)Jg+p$WEiJQgYcrXi+bWT#yb(l+>nhm>;|9I-hmrMzn% zy?F_DK1OTHWzy9?wR6T3NvU;uudYM%okY%fO7y{;fBCq(OSUJ|Dj^*Gb-f>26jutQ+MZHPQJ#Y> zmM)t&Y<;XQG&`U$oUOmyJFKSqM_-gSi8^1@)w()hw%>jj@YZEuy?Z&^qn|I;&*kI$ zF&B=@?+L2`77)RxOsbY-1P!2O^QND&KPpDS3>%sesA6xyxlrlgQPyUIEKk~ z-|Ms#7QVsudn}(q;U#fmv4Dt@aq1hE@Cem~w}Ooec9TMi>ubI)F)O*8pcPBto z*a|iD_+|lEQ5$QXaY;!LGOmNh*G6e{x?$9LEo@VwIK$MuYNh|hDgXJgAdf0uKDiAC zw<{1d zdAFh8(6Zyf+LT(n^6DX15n(l>$bUS&SP5BcwWSDF(nqrW)vz&_o!1!!lG-OO*`Xk6 zoW;7maK_JYxEsb9&S!`gvQ)b?3Z_zVe;Iy3{ zzaqC=WQE)fi|TlukaJLXpbr8?m6Nu3dQ$tI4kH7VQ&$&!9Pca?_DL5#J{o&rF)p?q z#ZS32eFAHW@8DWWyqVe#g7#vm`PmT8{)tKIhoz;osDASd)zgXQVU*vxBc3E=WE9rI zVJ(ku0;}TQ^;ta06dZ8W&UacE!1(qacOhi-xtUmBi-Qyjaf-jSydtTn5*QeW9(j5g z%`}4+iUK0KiicY&z>xO8zQl(vS^JDRwLEwhXOQ3F)cod$*7kO&gF_`P-yr`nq^zT0 zx$6%T8IX&ei7v?q-SJG*PJ5`Iul)xr8(UwVGBh6*88)@HcF20_RGrgJ6Nyc~xsJ)? z>hn>m=9NqOi~UC%8yjz;qoWa9^9A6v{sgRcx`y%n`yXn3oz1C|+f*{Cg-ovbcEc1> zyV+s0vE@4u$w`XeKhjF2cd5EaJ9TDUSKR#eDE!4wR5CO_k@X8@)WTkB*lBg_7>K9{ zA8*h7!W*n2*Ry%%pE*troA%A1)PD`6$-T`Q%y-&FD>&&utsOz+i`gBF-6jw?7BYpgo}KHM4iL zis1RQoiMLX^L+-Y-ZcNTK~eFD>KSa`pa6A}sCRuK2jUQ&Np@-(hBCF(YpFEY|1CjKfYu`0f#vUC$N1phFiUoS8$`>hG zg=}wyC)65$o2X_T@pES%piI5gMAg5mGW0;MJr5Z#Gqet+5i32;^w$3wCZFJ|2By1V z^rLaeFkfKxsA&_cujh@i=0{i?DM)U9)l4lx#?*0DhuxNiNG5=neynW2j@G#nIYHygZ{gjoJ6*~ZdK*(kG%0l1) zuU_TH$Hj?&05z_~mB{!F1Xit16?%^!N3j;WNhHH}Jc}M(D|fG3$Fc6`<~bM+0*Ck% z(|Mg+tI#JiW|ptF`CU^yW3K1ptMGjM^iqm0;47fuIg@w@NUGry@AzEaN})stVN(l^4^e9|(PsyQ`e!jA_ z#pqR`A11WD!d2|~;c1XKXzL9j%g_Af*95=m`dWProK5f^+8%{_`RP-KDnjpcggdm& z+RZh$w;Q1h_IkhdQ#+nE?^6)w27K%1k{->#sV}?<`MCGS1e*Vm`b9C!${>`fFGO^W|Csu;Oy^NmtvM&jQA zdFy&YtslJqTPHLoCMFV~Ha&A+)S8+HNa1Eb?ZFsh%_1fM-DP2kC+F)xL5OL7`hpm= z*eZQyb-ZpgBsqBql>-zOZPTy;<@5HIubP9+b^NinRGu3dt);s;8;!SVkO3(^(_vG| znDVhuh9n5sRYF4Ai}sf@NPO<9E@X5Tuv#X^Mnui;3qXhnco}`6D=XTp7)AQh(%LG!DyvWmOF!5T1SSsjoZ-@6xrSo;*2 z^wxd2!Y14eNUC~|Yk0tjhmf*2z4Zim%C`|N(RwfCH2>)Mfja$LQ58cX$nVd&GcJ!q zSDrO=b_8QyJdbl(>3Vob37Rk6Ny3ZSVt6sg*v7t#ALJbC3w_f$qmlLZHQzd9q-9re zD`95p`CwYZi0G*FYmf~YL_zZt{g-l>KDkdFKcI5pm(1HNe2|TP(ObMlzb3Kp=!mfS zhov1z5y!I8Fl)`Lb?$~KLMqC6nfv=S(d8s7G~*qOwt8u#LdYkHzoo5TWxG+^D4@#) ztS9S2VgyDXgfAAaJE)8OxubNRp$Ux}w~+}LVZWgUO1We2v}J?liLk8CAIl`}C@KoO zZsH~ix3(TFE>=|h(th+^llcSDkI?>U{(IY9n8u!&~zin0%XS?~5 z2eI{PAIz%nr zR=t6Tx~s~=h1vZ5SH}bu6wJSVv=7tkcpaWpYTz zLn=Vy*6zBClQr}V54H7cBc$>~c1OiXZmc#kw~50_2p*ID>E3zMcza=mv*mWH)`AT+ z5r*b+cwLT&6B@#IvR4fTT#S1x(r?@`KvOzP3u*8SxpA(QVJ=trO>tw%-xxaL-)>=74o$-FV2*=t=GuyyNm2E4!X@3G760~C6IHtI7@92mTqqJI|r>3*# z!CL!Hipj@>-u0=0mR$C3>i8pa)L+-#T`XmLCKbJ%WS@TDe}#Oyv##)aLuzu^UF^$Wb4-Jit zV}3N>O&C$ys(gl^CPQW_E!i)pUnWnz%mC?HYkP~40ckdA%T^-UPFUD5Nr?iIIxBJ& z6J`Ex-3S2APQ~ql7+%G^-OZ%tPoJ_LWuh(-LHYzBw`b2nm=SA+qTX;_%}!fPJ+c$| zwku!f!q>)Y^Yh)dz{-hn+_!qzveBNEweP$3_%(t?waC-SRI)TQG0BmJg+N_gYTFXz?p^l?w_Px3_EN2H%o25T0c*aHNSl=ZwYNy zU$4av(bMbZ?01ynB7acj;E_YU@11cy4oVMCVZEL{Ha@L;H?JmEKX`0Tu z!abcu%QbX@)6OwITvj=TRW>jGJpH(2;CAMOaM?L$ee5kn?aG51u5`}H%LN@`pAvB8 zb)^hySfjYG4CCGXD<1u#-5k@4T_&l=5}nK_C+)9ti;6lXFK~oIB%0snvDx$SyR8g9 z)L)nA6G%+6yxLh(xiKz#cWNp_wA^rgKnSBj-fe7=vu{if^p0VhYg&lAR(z1%lAUA_ zBc~&)9y_r2ZMol8;FEi;(BhAdR;Edcqs;8TSlOeeA$D9ufzT5)Rq=}xE(0kKYi}I~ zI;VPG@wW4$7UwnN#PobLF@Uq zi?*Fg=bMZcx7KX8o4ZT5l31i+h{d;pD@MVi>l(38%DL$a*Oy&WK&)U^#cupC-!C_$ zRl)B_2b6`%%B!HpYIz%CH!)m57g=!e^ks7x&;xwH`gUZjvR)ohGszEaIie2hKfslg zl$p1l^vtPK0NEyn z>`XvGd|K$(scpaKyU?Rer_=Y2_N|MG87_k*&p_hU3$+#%>7~;KL=u1z%Tn2g*5WoQ zJjTto$CWiECiGGi{7Y#8dU|hJPe-+Gi6%A;g->LC^s#4Kw7vjJBl~-8-o#7*Xc+0 zR7eIDzYL)07M9Qq^&-da>=q&j@cw11FtOp#7u(Srr8ML5Fx#SMj_oDdt+(b}o?uVw z$kd%X0ii9q2;C6W7@D=fwhw-|*S%ecLux5n`hV!RcO@r!|2QG-{8dv}*}wzU!8kqA zHjg_ct-feC;k?{YQ)ljQ-Yn8Lbg7q_`Zv@a4b8o3E9C!vsAxMPeRfGBsrts|nL`2w zgvOkebF(uGZM)-C8dRw#DQcYTXidcK`jCu`9V$VwgPO7LK9Dwa8b1GcNwpYnYhIlq zDL>gm8FM~Fi?dQeUoa*;K9zvU z>wNb?APOoZDQT|EsJ?gTv6L%*o`mst=rGdnao(+T`fxVeJux;mDI?|1)N_1r`TR*~ zClhss?6&ms@L8N9;VOLX?(4s@3{7(ia{Omxxl^uEX4SO*m^P$GeHDo-PHOM3JMQ7U zvJ@^XEFn|?*No$5@yLnZJVnR|Jrm=o5Zy`Tji@e7t ze)O2BqbwF$ao3{B9qAKhr5~0yY=F~6?9MP;K%Id0g+shhClj}^3Cl}MvtT01JPLHD zjp=o++a*=BG$zvQHRZQF#8G&PLnE}bx5)IWw39A$vES`&w^KF~>#x-g3R1wsF66k4 ze4d_~%6-Z*E`hwv!vhkhKV>||gaGW0cO5EvlnHQGr+xZOQH$#PrKP8+0C944{e|@m zB|nxF;0$?c%Jmh=^1WV8EEC4wc7&dc`>|t}KwnX6!YzBIxwSQ`Nj25yhx*_mC<9V z+!B>AV8SC?Vh8w4=Ztq+ZaO*#QpLGu6iwnd9Im&=7e)zrENwPbJRE*kV^D=S(^U=^ zTzJ%N3iojnbWELGwtb#ct!Hn%l3GtV!koMo z-7TuZxC#LVZ$Ke*LDsDm!xh*hTI=P>H#9UPB(zYG7`h+VUv!}5?0o40F~4f;UAF3J zW2felAE;eICh4^=nxB-;+f6zv9(DP;jp!p@VQcurjql6pxM}u$I49AQI8}fUMluq?J%pD55hZh{0<$^92uwt9)ww2R7Cg+`zK-y>!dbk+Z8~3 z{imyItM%GqZqhZ3N`#CVo+{g(T>em1^_sN1g-+}p?^tHS=ISQCV7lQA%7h>u5%ld; zG}zi`TV0G2A4{yNN_zRrLNQpVTTOg~cu?i`@DRtlQ}o4&Ql@}IVBfg2Gc_O;dL(OB zvmu+Cmxob1qXX*RQ7Z%w!$P9>NR z$;qNb(bR>0bB}|jRZix*A-S0b^|>*YQ>++zXFON4CajJn=*Q=Qy{*fcwjI4uP2;_F zdBux`wHy^P$S8j2cTk4Dq@Hqx<@*)fGVSv#|F+lbL&>XBm5nzkQ$DF<%Cs3(*umnU z_&h~_0>{sUBUWV)+Hs4o<5IhgKpADrGV{={=jh{TJZeyN`AX(;T})~6uw(z|by(6#3bvk=kT1*TE6Amr8fGa7pE z7Q?;rl5dA*&5dJ5x_9BSfg5eiL~M_iRNu-}H?(pCu1tjdef(?Bt=W`A|Hlg^ev3)1 zdvmcpKSCcqIq(u_v?c|yF|TI9-QXQ$l2<&5LZL_j=AQp-AEjqf(rO6yy<#;r1Y`K6 zzxRVP+pA?7D9ZOSwah&5zgu;lMcjngUmrdyudfiqjp3)5pFD?3=l-+%uVaSCUJ(BJ z=dWM7XMSDzmv3`d`fl~JIujGuD)+QPvK|t~KNhvSLqzwZCs<~v{i_VLh{U7o+(ttqpTwRM? zfyJl*w)pGUuPtqDn)c>QVE?}xRLjD`qUO`e?&Q?essClI34^HH7|s5F%u&|o&!07D zfBZieD3Hr~e`!@x(80dyI0W@F`jVdeeq{LgQFPhgF980|+0OnSDl`DE-v6kQP0M^n z5pi7|B82fK2M-t(6$JZ`0*rVw+>I=m6}`EVFQAblhX(u`1k2RVYLc2JfTd``jNS8L<_)0vm0r?O%#EYP-oEMQIuK7TGJedMa0)bRY#tE)33A4*-_+~9kL zkxILsr9C|o{E(}wb&zF+6D>|s=)Z_CWrn4g{*Rj^O2EMwfOH$rbcD(Y=`_t$w<@;)wIwn|g0=Z(TJ+V!c(hSp?e`jh^F+Oe{T) zmoGaL>d`k}T6JAO&ZxLv%A75RUK=!uWp$dtEEQdL8!pid|EBw|#5~U?u=$g$w=8R# zM9;S!A7R8WK^KaTF4k<{d5-=wV6C`~{fZ9c6HfpffU7#Ax!fnWdt{@GMSd@-0qHt* zNN^n0VwaedL?|ow!P?U1$@hv6Jt|S1IcU?(mx#UaSF9sExz7*oL-Zf+-)&F31uY-9 zzRs>AtGM0M0O(O>XD3c_cC|vQC`!7;q=Qm@RBvBVpo&)DUWiuctYR*IzGaCR1Pvus z%KN!z&*j%+)6(uhdj8atWwLg36pv~FdaV|H3D7T++UR5dDs#2ckRkjBPKaWv*o6y% zi;KtfZuJYKT&44(bhN}FJqx`;8yj@pBzHIa3Ta_!X%*m@`Zp`xhO(z63sE_PSDWHf zQ}a{sE+!+kv6p+F6QzV@z9aH_-QHFxMJ2m8LLi+>KA}? zbj0jYH&?NraQyJ4Q8i4+j^#CjbvaC^2}5UG_+KSr{FsnYqu~1OPw&{IZ-dwZlOS$0&-fCj^Dc5RrZ~~%-u1@>mOADsC!@c^FK#vOn2sxs%f8~QFK2r-4msHiHx`@gt zD!RO=81K35Y2c!fLWBDm$FQC@P5jsJe+;*blk}%hEIE6Wbk9yi@IyHGY7FG$0uhU} zzkbZN7yI=OIdszcM~BAezvhn{on~C)lB%x4x4TwateFAoR<2c0AVEY_+Cbl{%RKv~ zVA}zH|9%g%d#SoT<1BB_Gn0 z4^y1HmDc*}zYOKXIRa+p43%26qBvsSxxV3@6yfM~H#IpKoAX67>0F|W+t}TE*h78@ zpeRx)#pQnP<06pZI;dE+5NPu#4_oT3{?B@I;~wxQG^ z-@~I38N3_cb%^)2Z|^bo=tPjXe{+b6;e~Ydd{s8y8&<}S74RbE=W+WlfgNyJ?liCP z@K}F6arpL@S^XANad#w$re5{RF4X2`;{l^b-2rJxqRm#D}HFAN&_0krC zF#?uU^M98y0Kk6z?dJE-i{}{&|1q%|%QdGB@xTKL%gR=v+x{L7%SX8T@-RMu>aQi* z_OG~Eu3Iw==io0E4)g!|=#L2v-~NBhWd2CcOk0|rzfJ9PGjlVRmG>F1GKdNV@IOfoBvJ`l^WR54 zCFS-6t;iE<3s%1$x$u66x|f)L27U!#msHtmlEtWn`0ZH^o#@_=sfYx5`lu%R!Yjd5 zQTWo!A1GVXl2an)|6?R9{DpZ|X6I&{4^!}wa-JsF;tD^9fD(y1dlPkA8kUfr4F7a% zds#8(y^Ls4v883YQF&t3!H+2|pFWKt>Wmi84_C?7mi-UQ`6E2Pr%j^jXD1L>;73(;-U82W1;}nLX@?TPCj;)_ zc-5J%tYf9#eqP3l-xFCWH97zj{oz?V-nFF@#h;DUtBSe!9+=4H{{Dg&J8{4ma%w=d zBnmm^Y&tsu!1c#&@9F#vAUFNF);J z%Zde^vmyCl#nJ{tSM{drotRLG`wA{HRw3)FHO>&AR{TQMZcp8Lx~%~@PlC&>!eQnF zb{`H~=Z3*BBWi5AbGSbM<<8Lc(ep6`GK0U+D{ilLzutUiWddFF?K1L~NAl#9_Cu7~ zSYX_+|1W@lX=rmVOX<+A@QLkj!uzlvSq_+V-{j=v3&Fqtttg(z^3u|`1OfphR?bz! zuzmrGYD-4#`mNGP)~K$D0u>j|9y2-{J@S_*T?a&M@G*0AP3UQ`uou49A6~dDzF=1- z(No@6r)sOpcL!a`^CMo+m$byQ)K_o&20-Zsy|ZT$GOgcpH0gjSX%rF0B~`@qeA&;R z-={5(8yVzLiZA35nI&CFt?iQNp6f~#VihaS&$$Zb5X?p#FHbfwk`=)V;;0;j)p}K! zF%D|t6c0i_!|a#&`1(ez#l1y3x@)XLyeIq@9QFir+cJd6)Uj%*=>Ey#4(8;yPasENx$)emuI$`pX7+x^PO0 z_A1$BW#r*AY%pyp&ZEUnqOGmXa%iX~X+s3a5?D4BD=RAyy`ZU}=jA_}5x=Y~)Y;h? zBz|D8Sd@l%n+0>plqc3xm3`k(rGaXjSHjwAwKi}w*+&g0+DP#COG`__idJ?HD$I`K z-*opE7~d1E@bCN~JX}qv+VUw-RXpp4D4yS^bBw3L`>Qb3 zb9^Jf9o*!9STj^3VgAj9f@}Y(BdiXF4Tu<7^D64_0}~V=GxI|ruNn3I_X*v5otBxmIC(!G$N)1Ac7p7@ip4IFl7+_yN%c8_pKL`%yN}vefw=0M0F4{ z?9mtbID@9&@e3)70~XNGuS+Z`Yonj2Eqx~?DpNoF8}GMDJZb~;)g(=Xchttv|iu`05-zE$xHd2QWrI8)L_poQ=< zza!&9j@gYwi2=DpPklb(c7j#l)G3}n1KuC#@6t z0VBQTQ@7P_;Mbs~{-OmpA}%x3fD*Xc8v#=j)5uHs4wsNDPe@P5 z9$FReL9&nHm@x|HDh2@b{1A29*p|n(*~-=$DECXlAPfBdxcHB%Lv@9!)dc>)fNH?q z?c^HsTkh6g;l+Un)^!YPuKr7Wc6Jzl;Y*!$tNMu`wt{32mE%>kDmvDnDi5rUz`&Ze zhTe8F5BYima;!otER>o+6-+lmj$Dl1!ezdZ?;+IyJW z7&FR^#VHO`0fx3%8uuzcAvG?)8QBdA1|_aowb#5krmNam8_TCC#ec4a*;9j;v5eM^4s9>>*u`bpSrG*h_0?}_ z#oe|Z-AMMB_~=jXzi{Ml(YZHgRu=M3@^S&9K~Qr0c^*a7``oC|qCVumUc0*f()}M# zSoFkOn46nRZ+rYP{xK0SZ+cfm33iq5$ffFzp!Py-e%2sgr);LG8V*IzVm>%%d1!?$ z-%^yA$OVl`M*N^L44{%&sszh{R!d8ZOV|5*MoE7^qu@zXEML-J>AlxI*Z>Y|NYb?0 ztOC9z;tKn`)7IHt{08)G%|i)u-vGuR!0#H~yZ1@pLJ(FWjie;`v<(f;ymjjqGl{pE z7!SReqn$3lL8B#LMK$031?_;@d%8F>uB`$HK<8=%Kkvz}558*C|Mh8TMA%7oVL7>7 zzSy|Q9nqE#MTUmb_r=ozUA`$M(=dXLwU^@3 zOpimM^4?wbVm-wM>RQO;TmIo{&8{*SBN64A+S^Gk3UFalul1&x*%kEN&~b^mw9bYG zts~J?>kUOft+fU`fwxgnQNV-5zj^ci#ful3y#L@k7NIxFntxX@O41W}2clme^(P$X z>itRg{ng{;Gkk?jC(fQUG_(`;>Bu1JvButhKi{C<#^dAh<=G^CzHB*&z5)&S@&iCy zlH>%hY`mxQy)HfaBFJyGak#U$hlVfqgD!+PKu5H&wB)>avEb>`r<%N{tIZJfrNXP~ zF@{4l(*BBq;#QZ6kstEEGO9MaFWJ!Npf1=hcdHn!S@;{kpK!-wV%UZ+aAIfMxiQA= zTo;y7<5Kgpy_$e@@QlZ=%}(NPQ~_QL_JeHx-M@FSeTab5!XdURRnc1 z#Hy?h5qKtj)F2;)ZDMQs((?5y#2trA!XIshzkDeyA)#+$VpB;%#Pfq3(9E)gMUR;^ImdRxm8S|=~SK%^;AnCl8y+nc~)fCl{mnS0S znn|0p)8GBH9&U2uJdbjS1&D;OplO0cBJm4}ifV!QE9nmJnm+}(O);xZPf*$4I4?>{x(p@@Azw1znHa2kkE$WhOU3i(aC8^~4F<3dQ``Nw&T*Vvqp99BA=? zbcFao0_R2AXS zktPHI;J-?;ysX=B&JV{=<#fSigm9}<_{a)3Y+lP|1>+r>4ZRf&E8rl#e1~(#p$fN4Z zP`R^)PFqQRr8JjOl{S{)xLTh*gSSv05Q=b9{m zoY8ph--ZRAjjr8)&N0b<&LCQ=^yBfV=Rrp0wRW0iWP*d51ZE8S9oG>2y10ZA|BD<2jf9c`0Du3b|DSM(-|ns0Th^R)o8QdXw_ z?AbFMeU-!jINj8l-Ti$LK|ys{if^2Rgv2?nVI+dq)upSr_VE~~Hdb(Pa|2G>6+o!d zOR_=~@-qY0wj}WZzN591WHQ;g`-7^6j?REUlEnw>qm86tVo(+6jS`0-=F8Z~ z`cIyG00&6d<>k$Q#?{WIz$yaPpMb|^78Cxe4uNS!*vpuldveM+9Iww!RU${9^dM*D z-N4^AATfrfKRQz={sxT7< zgTdFnf3i|PeQd7n6@WfvfF4dXM8H5R+}_SqtN=V_REt@k?dnFl)X>&W zgpi7(ICKqOMLm;{i|gCl1M}V4mdZNs zZr)Rmx$L#f2PBKhc>n%)t+d;EySuv*T+y}bjoh@o#bQmAnVDIIJ4Ig9wCn*YK0ZEY ze%=Y9Z)TPP;AKNSJ+H)V-v1iYt=Ob)gW_n5EE^>i0l0Qk@Rh+s=g%s8_r56}GaY+s zJ1ypU_7=D^KeBAhG$G3mgKw+P%R8SC&r8lZl`S1I`ttzYf2>W{!7rmR=2Aaana@M~ z>sK|QZO^P2zT*CzSJ|EW(rKPs^Lpfg0ZC5D6OX`K6d7YQG&Cywmd)DJ6`)yJS)fjy zgaj~ZoG0(S`&heu4uD}Yr!Rm$n%>1?k{DoKLo~Fs$Zux(;HT=6IMwkC*JX8QvVH#$!EyByV#{Me_&io53icw4=W&vVB` z!YzMxU&w_QN;+*zbF0OF&KDFP%uOx&D7hT>e0fdlaxM!au2)duvi8jM`Rmu@?}vm~ zG8m5_+HZzjjg9@RVvz#ZzBKnXs6bwMZR}hrqH4k6>3&PYt9wat=3T3>+qb;moPBZe z;ljcKVkQ9^pO#ki=FP8x2!zXUg)`!4caHF=Q|2t?=^qus1Xs*q`0MmJU~nK2DNOt zKec#aJs|395y9QM-+qMRKlXhOkm48UBe=yfMjJA8_nnW;Tu1s`h4Oo4VA%_q+Cn;* z5w{#+YCRugrwPZhZ3uiibGd-xR!&QtAk*HnD{j3>NlmqvNS1Q=c2V(QtpP`eS|jlx z8*!Nh6%_y@3Sr8S^S9r6u;ake9}a{bCgBJx^2bk{5C&mE)ffVniL+DJ28o({VP}nLo2_n3M3m}GJ4UYR{w2xSYLN{7V1MbVx3{7=jImnW7q*F zkK#kq?hztSx=wlo|mZCS5s@egjc{w`y+#qzC$Ps zKZK7+%=0F z3J;x1{z4)Jt#Xo06Ln382P;yXA)0T%rC?JngWI=YwhVl$wlOR7tq(p7wv)JocOaiY zmR0&t?g8KhR&4!VsJF3oq)GF5pa6op9lGc@_I046w`-zhZ(R8X)YT%#ojAp{ySv}q z(4caXZV%gy^4QvT|H1Pm({&gb!8`VT>?#L@-@CDj^mkXTT-jPG?_~3p`>&v$mUpds z4mgwx@!#l!EAZbiv{w~CQm0Jp@fuNW@J@F3KJH>&XxcDw^9|q=-d73R^YUPs$5d2EF=@9roVFiqo9n=i$1*xk z*DkgC9xg$ZySlG7ZLOqUYE`fpUA&dRDX6}ekQk4t$3gzcdM2;mpSK{{v?$(rq$%2- zzL*2XP(3W4rmdBFOM9VQkjhTl4K1tmkGUx(CXdz>OE0==pMWkZ=vkkVM%J^jART2= zrM;({pNajQY_NQE+maiVPI;ukBl7@d=#^uUZ2h9vYRoE9>$N{#&cGO>wMs14#*AAQ zpNdnXKFb`<73AB;ux@Auxhe-zowRf^e=o1uyc}|*ghK+h7aE6NT)*w%p(9FMXO-@g z1qwwSK5C8JmI_)qcewV<1Cvz`5wQ*=4fy=?3M?P*-lEf3%7=3FI2xV$Ufj~Sgzy&b z`_vFoTbcQ@)&A!NUza)hF@TPnH>d@77iYmz|8R!X?p9*(ldB$!h2sn@ox>v!7nqy> za&htM<*y?g>Jci+LG#Y!0a&#Fsj%(39 zJFl+tLry_%NnDgSnEw1+|I%x7lDBEPUuKSP*CYzYNSIVL~4KlQIIaZgx)&@ zq=X)LS3LE9=lycuxnJ(gn{npMQOMqV?ce&<=Xn-kl2&{;q)nC)AuO#g3?CLr?gO3w7;y_N{kq5!tNMNr1N2J9ciG5K}`B!)cSK0F< zLL)fk?!~;%@Bh=6P0|*Ym1#=PG+cYXW20*hAV)68$H(x30!>#}{Nsebs#)sW01S5O z)TuL&Mqx>S!>G8Q*lzyz%^Nb8zh|c@yi}dkrvE%s#k(v2KvqcnY5%PNL?ZYfFc|y) z&;uI%4^T|Sf2adF{0FvXlC*}lwU4`sdSV|x20IIV5?pC=(PT-71;PErp-NsV4SVTlbe`Nd>PSWS7d;T~+C%?DJ zdL+JvAZ`G%-#|RrD&2Qa@ZVqH|FKb$g{PQ$2zAA#RKyf1p(Nz}Mb?G7fvf2t2uqGJa@&3+!`FS=0PBjU?JwLOuVLppvKEhrUdOJiW zEvkMhl4)$aKuY&`T8^-h@Ao{qu3oaLZ?lGB{PXIM(}ev*O2F*uLeirRW#rhwl4uA{ zls~u8D!{I#2AlHHfJD?qY5nis8wtt7o!zfFjX7&0NmWqMYwn{p8Yj?wDCFeo)l6pD zvmV*NX(ce>Mq;wRsGr6XV@^9(LPOpe!IWQKo{l~4wNUJXqa>pGtz(!9+6LJ!T?&a- zpVqhASry4TbLiyxNSul9d4B$R-K{kdY^B@!qpXc z>S_It!7eIdWYz@w`rStcbg*7u4>Ub>ow24o%bN&uz(COuPF3=FrDOV#M*lD{@Qi*h z8%)XJd89QQU0zU-ul-yn(xpNtTElny6|yNfh_uC)d#Naw-oV_!$y#%zaL|6|$yyxm9#Z0Ce>e-i?2hb$x!;&n*`~(C;HzJV)0Zgh~O&jfj{3-jYd-McqCVgs@e&!<&X&|15@g9I=35 zp#G$KtqGo9KEU5z#3X!6sb3RoUAHZ}xw#>}QWp?#q^S;J0k?B6M%9^0VZGRID+i)m zyv+l6;BwJV>%rRFmO3*319mUDKc_B$mlmQ2(afE$ zyQ^Dl`&uEV1O4E3m-4AozR_84vZ!y8rDlG(cu~&mA&uF??P0oqZgBerOlFOOfmhAw zN+8osm+I=L+~-qQ1_m_U3wdd8(PTuK$XG4J<191>gKfn7ch}KU%3_D=`~rM7c(^MK zul+}x6ExJ^aw$xE5xl2Jpp~U+l$ds0FAq|m&z~jtJ*Oig?yoz%KR)^)0kMO;toBz+6s*)ILXE@z=o8u)%QhTnb4 zEXi%@hd7l}J%{#E!ilpXEMf)m<>yPW;~(`FqsQf<3HN<=vUIZ8tUD$d*DqhstH}E8W>`=R-A(cj@^UJVRs6x<^WA6L3_aur z>^C8gSSD6+#wspm{0lq98Ux>@0$v~rF=HoE!L?9|y8+?VHk$fop`LX0stIt{rT_Ix zHfsla`)-vGkJ6`JsP%Y|P4PSl%(%lO3zGiY<65=-M7*fg=6$*j4mEqXD9%*DGWosb zrMdKxHJuEjyK4QTeHqGvtC8*Y?l@%_zp7*~bK6qv)A~SAuGjv53@S993;jr(3oNd+Sc+TjU?p7LRM z#e-u4YO%hb)^!GTBfko+g*ogu?R!j(Qr+O5h~F9; z9`_!(10+5S(C+j^e2S%IBoi^x*!&peBOK1T$HfhKj7?23g1Jan7HcMcD;TCFFQ3em zpK(-?<~0uk1(dqJcdRer@UYCR*Pf0Q(f4PPu)Z-G&1d@zUI=kZ^gT&l&;(9`%9UyWI?+c+Qj7&xQc zgL(y*o+#M90rk9#Q0{ZEHn^dJIYBsy6ZJ)B|16^*`8&0tozDcE|d=CoMv=Z zanA)%n-E2}u*Y>rJW|rZySnv3%H=Ini>oIN_E8xocRg*jK?ahbFY@9_l#!FrEfL@9 zd3IzwP%vXZJ`QIejLT~Mz@~j!Pgkw*pz7Y+H>AF15-eGrNqMdeP**y`NJF@oD0rt{I~ z**+o4**LImR9`u5~W znEiTD1Q(PxIzB5Iiq^#t=3tOaD9mdL(^(N-HB5@t=wf*jbT+>@1Kx@K=Qh9AY+LEh zw`Nkph13dzsz~E*#5Yelv|92J|4cc+OTRoLcO@tOqsbg)E)Kf9UDVq8WBux?Tu<(b zyXD%^QZxWtU$UN>mWSMhB6VG?4CTaguSOf|Pq`UA$tRP}R5BeSCs+LNKIEu%=FAyF ze@{k&!?XJ!;3~&*Fcb?8o87*z>DFR8sj-&hwmo7yyG3S1_8vTA{xXPn* z$lw>9^H(VGzo-x5s$9tv+~dU$aF+bePt5R=wpMUanc&+ut_!9a>gpX#|blth%8;;{*u!eQk*P zGtA*Y(?Q2Bv@3w!e0iO#HXS}wHWofn!mh5K6Vcz>DFzP;x`!J|%qMu2Kn}MksMuFj zYSZ>+?t<(lLqcM!le5W!{ztDK64@UZG0qXvkO6)kr15?`ul@z7r=qGKG`e^o76G+2 zt_Gzdt8DA2mkMZoEqs3X@)NFI7cnp}@ULzT2~}liXeh5Wb|DI#%f`;e*!Aza`Uz~{ zpG1R%gpBOp0=ECPME#%19Q-{#T_ox9)6~>F1NqyczMkcJ@WA$yybuexh@e|h_2s_{ z!^!QB|AIA>{C8^V|Ct;9Zxr$W|AK!fZh|lAiAZFBFGrD7@S^?csmX#~bN=~O7j6P3 zN{R(&BN5f93Jc@g%~#wdqN1X}{3vedhy3o6P^!|40xe1^N<>YK&!@J^#iNO~DDXW> zGviQgGMDqzBp^w5LxX?%>Oss;zGpoH0~ZPx(BYrF({Q-7)kb%mHd2O!AjB=T<~G7# zbDmw0eO4i>Z8(KEZ^Ajcz=)I*!YWlCd%k+5f88*gg^|R6>j4nI0Cs6HrYt38p%W7y z?>68A`Joib#=-VL&HHI}Kw#jxij0u1wrtO7RQegnScmqT(@kLsBK1GfHv7j@3Gpq)aQzUSB?nRsi>GQg-Chrn6(bu;PGK>tYU14 z{(FXo3=NIat({mIJ3HJkN&+tiS^T z0!&MWz@R!EbZCcgJL34jLe5z$vtNc>p|asi^YyJiSWhmhHs=rrDhFrcWYtZ{H^I)a zNVRHn6R7>n=iI=J!I)!Om|~V@U;1!CxwI>i4OKFdh7Szt$BqK7#>K~>Pn*bssX3%> z3=XF$Ak6ae3kdA$v`y7ZS=(DhF@fgR%`j4sDsVlK6cO4A$Z6Tz%-F)3C)yRkud|$oC+hH`9JXkl~hg>xy!2E{s8&&NhPNizm z`U_jz+g&Jo-3MSG0~Tl)Eg^4Z1&A}78tUpHS{RHHX<+HAsUv!1x*!;hCkZ)b*lf7P zv}Aq%{tS#hi0BQfcCNp}sDnLdyV`%wuap`R-j|aN$7Yx0$dLxZ;nt>nZZJom#?tdN z@dGH;>|B6s>jsw{ovGb%Lx}&_({R%Zp8t`S^Zkv^O+{%sQ7znsH%LRzYy?66L?{2v zo@IM7?@Z~!SlPlSV&@HC=dro7so^6TLqnSc=u@-n>$#%#dTg>HZq>R_3|gxFXQ4BVh>RJj6mrbMDHp6Y&CJx5S?TnOiu*M#Whk!TwHMQ(hiH zeC%lTkirxe2??0JU9FsoN+eK21vAJvU^xE%B+U2ju|0gK0^AWB7uUMF-(Nm$a$36L zVnqp;v>R|5&89rE(RWDS`ryoF=co{sm6{1ZQnJ3~zc0k8Mon2vRH}oX4ZVDI58bB>O@f$6|&YYlKXoHYQ18yhbWu<9TG>ZXGh*iR={|gJ?sbOZulDi)k z@_t#p!lq=12&7qYb5VH1J_^Pv(M-z95haze^yJF&nhr1hch)+4c2W;Zow2@(i9A}F z(qv*Y;$VMhs_4KcKat?Zy`C%O_Ko(yr>F1ADXfw?7ic9jxxUcebMcW=bF+Va=T3)x zsf&)z$DW=ZCRtZkSD^I)8y*?yg#QV@oP2uamT7d$eSQJazVWqNi%t$vVA|Ugbyucu ze7q3gWtf?nTa1NEI(<&Lg9p|BEr>#Ppo_5up7*(@2{eeuf(okU8Vgrufa-+H_`oP0 zzYz~S6+asvU*5#RqM}CMZ7C^oHFfop0lUE*4n22vMsev=#})#VPHR(7U0q!A%F7La z^%UX~hdc@$u-M)2{CxL01(r*FIIElrDa_~Zi?SF3do28fmN+7a<|eK!E_QXKikhKD z4#)oNTRWuh51qMk;Uf1El-+uJsC@o4=Q*nU%}u8Qpz8}*rjT;4SFbGReLbtkrIjVlj>;GI^z+^~1sxqq7$C07da0ozTb3f%5X+ouw%&)q~KM3*_WI zIqjkv&T^Kc+Wh?d0F(k0PQ<^h3F=GDIo9CUg2#MGIo+tHw+K%^w~W=G?@7vpYk?6C zWEET+wmvG_^@juR=mFR${B#IcRRBa;S^4a5zcs&n*lb;9JyzwaTV(tej5lczK^oIb1hodXufh0M2! zd~&TZ!ey-dh23z?LRSlc)M|7P2#f4hSTZv=!U26AT1|LuY;wnp=VpIbSJubH=j06W z!^jmIJDc8-_0O6L3U7|QefsUomK_YDynTGG(_HnI_KBs-R{?#?d!e*LW?sXQrnn)m z=iv1w@mPHZA&TE)+R~2T9idgb@%rjjW3VN&bLMY=CQf!|N?!pKj7-lu_u>ac8CYjx zI$dznZKg%r1T+3ekM#5NQ+I(X@Do@>u$L!3Y?sezPX#zg(@*mIjULH`I}zIa#)B{06Zvb}YI};EYDzo{%yX4- zy-w$&HT1Bzb9>VxCp&IRgllWsjO^6ty3CE*+nw8V^xdC;F6#+$M7@W)0f*tu99++u zJa!KpIuWv8M`*50FOo&p6zNV>&NPDbjw-Tk;eg#Z@&y-0Z3Ia$49@kXEyz9bR+x(; zBU_Snb}ljS!toE`+<*~SZcH~25sQDgeW0o52~>F(h*l;o9mI(_+I-Dj5ZXs-Uq>lQMj=^nt@X z1k7+WbhQcx2HZS6JnxIJJ&m~+Dl&7KpIua${zeEOI{WUw@h6dxk_uvHL8)A035{>D z>|0c|A_m;ofx@kAT=Bh2vA{j|1TpOn`#tuDnRh0^BY4F78x=?Rp&i=>BRh>#4les# zoWA}1uEjb(T07cVMhVdcBO|3Addb!+w>Gz9r75=h;u7|o+j<>_O;VMXHY}|1la~Tr z%VEr=r9#^~UK}F&FJpSAG)&*T5ah(GJ4C8Bg}XOTxv)8@oG2NW$nT#O$R>A$l^Hl~ zUC`9hDoidke88oAslRWaZ7YPoHRjsPoCC$^3gEnJNzH9=VtO|>L$2R)SxY!C)W27B zm-5!FR|cJq>AznfQ&DwH1iKU@0s12)PFvUM2QXZ6rsi{OckYZ*u92QQYu)pvyQZ8W zugRlhb?Fxe$1uI1210CI4b~XG5cY`9qD|n84Q|NP(+dff5+65M$PWL|I-|S4bGZ3a zXkWYypp`rH5?BS1Dhg$r2jyHUT#njDQJV*EcfLn36~t& zun@t1lZ{WG29z$g3L6gHyK_fjqUJCMX$zQKC7sq7CLnqfM$4-|(?}*^Hv#}!Zo>j< z3(bT3&Yi0XO`m>t(IdAI;TX@UYWoqj=oyd-OssI5G?8HkxgaLSPk&gg&mh%QRKC6& zgMR$6T2CYFokw9d4;J9bwamB=CuD)!_PPAPj#nIit0IK)qcO9AFlyp>M42?`QF~#Kb*$+L&W`-M_v1bh4Av zXM4&upexMIW*u2wLj*=JvYaU!tv)>@D#B+rt)rGUVj##om!K!RH}kG zZ4L9Az+pTCf@!F&QD?7@#m*z!qlSoC7r71Y8~yw)zNdz5<7-`g9!{-cZ-Wv#8XR{J z%lU0BALI;~Kq#gS)GdG^IMbMl!QOT6>+1T{kI-J}5M9>*C}bKMn!cVo=iR47QH-xx z&_)D-Q=|7@ggQ8`rPyhc&!=_oHK)y2SC{820_Dr1sB`xPa<)2S{7Ew7IX8NHI|o<= zJ9E+lIXwOBO1ynSllXh}qBS(f(*2HeWhH8nTVV&Ae*8f2MBJ@f5{k4RD4KD5p+GuD zz>&Otn^={y+wPt`yJXc@*XC1Yi$tt+m9`j&aLYcHb6Pk@>9vEhX|hL9+$sje7)O;= zBBrih!PDKfr1;WEU-y)T_vaUa99s!3Oa+Ifng(g4bMqsyhA(@=<}TOl zb2T}qfN=wSRF8!MhzlAfCMBRldHM_i{l+)2P7*MD<3L5yHE)M5^;&0aSAt~(HQp;2 zsgS}x(ESuS`H54W`j)V6x?^UrjoZO>P%$r-31aW_GZ!^|EzH_}S6J&*2qLC{-B3E= zTro&Qs6lFGLjYH=nw8r>+-EZxvj7QH$%xY_DjtADDrICH_Io%&xi(LAot>H6V8{2f zCLa?&DAvs6ls;%%I|YCl2X0TB-dUVsA*K2>hn`dR=`%-haWT1_QVDpSriP5sC`zZJc_?orEVZ76oZ|wOnAPsMWOTZ7Bl=1` zsZHOzbmF=mD2@O);oZ#cJlPo;bnR>+xWaH1scg@;AGbgTq%qSJ-o-*&o|luOv=dIT zzkgu4QMbSOMPF$5o1mgYq%3#$4Y92w$FbeipY~I0c~b^eMzWC)%mc$UhcrFE59AVZ zFI?hZk~q|Joi0#SwX)EQk*zvGPi^kGFdbV@bbNE*6k+ZCsdDM>NcLq$fIc_w?H7cf4rS50nX+)t6Sq)79AZ;WI}*O zm{mVqQ^fbE0u0vdfx5bED(~AugBl_&0<z8Na}^WQ1XgBNgBO77MH}5y0MS`& zfvz<+NL!t}ejuy%#OS@6`c+8p87~-*qQ1&Y|LChT`V#Ji4dWghhVcSS4fqqs*-s9X zH{1>MfVWqRxi)vw&^$9`l|Ni=5~26j7i{|t$8KGeV~DQa$Se+*{W--O!edfQajSJ- z*IEb`;p}8wy~3^dE~L90=jB;2W9*v(x~(0J?~Ek*jCHWPzk8f*um%kUq5F@!Rj-`U zc(D#{$hCC(BakU3#$4=9g!r)r$at!pzS;5%qQKtQW@ln*#v9D5Xcabw(Wj#^K7j!6 zDn3!EeahFjYPndRSr)XNCI(*$3N>eq-$M!)jdf!4zFbz1u%k()vxZ6>vc_6;ZujOC zw6h_qPm*wpk4VczjZ~z4uf?J z5~+G=l(rC4kk>tareJfEr{d|vHMQtRE=?Tio(G-3p>qK%Y0vs5Y3jq1rbaZr{8x2&Hw6utd3L$_wCsIN{eX411 zUnqX`Q&PKPWN3 zw(jjku(z_Q3|>9CD;+vnw0w5=0%(Q<`W+k&7qRg9R%2$qke`>OAY7TM?&vObi;aU_ z+gpE|L|fpCt{&ay?#e#hCm8Gr9`B>qTUMPRDV@n@Zn}`CV+f1!p#_J7wH7a{Xrb3C zGZ&W9Si-krBNCoK3#_ACm>>e!CVo#8%li$MlPo}r$tzr&rTrYs6d``LmkQn$`4Zx~ zpKWbv+Y@|~)~rmI-`93`vb7DdLZeF>_^mAG)nk85cszr;OW$oe5GI}kRw1#&qoWxh zMJsqUXNMI~z7ztwE8xVeF%vs0NwwI-)ZEytS9BfhJ+2{K+C$wRIPECR=Gc&*o`J)E zxYj#rlalmiS4PAp6Z&x8(wRlz=se{b`@oG^enEcJ{RM~G4{{>d8e#)$o=pQ!oTmvU=#JnqUsHL}akfD^aD)na<`3x zS6vvEbg9wC%a>~dMah3)tGD%{|0SOsZ_eH_O4&`E3k$XGNpX?Tt`IfPLQyQB*fC#t z-xa{ux^~7m3YpWCP$z9#wR%Xa7~6xLux~txWCuTENbQEk4>ii|?35^P=s_eHI-o!d z!eYTh#uM>n5MFam7jmdfNomyGE+2?r4QbMJ{4pNkwJ=S2?OG=27ScU>rDD<)jw@0G zyk|H<)WY{e__4Vk1@;=~^P<;l^;WP*ETCt|gwc!X0;7#;FzyBnNUs{zN0GcyC{hMsRvX6fXL=Y@a%91DU;b5tsfWq&Wl8%7T(7XS?} zn+khmMof^1^|JYTR}f(}fuqh*PVFiPLn0%dN(Lwu zdU=ro11df4Ens`Vsjl+!F-oN%uR`G*#oY2Rf-5FCSn6$qnaEzlJ9P#ID1iSTO+7Je z?A;aE8wYcY`W{jn=}XvU`<|ez#TfErGt>}&X|QUEbf9Vn*V{@vJ3~UjQmv3-gtH<2 zBD8J>^mMzZz^r9|-whVmJaQPzB}c;qn$}y#b12X)#Dz5Gm;kzoM5o*?Y51Iq-nAj*wkPo!l?G8Li6cb1{AOgl*oC^XCQgdCoNoj^;Ynul z!*q1goj#)g&tDOlE-S2+G)hlR-(K3;HC+k6VK>&D@kS_QZQm{Lh@fy_3F%xpy4UEn zSS)-JJo06XwY3*R7M)T81O|pyQ8xettXWiSO5TT`54pf!Tm#$+|%c zhFMl1SrPckNG2cyVhL*I;vtb-rjyNu)44$<yNiyT*GU3;UAu0S~x299^?_SGbmbtP9KR z0^wm{%tYtBAI*7_k^Ex0lfCE!Go|LP0lLG!6(@*EK@p>H3mdXY1?K_9*hea=vmqUp zqRt)GKKPw&w)<~X3C{D z!+n=EQ`|1mtl5wDFek_#{K(BPEqe~N-TQj2isARbfTDA2^7yb1?|ui);oYcx1RES4 z7c2Co9~Ty@oz~ssHGk<9ojwPnxE6iHF2UaZFy6-AiuUK()_!nAPVqgZuK8Uz>!Ikg zXXf2qcke%V@QflDLLZ@>&C*8LD(2zcZndH1v$L)>&dBWR9?E`aQ~I--Zs^6e?alRZ zt#4VjpikVqf`^wN^i-p+2pV|2kyUsXgI($Ej#d-HNAaJTmywZy7gb6bAhop+nr4)_ z$e{}YOyBe8=LhtLC9nX^^>P;cinejxuBD#&}L(TrBJYjc`|F z>Eh(nH@)(5ci+7(r=jWJ2`dpRhhgf6OS#5nuY4=Uhx-Z#1dzqNbcBJ*TcvVUQ%5WL z;wu9=yTklm(jUi%dc;57y^9{}G9m6lq`bw9Dw^rSlEI8fUW9w9zHN&Ex z#50thW}qI#lv}bMX#DD<6ob!$rzblzT66P*anXGzcU9-CP(8i2P9o%GrTZE$<_07Y zhfgQJJPcW0K|T^59I|2)Zn=k$vbNb;+LKLKOLN907yVR`OrQx^X{oOY!Hbj{h3Ipl zRw>ZMJWV#^t4pZr)Gu9^?FLH~-O!2x2t})WS>4L&6Pvn+WMUPbiREj92s_c;7Cyx} zQ$--l=5FXdvhOj(hR9Q!f3tmR%c+D{)4)*|dh;Kl=4)|sqLcRL~3R*3`5T=U}uj;8AqB@V5DlI*SIEEFjl7q$aeOgC0+}n zweDr^vhy>ub}^5sSX#d0 zPvD%8tO+B16&h%3t_sR&zF8OQDu*JZ1!kKMeQ)9I9y*j$e4rpg>_%q(Sw=Qi9Ked* z|8BMCC9<;kb2*g*$M$iyPlh3+dy5N2mS^>uhfI>;aIlHU1P9q|{^!pMfW}COuYA|c3`t%qmi|GfBnEtr0!|!s&=v3x&$0cc3_e)2)eM<{cm@+(VHJuO`;o=P5w zFII~6R3!}e_e`r>=B2alQ{cuY5L|xTy3yNsj7^_}Zwa#eqAPt(RjoR?qJi*gkoaN# zedc>QhaBqOrP?EIt}7Q|5m9(JeZ23XQ?OjyD34E0T|GECMdI1TwPkb^Mq|An34;jMiuKQh#fx7^%hrmEV^N)?9%*Df#bz>IL8QjZsVhF*^%7ECn%YNhLiIAq87xQ ze4Pu{HDGdNqaVt*oNyAwoi%dnf(5)YKdV#$q4glX-acVurW-hKD{s%FdsLVG$={wi zy!5kHko4A|-(&TQxlU(58WA*#8&#&F6E4h{zAEc8MhU|>$VSHsGeW4i__y^0SlHF& zsV4YKr7*}f;~D|`ac)c0kL+AFm#TvldYv*2I~?5sP9D1Gj5*G6o(XUe9%z8JUeC#rbfae(or_O7mYxq!3*GM~zjc2K@c;z;pBnqQ?9bGBHL z0P{0t@f(1NlhG(05e{2c)&H!C@vL1Rgz}y61lM;N9yw<4^x$|xYuBQ8=~w#@(9(nY zRZ96g+dq9&msdBJ)-mp?hDQzzvPAK(WIwnMO>TlnFG@1cDasA-C^|a(e`1tWI_T)HKXQ+_>qXUW0 z$Mqdx$Uwh~f=(oBYoQZ?LTLk`AE0UK_9g>AgyC#Li@kR3LAO*H*f*g6_Rd5|6aqO0tNI{v5zp;BK!4@4=(t5E zfub*sOuy8;^c_5pBC7Fy|zxe++-+~5mSYl9D?F=Rkt`Wwz| zE!Rg}J!dGv81$)SC^h?wM?N3)H8CXM&k(j~f*3>Q=BrS2?b4eIl`k*3JK9K_RCw`@ z7+`#BBqkvh4(VD*1F@{bug&~Pbz@PxtpKeUSvvpPfzXK@E?wg;Q3N7>f9L|lQTQCa zfA{GY;)b?DM`!DD zFEMXdQ|oDFpuI&+_Af2v#?bBU?Z*iuqXPfHCm-4#Po1MOD|?PhttGM#pG1K0{O5-( zdHx3UlRSyPYW>xJ$#s&D2m*k~Uq3`b^5j1_9s&P>L;USO$b`S22BDOghofl%x;Apv0Mp!-*4UTemoF~U5T?w0%!z=mu0$U z)Irtbj`}ce@csZYFfoS}iKSh1dZ++?6Ck_uJbS1~NJ14xl`loS+<&C4^YXsC@wwgD z%#TLYK-uEF%S}O|+Dc0F53egh-{*EGyNMOT!(|{fG-T^@GVLwSAjl*MjuvPJU;p4n z$I9=f)TCI8P|z;g-1p9$Kd-UvzVB*RGGtoxuo$SVQH%e1C5s{*a2gD=tP^@n|3nr1 zHkdF=NlAt6VWh@G9%=;4iS20-$*R(+ZOXY-j_Xv^DmhuYksc~Cz`W^{GLDg zpGNumNwV8N_W8@|{{G*~zx^Bidi!_cL;v-8eEHv;S^wMr!I}MUn)zSfN%Ftwf?sbC zw>dQh5b~&US&G)@)CLk8e?G`>@9L*i)YO2kMCKVtYviw2D+hiM04$tDQo%O*^XD$# z&aonjOZ2@#y8z{1>N2wW-(7$+{PpoCp|P8|_Ah09lK)Lht_Kj|BqS#Fekb>yE#(`G zVd`+OfnJ+MzclCN#EJDK)M_Wp-MUdD!kp5R&|6hSPFPWvm0hrzdNVXT;@L6&uV8DHj-uksG znl!ZZH1!txE3QxQCsg*mY_^Y)lPe(9L1aIKY~)XuYSKsOl_SI(Zn2lK@#miiIG0rG z9)7ZUzVeH4`|`ny+U5e`Ua`$rSsduq;nMb;vP%XYlLvdO$viy@bnInTLv_cOj9HlX zG@N?iXPpZlS&ofbH_z~|=@3XB(sXkGm7offcrF*4`Wt9(YzH_+`Hdp)MX_QXGJZZh zW~xM&JWP2v|ERQIqJ1v91N$N_%`G+T*N*fb0YX6Po7L8~YYeR5_Ljjaq{2&ES0^58 z?+Y8m#p<4WU>G@a^2DCxYr(JM0o)blUm-K@D$j;jY+NIV{C#>lcHBhE zyG{%E!wG~5OW|ifcn@55$YB4+CHp^o2HD$jgQcI`hpU(wWUZD;-pt;L7S$t#n8v`3BLvn2>KdZL!Uc2Gdy07L*c3{}@grC0Jk(k)+< z!?wvN4{GRiQS~V$uc|w~R~$;zCXX%CAd;@g^2dL7jilqJ_yz9@+ z@!iV0!zH2|w`7y;HCv$x%1*yQPq{ShCM7akWr4DzccBr@!V&w}J3> z`Eq`XxLMAjhGWip$J*j~1ie8&CfPi>Ut{1mq*CmQSXi&b_td2I?xC$4Z-dFj!`JfV z5VgCo^y7(y4tJ|Hh4oKu2J%yVtOx6`qvu6M+^Et&Xmy$Gqc`aZ3tW;Kns(i9LvEOf z51xqo=);vEV?i{@2PoFI_RS?+dxpF@3H6=E&;ZRqzq=Pg9UHP~`^UzJN*SX$Nl8f{ ztNimr%?O0*7#6P<+JHf>VA-uPp1(n~|Erw+KPfT^$$!ltY+r*s+&|qkDTj&m!Gi~1 zeMZf015_sw{ zfU|Y4|FSb$>6|%_HUZE5HIJ9(_{m?d&Hi&q$yQGMO^ylLe+Ta0H=KrzS|HbJx~5YqoZ54#wI# z0Sk534!TfzLRe?aw`j{jxfHE=e?@H=k@xTaapkY=>2n~PR4CFR8?x0tCfp3V^3}RA z4eGtM-q$nwAf%rS(Ev|Lu@%Gj#e4{Rf8NE4dJ5jDVT}#vC?3LXdemGXzo@OX!=#uf zB6*`N45s}^Q#Yfp)I~8_TrbCpLRN916Q-%AW{ug6{kF_cCdtT{&i3(Wj}zYBj!cWn_lh59@8#?78O;hj2LuSSWkc3pc+|U&4C}_lrLtQs z(k}}+?R|@IsU1CxBRZtpEAE6e)fL#^{A-V|X-epS_$r)9egSmOTSo(Jf9 zMp^iMiTu4c&;EQF{l3$>;q3%H#aycR~xohlgr1=TQYGV zR#S5@?(X*qh>bl43S$-5g5QXOqN$;=VI?gav3;;UK)h*3$Mc|M8OFE=FRyU5+LzdL zRG$SpqP^XBgEh3Z5ji=esSh~1TQx7`7f;8ACtPn^z_;GK3YXgbtYT^!3$#Eq+&16D z7gY3^9kO#BOJ5Q{dXvJ)cx)9+v8K9p_`0Besjp^N%zG?pX8P)NT3X~zc=@R*dr5*6 z-5^GClxBrK6ox#{=IBVk{34P)QAK`Grd^7sm!Tg zvRJp^cqug%C#N8{gQdSlyI@$GF`d;hkTUQ-`mS!ptICv)%edkFwBZrm*{%K9WWg8e z7cJzLX;DX$#}GJLg&o$JE4+1s&Dq!uL$iY$;!GYZ9CDtY_(}6ekuteMELR%e6s+56 z4dvH53g{&z6M84c_QJglV4Is4Piqc;>>pX_8LuG@MoXV8Oz^uGitls-Y53a18iu?* zhav!W1JrXg6jO_Dhu*()N54TPd@(!0pj-QRlj~^ZKtWxCu+#}oVjKGno%ykMNkdL9 ziL)P~qOTu7!7z;Y^2I@{YKOI~oX=(}M-cxrQ`vfWd&t__R%I>AJ4_ySJDvm$ zrIeOBw|h`O1~&G-0xTX`ZJ1+aU7w6xHXid}&du7#Vq7)?!V_){)F9~BfT}MLp34!a zDKP+ME!Ww+jnYl~7?(LbYz>aKv3_c9_}nH52y=F*s5!wstI&90Qb5JrkTE@-O{7A` zEm#sDNgRJw=p#@GmT(4DC=|Jt~Q11lArB7@Dn% z-~ceIo}qaCQqP>76;Aubz*fKdWC6Gr zVBR-3hdWZmG823cZ9Hn|!Iz<_9z^vn-TI=hqq7&MtJVwFvK#8_=4QBlI>_k0IHM{K zXLsy?Dk+gz0kHKd3lukRX4>J`y*mS)!$b3x?({>S2=-Vr+!_q>;T zVR#gu7qkrx^r8Fu>XxFJ&|~z?J%#zT$I;_s+u=Y(pk|kqo1673C3zU~crGC>OLGn=l2!PzU97_nN80}fooh$e0?V+M%18@ay$`1 zC(MMzvImbjwu>0gOqZDsvAMdG+BB8B&$*%bJX*I;wUwbz`LmW$RU)FG=`*Z*=qdd3 z)g$oBfuy}4zg_GSe;X`AX375t)MH?jqvrrs2z@KCi2%SL!q3 z#l|XpuZCgsXcgo4nN|Hlnaj`xuqVz|)TJAh43&BhsTJt$28f8F58AzOW!V0a5d@dd z(na8L4san{J}>QNl12dEs6XLkx^o+Mau@+bOqczOUU7T6xVtYILki40l30Mg3vk-! z=sibIbTRS6&i(NF?EN1!FSSfLHu_C;l&(R#@fV|lP6ECw+AmN5ZO)A)79{<*vmoD-uOSXy>~d)fB!yy zX=*%<GUxT?=H`X;d-?G(F_K@N$^*m(KXBz)*sp32H&2CI=lu## z%)PAje|THM7k)lHgO5V+&U=yET=P+*^v=#53l}Z3t#MR7d{|V83Y%T+VstDT-P72>_~kfhl_r}K5ezoVdA=s1x$>U_zX z@}#P>dsE(3^I+K~U2j%)dS-i@^Y9Ju-HWp$H9iMjPdKcISA|&4Zo7Wu32#7o%ZZC& z7{xhVdE7xr^JQJ!Xq}wHjA&D~X~2%IoeMd-1*M^PW2kA~X3e>&TMyi@Tk4YAc4?aY zwW(fpzq5Jav+AKMXLO%oUxoiwo;pznoE3xxZ*`lV`}ghC&z?JXt2Cctq1-*+Xz3A0 zNbK9MW9s&@jNw|JxetFIczaLUj?>7Lg<2mWfQ^}1G3D;WV=u3I&CM~@-@c3l^_aQz z7oCvXsxo%Ia5??L%Xim|17G;Z4F~7hS75B=5q2A2bqDhhadC0~du-LKI=6c%|9f@VvCag^kYP0S#A8F6LTJbzV zUUu{~<6FwVAIS5KhgghAruCV3ay$?W_n=^-*3)%lN#BT zk+F04?x@mPj=Hn~0`U9uilira2g?`m%s!n=RiVe_YV|6=KZ|u1Om;}J7;8In_0HHu z<6@lJ`AJ&C(Y)Lnsg2#!)>q8>9dA|!bLO^+R7(E&q;sidE*qA|UE6C$Lr&?YHe+nM z@WMj7?NDI5K;B9dyUOfR>RS<06$C!^Q<56OsSONlMC%d7#97hq2wAR28@qe&Y>)36?qeFQIWYdG{@c2=oEj z-Bc9!ijVsJf%U@k-&r?sEJm)*N*K51oYhzjy7M;KB!@Znb@!WN$5`e9uD#QJX+M{= ziU~m1ti{6?hhun6e;8d1eqpY+ot$)ziukT;UPnb;OUhq(H#6E8b!Arg)zsA11m$cQ z)7~$&83|(pa}zqg1~Z&Sbb-fiynW7f9owW1OMlBY)k~iUK`oTXkP z^V&)~Emw|YOts=R<;0Ac;82HAhw0qY2&h!?1Dv9K@Fz4*DJHaqnMsuGjE zMD0nQ>|t)c(5|tpDv<4$W;>t%SWfC|pd>@Bnq_r;Zcv*Kb3)#%$RXAkDc?PVL(^?G zV{aq!IG*14T}?V3?UcIfgKu|Kb0IP0l-cak`emoqy#jah&Y)|REtpT|@cDI)M+up> zBUMC1-UWYu>Oej_7tG>L?C#!W*EuUP64qW*lQ`0x0oy1(#+}*!2Y04BXJav@Xl$G? zdVTq)=6j4;u|kuJjNzNO?}mx=6Y5?!${Nh*O*f!Ch5IzMy{|gG-~Cj)0xGy)#OIENCUM(M zP+YO@%o1YoR^ zk_>iBPxfsgdGZk@w&oebTX*W_n^Helhp{1txa_)pq%K%s645E_W>q%(d+N5`Cr-rl znDP4u`$h}rW*iY>5|1|ctev8-(55R8!kA;S5)4AUDQhc{2fmBAaQx<%O=bi3rz{I% zJf}7VK7H8}mT{+g`ypnQBd1uo9sRe3xwxEvD||H)#m(+xyLRk(JLS3QvA5h-GXvsj zm`JqGj~4cyYF`crQu)zg(!a%Fp>N?s*vkB?$*T!D8gWXkqR)EB-oGHY*Z1@{Q>Eh8lb-*Mud3Wv&efEJxyU!KZ8T7W=R`8Pi@dhPo- z1O?C0a;raW9#Oa_vxoE1U*CM>x2r$EA*9MamM=Fs8kiJvDr2C(M*F2mJEw86cFG%* zg{_?Gd5ReNP^>sW-g6v_c&wZy&%BB5_2l}Y_O$h=)vEz>8~?!PiKx^e>5Ges!ZwB2 zdp0efD}tEXIYVcCN_KZXi_4VsRa8?ulv~FVQ@i`;+YQHVupKfF9_0GYcFM*VjjX&P zB4Sk+y?ilZW7i>;=#PDq112^0Rlz6ZTs>ykO|>)j?BQZ#JSsgMFxb6y6VW?6IQV7A zcI4Vw$#M(;VY5_t0PCm;w>v1(l5kiw>Sb#0C&To&!{0YSG!H8xhJ>)5)PTo+8_!W& z`O*}CY1HwU3IyzM*vZO9%GVG2I5;`gkoBNYy?@4Kk+2f5lY0pBT_>-o_>947T&q>s zA>6{qL5}rFK#SF(;;-GGE~n`ZY{qt_tqwk9+DAEj4Hg_uUcBsbH>!CcdE7)=0eh?A zZgefrXqaBt(z2^i;Z#CZ{YgYnh2PTB60IbPz$62C7|+A3dTy{j(~zg@|F*%+a71F4rE6gGR9_tpLQ!G>KDVb!ciFp`sb0C4q-x8A?_ne(gjmUOgteuE6ZXZrrG z-8Gi^#o~*J30ic=ZftKDlj=HdhqBH0wcAI19M{9gm}D^2#CKukoP>n)hVga9OX__6_prH1CCAoz$BuIj z0dZPd9cUe~68(PSd&~DX{cqUGZtF?rGBH6BTND2ED-UD|_wDS|RB^Qxyh-#^yIsx} z%;pwnD03{UT(q<_-&RYtE#b%Wn=eH!GcpE9h18%{_ z&F-&7_pUjyRq!P2s^g8co~vhPACj-vBP^ z2Kv_BtouIey=b_8-dRvJ&EbqoF7}>aR{IqxsW+l6=_(1}y#+1erDP@D#Kjj?63R+S zVyEVgUbGUeyeKPsWx!oW(7OAnuGH?`FYOX?A6`&j*+qXugqf(pYs0g$9_@Acj&7+< z6YhelW~Y4>dF~Is>f<m4 zbaeD|0NGEP7tx>v5Co>9k}`jj0sX!v{$DcAZ}yhiY__|&Q|ZZwQgt!TAwE5DYjIZV zFPF6F8VmaJUnv@VD&Buwxu?Cxl1u_kb(2-s`a{AKB|9;D_SjY(<-%LJYC(L)Ik7#z z12K;is(InBhyHvc5*?#c68EXU{{w%~JtWe@_w-sR%01Il8=%zbyE`kB$ukqDl`@lX zTzs9)^8PYqk(r9y)BFCs>p=1!3Y99HDh1h;Pj&sCeg&6jS}AP2LcP%e6q`JhEcHCB zb0y2}lg})OdZyS82eb|f{CV?fO=8%&6G@Rv1#O|i-u@kbeiLrVF}NznO9LKZ*{>T@_5N#=Jdc1` z|I^1FYt>3a&SqBr^Y6j({a=VvG=B@BT8`vejw#vL*c2LIc`Qal{GUwAS3pJB;1Y*d zyIDX5(ZKxYMZWpBLSrYI)J1;zcz+x)1I!PqWo2=ULh}k~^jTseSN_6a+(OkI`$%-d zG`D?`!@3j3ctc&AF0YejD@*fQ?Q)kc`CzLM&+uZ=NOPKjA3kC&A?Cu1@;Bz-3F~V# zTDC6_nUt)yJ-a$SlEf~;#rHMpz(++TrNLIqcK_Df0rf*y;Fyv6@tXEtxW;kPUVQ?K zo`?lR!2+hbfxS<^o6QVgLykIQBbObIeJdy!u&Su>>F|&Wno5a9JD4~0*)W|=SXgbg z%?9My)IcpJMRg{RuHF=098R?!jE%ua40L=j)$&sBwYfF67V;HqT*#EWoUOPoAqj>L z|ID-XWppf*yu=d!m}-=3zd2zUF~Jq`9-X1nV;;NZMHan-Xp`fIU0-h zAjYZ+7PyQalKKR_@+b6gGBvEC^U_EUrh;Y%xgsaJJjF?`!*hh4y%y;hvou7& zN^y0oDz*y)G3aDltJFNL9`yU(tvfYJY(Jsgb?o?Y^L9HzM~*IL18lAVx%bQUWqs|p zZ=GXKc5fI48wwo?8EnUk95+b8T)*LdVY3rE6!;KYO53T`>=&{!DS4E8{yE3QMh9#+ z!k(#|9urF*Vp~aPXD5I!Jcnp(le<+FcmkOpUGgv*3y~3RT$ih$A%E)rFWsKrUd(qp z&z(2!1a=o41Ia_9qKu4U&`uN6b{r5;gYwkJTtzZ`Zhw0%O^8aF2Szg6mF(?T+~#fe zZc6&YZ@Fx-ghd=JLJJ1=s{sR^o}OMApr)oiXi?dUokH07+o~8>qN%8UDN18$@P2yw zZ2Ggs%9yZko&%Ssdy?!gJ1n`%v9soKGrtj3OK@294ja%YTs6+mG!9VLC=}xgxL#9p zX(5%{9+bdZiG7~82|Qdxo!GHsS}jaWtT=>T(GiPVi!&p*B0tQEi8$-=?D_L(EU$cv ztypN&A!MxB$P?+uDmOUZQH*>P%@B3@vu$yxsq4OffAjkF8496sM{HwRnNFIn@wQh) z-`I))VTckE57ix(&B6K#|N3~aKH)wd7%?~ysN%hQ@01c%W7TpkjxaHagYkOC4D4=s z@M_^VVigq@29LC4UltY?hD68R?Q!t90H}j#&afsUt=6 zpFgL;6>?tBsBB*|dy{|P_KP8cSD**RV37BYT(fc^f>D?*NUW%QnAi^QgN{^b1acQO7@G`NX6HLQwbRqn2SRN|Cp30? zmq!vBV~oQ=;zVqWt>{s};%#edYkXe1?k8Wd>iYD=`BD#B^Zu`lL`n{8L#PiFvCCOq z?T9)bkY+xl1hf`2(?0i2wvMaXe(m}36_e9Zy{IH>KEOT?=4tbJVf!Vk5Mdi7bko8R zPfk>q zjz>G-*6Pqua7Y@@YGnZH$e!W|V5-q8ncV1i?~b8Izz}@~DA$!s%F74j!sdgatuxD- zdttvk>=Ag>?kx>h)_I#5hAG!vn_2pA5$i3beTnv`%+=8(YAS~4cA;6{ABEYM!j_l4 zH~!+GJdIQWJJ4$3m(YLnO4w%RFlH%*&6TizMr_98O5wu%K!-CRu z(>_^;ENrojo13$8c6Nq;N>I&KsHv&JcDS0+*4&L44v2q-+s>(+e7d8f!v#)CySb4Yx8+^KBF^d6glGQ# z1D{yKp4AR~M>U)V0~M2~99j;!diClpJbmmtRD!`0hrXw~8=BmFEM6n_?|rMSy@JOS zpkaSaKtSN+-SrDEqGL1L?XTl0d_{eU7#2~l;h=_HbJLbuYFDqu5L?WE7njm%fE5pb z_d1B8gm~)1jj4M??emZrpq(p$HPlKR4;xMf)rXs#n8v}(g(k@1QJ*$=eu+Mf=~eIX zO%nx@a&i{7E8PP*ORN!08T!Fc*{QAb%-5G$nvEkk-o>S0RF{_PMO^;k_W6M3+_6_Q zTrb$A19?Q$s5V4GaizdU=k3sDq*5TjyoOOG5m0w7>wEEnq_(b((z8XNW;vR;ISCO&!cL@YBG z!yhoWZlLa}MjbG5P*4_CdlJnn;jgf*dB)Oj-<~~#8h!W1bFHTb#U8#xr!RE-V!jwD z8Hts3^X-?b!$gzP($aziMR&5wPGvGN;6`Cfny#jB(G6IW=gGTmiQ{t>816uaGyLF}Q>V8(glX0oObAZrqr^&cn*gJm(!75g~s3=90tA zs1Qfubxvx?N|@@J{F+t+FD^k{H?N=ZKQ{6?eI)x5(gO`}F~(R$H!Aw#EL${zca z-`ZuP=WD^2TRYP5_+W*P&b7yW${DBk9!PVd#CzAdT{)dXd*wTy1Lcz|PT~j1*7q;o z4;6|P>=W)=YRmkR@_M0e%Z~HofHzdOev((5#qR@D zW)H}8+?|*F)@NCz?6M$9NEClH5LTrY;iQz1kWkJr+RZ_;<(#DC1We6s5&tEGWg3QD za>lPlKlq+;-Gt4gJx&$8$j_OPX3BMB%gf8k>4wyfDeJCENUX&ik+0vsGbe6rZfbfA ziz?Dixv=};0dgr>*>f1=ihTBN_eZL{sc+qN!(SGRc<4&rw&!{!T}Fn*+PZ(=K2Pjz z)zQ~CvYv+R2MgQK@C9u{{HRTIDKK3+wBwWA#kE^KTPZAAV%CeD#(H;|^XD(g%l|a9 z>m}9>N=ZqbHZ$YB-Cp3uT`;#M*J8veCZ%;m-SJ_ZJC|KLOm1)1l?2X=b*mPbVd_a?=TPUiYeclGT?NLezpb4==B0SiwBsO8frhlk3*6f3MHmG1T71I$EtB_FxO``2+qk zKKUYRN%GcF`{r3Uk3U>pp)j$$8vZ(M-~IU*wYv2xq^%p)B412Fx{cwaAA9ZzVfE|m zQqj4~mv`c^Jq-%llCAnAI+_Jp=^DE89Bm=Yd%`x%*sJB3ZMvDdo47+~n4k+v$+}~! za%2Dg{n(l<9xnf0_gIGqcftGP)k14ALh(h_&EfV!htIdjog5!MT8FthVoCL&W_)1Vd{~&(Sww?HAwS$Pn^&yeN5e{ScU3B{_rk8rNt*nk%F{f zVCkcsp36kZ$0{mk-lpHaU&Ip$PP}UNw+_08MULU&j46gq)zuW(7_5K$cBs<5tFek7 z>g#Dw-f25?>lQmwg?(>bWA@nK!We@f!4BKm z{0%>RZ#izap#|rDNA`PVUyD3Jo3}%9eX6sQo|Nddz>AYQ`1wmWHwHn(I@!4IG>=i< z>6n;MD#Y$W4@u&7y&t=`9(*drJpPLzXv2(0-et=19@^!h~;UuD-<=gq(q~tiGm(>|qfJPww!p&2(Egyr}B6 znGp~Y+J}U^Q}LrE%eQAThtPwm#k;4}ykd;mN9t!!k2AvZ-OX*-wuwk95aN3+T)t&C zt>OzN$-jF2dL10KG9cqe%eXJ-#+SHzcjD2R429T7b%tjxEl)b8+(iQD91uY3m@+}P zUs4b0xx16onn%vg?w7Z;?+!{$*;vNraLdq8llY}J2M->+q@h8PuTDY2C0~}!7Jg}? zR=*o3kobKG3ESQ`?5cp}AJ551O5#Spk5$s_^tAisi`5t)whj}C*jOrEU0uiKJzJKI zp8NZ|*ShZf`#de;TMjWrxXMaNeRK+WyqlD$n(cHx0At8e;W8yY6j(XCtnjn1?_qH8 z^jy;7$|jYBjTy$D9uyWPWwqB{-WVdgC8%lxcT&oVUgE{_#!inpxz(P+qT0)@4z9^Qt)p|W&2CmrIYCK3U8SV7l=0}%OD$n9U%DgJM)|lA7xi_(%nBqj3v)B7 zh)Y+mx}y2)Jr1I@wDg;Vg!MenTfDrz%Q34oTp{*nSJzr7Jf*v&<>VyQ)fw=7KnvuCM~$YQgU+T?mJoGhS-&!$T06PYA>LVkqe{o%R=hd z8JLQ<6`|&*q@?`G`vf(?PrwX*vY&+NmM!{Hy3VgBIi8Cw!pISyurLZ7=vc+a$fEfb z3)(936Nfy{Fcls27IH%M_uh!FxT4~gc=}{MTvR!O&DI#%5OK`P-nDr>mSr=u=;$%w zUaeibmfx(O5;qA+sgJ+^iJs&4W>Xt~{Mg_u{^0%l_q#R1{)Aioa4LmPhK|lNXZ#Mw ziHK;)2r{WO3|pMSpURuZ2Zx4ItfvjHy7KD4D(3nly-6g*x>BUTY@?@aW;kqZZCNfd zaL}~R&4~VVSan!ktCHw{{=Djq8;4M3J@xbJ8kB$TSx_KiiwUGwkfipG1V%#Ul$hc$6<0-iYvt!kiO|A6!x7{m1q9G zwvhSd;=gWj{G=;xV{;1Udi?ukIRQm?)Csmr6XkRWxb*{4dd=KzOSD&APxJPHSAOw5 z|doqNdNfx^M~1jSz$!Dm=V$W?wvv?n5Cqp#S9JEh*Cl|-f2JGQWERfh`iY+C7N6)l3Xj7RQYCja#56A3-L z_XziC48P@sjmKL^AWfW6j2;jcriZVatYu`1IP~a~8ZK(Rc7386bSainoq<(1@vfsO z=l|a4Nfu7dZ3YGgor}T2`%vKNi01-w$Xi`mv#`H*Qqv?~Z-^cyn52 z=D|aUZtzJkGg<5N;Iq!8IX-fAE!O9aK$CO}&7M7veSD~B&jDBX{h}bYAmChT;sSQg zm|I#N#29v44S9J-xIShUmd?fY)qgM**c{_uU+k*#Wg!^?m;MPpNx%5#666^kf0kNxBXF^(XY)7R*Eq#7 z1CXV-W%llI`nA8GnzhyU@85sswl8zF*)JL+C=jy@yW@xB*efsb%y%9d-}}; z*R5N3fRT~RFntf8Ey+7~xMBIO@Yz2B;hep8tFl{fdGTZvwT>3k{e*NkQp+v{k!UbwX(2xudgv*8Lp$V@$wrfS_iYO@Y zfP<>|dJ2!{4s+o9rC{t2WV4n|B3p~=uX&SL; zDX`m%1f+j#tQ@e}*M!jjCwWn&0JCBo$o96n5*RIu>2;ihesT5juh#1qVx+fyyFIGi`- z?4}>9MYxHN&Ilj~qoa#fNOZPt-`?3@6RB@tP;|B6m;W`-FX;;li#6ZBdv?D*L(L??yVGm{C| zlbeUhw`@7P`ksXS3{SJUitEBX3O=5C;5JU}_GIB#eH*J>h}daI(atVyHM+)@f#E!_ zATKXEKDq;^9dmOxQ&Dw{cTJW5KHX{LdGozNR+CjHW9pQ+0(9C7>>3a#UN2gXwhTOF zW?_ohSp16tr;7Hmm-z)FXP7v08@PD#O`F7wj5sj7d2V&+M}nz7w~y5LkL!2B(d&v` z8i)bZfUm*&pJq9VqRy5|>9x~#0&E8B_I4E>`8$mCl~)zRVh`0@Lk^xdYR4GC}J zof?foYj6Ec@~M1oagN-n!^xt_LItx=)P5fVF3Ay9CLV+K^1?JSd}L*3;ln}B@CXV% z28{5%zCJQHcQ20VSAAY9fW(x$cCCw59PDhX+%HCHk`S?jrS>p7viHM8ayS#R- z@^1R|q{D{=J%Wchf(xxSsZdbBJdlFNOKW)LELfYFS$w#e%BXck=!;sGTYRPY_Hs@$ z!4R5zCk0%#Z6G<;-IS0v*sb8_*U{VC+u6@@&+@94z7^e`^gUIXw;B2QDOqY8?AqIE z9z1`p^rDIep;nB=Ps_@Rjy*E6>}~UwYHb}^zQkv0&UJ>)bvKI#zwg?*b!$X)1`WOl z-MMp=yXf4W{yacCYi1~HEu2{YLyW~=JW7G_s226qg1Tx_!b7E@2M>zGtT?Z4K%g%Q zXFMZ$_CtB|-fbsuM@-HbBe_a@rrU30Ltj=|aceiGQWAIS=~GQ5^1sy&5xb-9+9v<=pCH7H@t>o_mr|Oh5 zEw%;TX%*LW6cxPVePzMI%!28_`~4#$^e9TDG(uc{zB<6mcVaE6MfVtU{NwV#I)kjn z2L9EMpzzP14dd1Po_L-ZAUTeXGLRgDg7zwvmNRm(Y``tup;UVC2qRflRi@^#mtSid z-KU3sFqs-}*aeS%bn>hIt6Bq}7s-#^5wm~4A`W>H@tcXZ;`3*x3oH*aGf$?ea0&?t zjbpMTnJdM97jJJ0AbOXsTv?y5?u}%j=yv8>y0`_k&&)gA8?I^^RjIjmR}|*x^BR&I zU0u(`tDB!oV7SHZFN3Ld zFYmd)@GWCwiL*DT4HN4>+)Ni^@;M!;q+_B_o~fcjl_Run?Zb!tqXS|C{&D(u?zmRn zTsz*Q$4>54y!=kSahMO8nqbKGhY}A-Wo$tYQf$nYj{D0*B`YW3H@})5H{q{t)j9Vk z)}J{;u~D|-!kIJOOyCpf7#Lho+rivS5Ywfb!3L2?w^!4BkBW-!2fjBxG4V1}SxpSz zMN^Z~BA4Fjz^mNcjTJ(|rOOW=>ePj;UHeczu9eYWS}R?JjI^Qn<1=>=v8QgvhjzHX zIK0lWqa$`_=@w`8_AR+p+v}{D=saadLjVGuo%Pw~diE>ZQzKa*Dl3#G*RKNpJt`iHk*h{zfVd*)_bqz*3n}!jy z=N1=RC+RHxPi@$+v2^xJnbA^#x?AnNf6Sp~So3#+7z$E|5!c1Z$!P^F9%(9)dU`NkTj#}e&v5JoVga_6{ zot7xxrl_cd(*I*?w`_Gn&6}e_M(=yRu}jIFFFhtFr^;ElAy#p-bIbhQM4n^Xk6drY z8=8LSeY}O1yZCmdL@$fP!eH8_k;{I5CK+==OMF~;-M9OH_4JR7Jd}yr;g^dVUnXiz z09U~2SVi&a`prsrxVcrc?@`w{T8fIMc@ckAGfL# zjA7Si0odi}%hyV5)Z??05)%*}I(~olTDqKr|1c#jx4-5Mvt3yD)0fBn&#Bn*Kyl$l zO}V1+^UaKhMtz(`9q4_wJiD^L{NqPAr1=rMlToiUkKnnyNlGdotcz2l|Mr^5V2D6| z`}S=s^l}VP70n_u_d|-yF}ZU5mm9?LSF@ z9dPnDadE}t8e296Uy7@1$TnqdX=yn?{vp`D-l7n!I|?&@qO_SD_P?qwh8-qE-sG^D z=^!C5Pq}8znx7*hUcfX$=gp2W&|zc{2C+=SSS{=;?v08=Imv^YB+>3#m!t zs(8-%P(4W!E4KHYJ7MP`ys~kEcqSLh5x#%F^uxhnl9Q90nA4d)`Rz{wX<JT3;=!uw+f*y579R#(GB9WSs^XFANTu22;$QnWt^OQNu!Y!eF>R9p9QxT5gg zlJ|aFTG7=Whto-8536oIG%(x67PW(vJd`qGwbIDio6pTV3%v?_deM_mR4z7VIb`9bLbPgKU{TZzog;#RU39PtSR~@ctyr z?Y2C3aF=ezsN%Ckd3kqXJTsf|%AH<1CQEdK9%*^Y+Y|<_w@QL)yRR$0o z1zbN4?rH43Luml3n>pQwCU^qpCh$L#{q|Eel7xprN+7SS>^3-P=vr-vv>IBRb)@%( z=@_fzF1buf)iiYF{LjZmvqWvn8af-#vp9H3o}@Y9#gaTa@_oD7OsE5n(kv8ipf`gU1JEf z?YtCYJd-!IkD)cCRI6ETwceYI&5`1km3nwibv^+T5w z71!mfbF=J-`Bq==`r-v0$?*Vr>W*p%4N?G~*|1oW5%4HP9ukl@NUWKa-R!&uMb+S= z5%#Nz>qz@fS!_imC+io5(wJ~@KRY@eWMuFW$vmD+$k$Quv*Xj#rJ#;oaHG2*`mn+n zZ`LWDoDsO?r=g)w(7*5bNeX*2-r^$^8ud9S;yBImSPxxM_1QFz0ms`_IPao1wyfJ zHlKe{m8{&x97#D23IDxraso)gzB7T~@|&q0lhvY7t?Ah77!|Y~W7EiZ1tlH6eopUwD2-p76 z(K6Jkk40A|yk|Y3XFYiA*bd04ZGWzuN(IbmWo1PoVPhiZacLcUxv!~V#DbTPj|rfX z(}DNx?fX$3AwkbivLNyB^B-mz>Aoj`Odl!P#C%^^B=D2{4ogP?N|W*LwiP)4=g@1w_TF4F1i*Y^_9orn;Q>Ph8z1IhDHS8h&@ zJP<~hK(MF-13oq7#bF?yrC}>mtV^Sog~gKl?H(m2CK`CzSX&E%B6tiXut9oQY+&FX zm@fgY;31KI4h(DpGjenklB7;d>BA^K3V9_Z76v3>U{N*`$80Qba{I!ZQ)+|31tEb$gF6yE+Nm?dcRY~-b` z*p`rn)#mnGjtwe#K%=ovw6yy>I{jZM#ZCOG;MM`u^0B(wwbAGtzBF!}Pe_PYON%PW z5vg(L{odU;(;JZtXMuP3Ow7mDsyB*k-KY)0&*tgb#MM2)pCeP_`d49!q;LX6@W(%6g2xe3^OvJ0BL+fskB3mhFe#HKX7Sn1il)t24??vDW z#6OT^xW5keM3_;pi5I{+TNqJ8jyZdpN=(oiXG79ruJEL8LJ;q_Ns$Q2N6*MJ3+KtenPdw*Qifh4DAOjoa-#^OKhK zhV4c~yN23VT<<@5N+Srp415Dv(l1_eUb#k%$AD)c!L<3-m6xAiTGIEcr{X9-KYz^K zCrK9r{r#Q6J6y;aP5Pw!;g0>}-U$cj?Pl!~0EQk7w}ygzP)eJ$UayRz$xs^F7nIS#8U1|gO$ zA3u_RH%zZxhA2?#{Q0#cI3Pv=f!$E+bgn#oO8LmeH0D@tFic9SXdwl zdBvRV-?X7-_-7#Dq$t9n5Rvvfp9pk)e7b(-r(WTlC38vn1!W5JcPA{}%aE67VLw8$ zG6%tZvWD3*jqatV58>gQ$a?#hjgS@7#6i5i+Af1KIsNhR+u%D>D217va%jLa%2-Wm=KN>O|enZU8a1aSWRHCCiU&x z%}xg{yb))h{Z}|Zj|}F( z@TtBfgbRaaWslpPOl{m6RMpGFXPDj;-lP;$>z<_d>(3o}n>2Re^R09h!t#LGy@`)6 z!MTjC*ZOPyfhd}Aue$H~P_aMt^>vebV&BWNjiW~Kf8^)$TruT`0P|t!NPfYThrfHb z(GeEn{9jfeqxaX0`y3HC@tOfcEu6gh$!@KMw?^gAN8LCXN~e>BP2d)zRd3xNJz42uF+v zUlJQQG&uN5{_rbBHdZh5jQNEHCJv6R5Do2o8@m6Drsjd$Em=nm?lmA`a5}(mD+>}@ zc4yD=`?6)N%h_!&ISMY@*$IP~DVYW4i`4~)yXBC+QSRHvA^YIsU!$D${vG3L@{$`z ztomBl_78>G8dlRco@kiw)CZtjYp$KNZ>h{Sw8tcwVFs3eCz3LN*PffLb%U zeZT-3LjWrh;5~m6GcZpO%p?+?8=U?IP&Hq^$eNj(Gcq&lHjRwx{$bFcT#}UBh)0Lj z9^vwbH6c^k{4=&p_&*G~QYhQ5%>>wuSP8-2&IQhYIdqUOWOPAF+41p}2FFywa*Q9-Nv ze04{R=;BG&k+J=A+MQ!Oj*u}?0f*IC$d3jmQwpt=ohu0ZW(bD$GSH1Y3 z80-T+{41eHaitJ)@SFT3K~3TF>=|?7rMGw+c%x!h3W+F%qVn>MhzEXUe#o=1wDPdO z%y3(NvsM^NH>_x_jnyB^>*e?R^k{X?dzG@?)x|{{((c+lLEu4Eb1iVZUO{x#QeZKn z!hGmZcx)`x7n=aQe5|Z2fyQzI-LGQd@|wtf2`sB6_NVD#W6vGKall5(cMEP2_UvRF>tqxaKb>olezi%=|Dm~MBpVIfDx=;zaEs+ z3$wf(J9ZEhplYE#y|>V>k7;FebcAxeFXXm?9}W zo^T6CL}>c^Wq!-i-`@xPD`-<4D1*tjZ$D>Y!3QQ_yt#da24*ucF#$WhXI(HL#Cq8- z5d}0B9NW_P%N#<%?gPR&Os~jvXh|t|8(l4qEXlO@08GN_ z-fm$Ev;kIzKhTK4`72|))A>%YV*UO7BodN2H}`O)u;77SAyWa31G?xBWIy~`+9?*< zU0Qm2I8bjy7~=mTCnx<>p6IQF*c2XXy=MI2t-Nn?TmfxP0yb{?}YrI7>@yrym@t@ zBEACJi8`Ro>YNI;Y)UiO2_2!lhKA^f71-3$nU$55xgVJYCq{HY`~7|Cul%N|c*}zi zS(HPc7R-Nl4H%w=0=wK=3*3l}+xMNuEU#KVoY01>y9c>cvm8MXo7zeIwIfvAf2mXf zbap*}s(8ZA%q)$6g+pKsE;mfqUR_y6-n}!^D@p5!zf8@yZ%&{U z;YY$#QmF6+i6jWRELJZ}hNi_$CXu|(9h9XX>FJsN8S?%N|*wlF`h zh1^Gbhj|NDWsDERgbC(>I-Gno2?2;lpRW`%<^`OE@I6ZurU<>+dNz%DBDl*^gNAdOi-%C@G0X+uB`Od3XuSh`4i0dcDL!$^Z{x%b4`)iZ^ zY;Jo8WEPO~!2!?3I4rO-v$7KUkh#UeRU5E-Qp`K7?%(f7uU9XI9Jw$1>X|d=AC}aS z!EiXuQKo4uHr2B*TaXc@H%>+8Vp&<)CS$-_ccG1+SVpe7Z%1wyh)hhVc7yi*?)6KT zY96}C{S6QlFTo1v-nvfC6G{!FAOCe zc6O>N1?njP=j6PB&li1Z%$*cSKj#;}Q0Cr%Lr!ydV(B87LUtWJ|_{*AG zjpffKqYVIo8-HHM#K`3g?;;U&G7J;~b<@^uI7{vXj1(b2LrZ(IXXO;THCAD;4r31} ztp+O#%VYQJdZ@c$+NHBF>Dq1n{~TkSqHofARP2WjpH@<$Sz2C>v4d*l9d_4pQT4AlXY3rNTJN8rr$PEP+6%}<3o$_Y=dp&{f|8E?7tHBL!DyoYY|G-qw zkD<5_ql`8>=w~-yKGFKAm0;mGXeR8`)aX&Vj@@rP)qH`kE|ndg4gjC)3dee+V~_!D z59?BdtQ55#Voe|ayX=#-1%CkYnob3j)YwE+j>enS*Q+y7!68agxA%b}ijUulT^b)? z4J9K%C-0P9zmvaS)5s-d%(m|oVn8vWDk<2Bleo3e{U8%lpv1lGrBLHh+5Waq`n-a9 z+((YQyt~yhtoX{5HA1%#vK&?V&+%Ae2b?=!&v}L)tbc;ikbFwMa#h-2P%!QPfgv|h z0ZbCHzEby?@VEfW_40aZ4w?$z^TTU&>Kc#7en@XLI?t4{r84iIU%}7I%c}`Kb*?VS z-udH4shAN0kVa`|+qJj-G(hAVjh)@Ajacb^xFX@(w6*C6D^ES7MtG|8vfg^c;oaM1wm)!+xWUO5d1 zQRJ>Z!OzGwjTQz!q?UgC^hLa;(e&e&&t`WYO-9cD;QW5F1!VH6cp7$br-zg@G=qc5 ze~!2QCL7Sn6KIK%jZ0AFD#ymDWbAs7o*iq+u4R-4FGS(;N71BdnRK8!W9H~3=UpUj6?fE!ksPEnkTQg|F@eB;K6 zcR!LI!u_N_mYJHFVdVL`pUr=>*nq#j^>#A+`>cG9|Gt#g80^)HMD~X%t(1;Xbd+j&wKM`gaKfI2wIl*B zT9`60YRhXhSTybX;{HjL1lc!xe&x%5v;Z%@o?}`3l|*9kGoXnK5r(ZWP8emv&D&;2 zJUSP-0ZEYltNK~Ni=%5CRfK;Kq=7!?xoo_d2I_Ao(CnYx;ckZeD!)oqq@}5)rIcHKYSRGRVk#51Re4I{c^gX;o9GJNiW+oOO3sBO%i|4XsPCY6WVo-_a0N zN3fu|;kOK=+}jr+52OSH93CPbPN`83jX_++{GP^8n1qEJ5LT1~JB&g~g(cpJRQt}|AEyx8CST~>zs6+>J3gDe=>FP{CBopbS+C{(p zGKbGn!X3L?Ulxw;{J-NbgRk}(@)~ZX*lUtKwfS4ELP}-r&Z`wkVr>3gAK$jQ1f8sD zYutoGocvWDmP$tQ_1kfG<-;N18Dim2i|flbf)WUjk$W#fTQ3i<7v6#m@z7TAXe~l1;FqBhlydV!#&GDWz##%OIfQ~r!1!Q5#RMWwUAPl1sq*Gze2L7z=ip4L_ix5>)&Poq zzV7IaNtk4#RWPgDSu`Ua76-)MN{+C^mLJ7x_C@2 zBY+y*TTrK=eZM9qirSZ3+lh1-7%hOIkf+0c;}j99oFD$qkFG@ncDT6kg$3Xf9;js2v1?8OC^!@C{}6#lS9+! zkE$$-AK<#`|03IJm`d(X{ZnuJ`J0C9_W}B^AO3ItSb6HV2ID_JKa)oOyL$Rx{E5f6 zAkkIJ;6nLhLw&}^L+=Y#hg?(b)$ehl;R7wJKY_!)NlYvln_OC`FAObx6CDHIx)Ae+ z`?_1u8m6zW|H9AoRBdCUJ3&*N?-e{>WZSY4ups!kU_aBb_QL(=BJaw6`HMKEef_9P zPr_jowpCwkTYYGJ-89*5=v;v6Y6C5+<}t5Mpz>VVB2}trS^70O39;1%c-M3lFWj=% z(b3{e*|THRNnq`fP9KEY76DU98`ujC0D4A0fmL>M=>LiG9ri@+bfEIHx!&MI3X%8d znc+{kyGIYyQg7vMs9N9O&MZc|1{x+Fo{M~`RM=Mm)IpOk2naZS<71%TKqVu?7jmsl7PL>LF=h*H+?0>80nS?iYO%75DXYKB$J`MG-U`no><@wMuaG&g=QLb zjteFtR|KR4&FBNj@TH{4Gw!r+C(SL7Id}u|CwC$N>w5qGbo{l{4KM)`DYC{5ZQ6pi zvS{oiItXsw>>W0L^5;zHyQr>uZ6)rXV&I*0Gz7hRH6C2yn>!G9Su?#!^`3E#!~aO_ z-HQH)+Ix}S{rxYocf>Ck;umy75K2dY=z2Xl$J0@!?LK5dMs#2S;~_i^Br(8ie~ylB z*R);^<;Lez`b|SF1A?I-se9BG;O$4Wy{RM==;adB%}c$|2eL5|U`k~}1f8*2S1Xh$ z4cUXF}ARYmTXo%L@GotBr! zlkrPgZghC>R^z5oFfXb_x4Xz@QS^-YMSA8PWX&13&y03qB#52|x3<}L&(`Xm4~FTT z%WECpNBOdUeN)%)EY*Hxqw~!(l65mNK28hVKewEpf0HQKYsag z4#E?7Zcm&yLKBjgw+|{2jEyQnZ3SKr4JbsX2vpI~e@8|{dk4`Np#lcy)m3y=9XT7Yb_z0?cRLT&L>A*ige|ngcvlO>tVQ~?S zSz3HSDR;ZhrLdXj%#xX`&Q5^Pkp;v<=plLctlO|b&~rj}_AZnb=-<{%ZA7Ku2~iQ! zqHwU=7@tlXmI~q%jhck?0PA-182O-vm)D}&|AHIY< zNfjh8P9!^4=)VYg@;B+IvHb(3kPQqDI-%=y*JkHhKBD`~Nhc)*l%T4DOOnv)u1cCM zvb4LpqW(YH-aH)3x9u9f(xfD5Bt;1cO_XFzN|B)`G8dA0&O9Ye#xj<%LS{0PsYohi zNQR6_Lgo+|zjdhY_xZl(d;fah=i1ut?ycX&bzbLr9Q(2FYhP>aTO#Qvw?r1XEzG^9 zdt4iDkw4D0cH1n2*le7uN4a4+98dY77*W0#$l!O2$!BV~V_}Q#6}>wZ%(HX5r*1Dy zn=D)A&92!lHbQSLxYuR$dkIlRMs?V2C>CvhqaHG}uI_E!N&Vt__o%sVQ9JKb+l_5m z5EZ*2PA3BzHtIN>jbIMG{9i3r0uQ@R*F}xM_nj5NTth?Sj-THtD4HlQAS)!qc2vYD znQue2;VG5Scd^7p1R9_FVPXCE#)}kzZlGBS>aPb;8tL-htw9x`dnelx|v zi3?jIv)}?hj|krTs|m7rG2fEimqpAMntrUiOLNn2U9Hz+P}a~)7t6vOL;JQZ`oHD2Uum=QAuVgBpb_M zS+Ff9J16G}fF*fMCJ3CQ#RkW#*6HErS!2ZdLviajY&fT(;e()}4v8#MkEyY!1#dif z(p&@Y1g8+rtJJj<#p-(sZsj!vo#p&?vBZ>ipE}RYLEj1aI_2FG(Tp=x^9#>xd&Dg? zY}-dlCVxrS9jb9_+OmXd3DtEEeh;8SCMx%NKBsf|M!cPAR*8BaCUrg_H1Sj61$qC( zT=j|bys636@4wx(t!TLDZ(E_tvr3Bk9_6lc(5qtV(1@X@1;fq@JJjo*h`TjS%*~$c zuo~*?IO?3vJ~wSE=hSMLm2FE!8EQ3f&wc5Z^+}gyW9!u?d zqbQGvX=mSOG4etu&zjx!xJ)CBnfAg4zvI#8(9h{iFDmG9YCq(Z=O zTk=4Rc#@e}*X4{7LE4=Qj}(=V6|)*Ta|+j(wrsf#E3@2R$aAO|Qch?>8;&2KI0cO> zB%EJ&k{*OL)5(N-L!iA}YI}i#F@e(~9eWiu7)7SZJtrxN28A~s3Po~*f50H+-h&5s z5SzP7Z_)@{`>~OL>Jf3ENdx{|LNYw*Cnf{ob?Lg%nrXIY__d(dLUwanRyN{*9tsJJ zrl)vEiiN@F;1w1=4&V`~F2?G{M&*Road033;6P>)I4Q#)1q%i6{ki8}@3!rkwwIPe zhqJ(zz}Igwg!`N%jvwFQ$$H}42~d;N{P{RIB=7DK5V&YxtoL&ph3?n3xVX5b6woOH zv$t9eTdAWof4%ALr!Q92bUcLL7}LZ^>P5wF_o)IrY<6%jt&oTmS}EU#$uk zcLn}Awybr*k;+>sSPOI>B%SYO{^t0b*K-pi^;yd`U#(BPaL32zrk`Im zl_dX>Dy>5IuU{3|-~O=7KdYd6eLSo7ThQBz`0-6A>Gw7EHdH!0&y7q@S}|2NHab_< z-TRWZzCbC7c3&J#d;5y+(9Z)ay<)E(m%+OZ_%Y42<78plw#}oYL@Zoxx1^+``Oj}s zu_j5AO1a9(oxIhqU>lS3Myz6ia`e)MQZ?uo&v{RY(T zkIq}e^Ea2P%u%Pnwx9#yYSjgtV(koDw^}AWFS1pa*YVr3VZ#li-hjEj;9v@w>G<^jwS6h{w zjbH>Bgbf}aDp4>_lOy+E`q^_Sr#ii?(~YOnwBRh#$NV|b_Ti%qeb(mr>Ko==1%!nM z8ss;gME>MAO5^SZi-BFBM}ZGQR&4R_4`#=sYwZRA=LlRDMYeGK+&Oxv*q>f2K0=}o zi9b4vB=Nf>oiZ@!bDn{B79l7QP!^hr63YD#KJ(Yb4;{|_GI(S@1{0UJZ0sy8=a#nI zGNSI>-EVZ0;asEhU(k+w~CxUzt=@J{8K4)z+dNSwo+ zgM+lV8uZA)b9w^Z2zphtlqUJ=o!n|oT+FZ-&ut8n{*(|I_Zjtqq`p2Ubh>V} zTt6VuL*FfTa2z5uRPk=8BUF)?g7m4T;rg4Gu~LN|Wqb8Mx3Q-*zX zi&(GOo6`~}PaX!RCoXTpox8rrTo))@Tt*a1?(m@}&!3xqoOj=`!>ENLTzF!YuF9=? z!?KE~NJ*Q9@2i}T=*4t;)oGRXS(kQOcD~Q|$_(GRcO!O5d%G`TSD?!*?Qbv;-ju$& z9K9RA3!&Hw1S~PkHW!QlspWjL495nD?G+AdDk~o`h>at?`|4MQ7#vI*u$|jJBTB;l zq0QC><8G0!-ei-y$JSSTw-nmZ*5}vGd|o?=MRo5ABhCiXIaJH2sDcK4P^jV>hAiHr z0EyMMgu4O?yLT#?T|FiMW~tpXPMWHyJmi(c1IMC(e+TWQEPn@{RdIEF^fy@ZO!?%2HzcO0J(VU?U0~AU~}T!tD2g78C8>fZ9QMaqq@KnZ7sCl@IwD* zQ{CpuE=+-4L7rdaeb7|xWnv_eHx(CaN-aZy={URnn!q;bVw9WF0y~IEaBz=Fw(2=> zby4UTqkDb4KsvUxy~q*iq)^}Q;=c=n=H?>c=Jnn*ch~;?X0In{*RF-}sd@7zW3NH% z?5SEhE$f1(wek1wM|!pv8Y#9XfiM`r;>xlx;o9y2o!EKZWj4BI5kDi%T>Du@dXzg} z)47cE!RlqeUKfT2AHr+Qsw21Sn;Miaj0HrMT^cFf;5RI;qciq?_vx`Aq`Rcfq6(>8 z4oU_VWbT-1u1U)+RKtaHzgiI32$L2SWm`_ha>;616b0o3=LKGagAE!*7Yn4qgi!|C zvm1gep7m}vsR|tHUomwH@S6af-znk}hV}PA<{&IDXqkDtg#-NkfuWE5YB7^Y zs@HCNehn2~z^@|@WeC+7nn^x^2st2s#B}8Nl^9OXMM1;p*OPEZgO`O=(mHN#?op^O zX)}1B`=>!A_l_}TvP$USL3%>n#5sH~JUrR>0*@zaq}|t5(3^evTJ8l24+4N&%M23V zPBebOw&8-9yR%_4GqW!q?Vh(9Vq#+W=Pqh6bGRpH{Uy8$hx3Ne#s4&o=K0)pXF~Wu_ zy~y|0Q8!jP`(|clj&_G=ef@Bx!{gZ`bI%=e@}BruTI8yBw%nEm6KFA0z*fStA z?mnK`-)76C3iPoR^$XZYUU_+WdjB=i}I^*fzqAOk&Bs!okX_ zs%tyk9x+iQ7dLi2cPBfm!$q*|!0|vuU6uF|-G{}O{^RubkV5OzqudbpXG2j88uHKU z{;Sbcq6D%H>#9kwr@vnSd!Q~XT`2V7*Rh~|`=7qRT5GX+wD%cY%pL*Wc*6M=H4D*C zU%lFsT;b*ES&D*?5Lm!61>lH6(QfT74p!BNHiOM1zU;MO_`5St)V*HhWKbm~CoiL% z_V<9|ndBa@m}=;PjRNw8r3`J_{r$>8?1jxji75B16HEMZo%PA`aHqubw=S#k+{gX1!pO zQg8{r0?HkeSk=_%zuuo-BCvG;2W6G}OV&vFj{xXn0X2bH6NVD2oL%SowYu==Az*g7 zj)Y_>1$;Ot9a6?&o=P_l)jAbU6|Rv|n*9WaK@LvC2RngHi*^G4>1-(wybN{yy3}!KqK4u*P!P z7Q%o48^u|&LITL`W1Qp=W>_(@f;D|(F&h4R0^e$Z#nr+q0gfp$`dF05_4VU_3PM*; z(BDqWd~?*?voL$4q7W)L3d>tihagP+?hXMAlY7Z(2dk>oA54GwPO0_ir-#e+Xy5+% z;>0foWKL-4f%AL?aFvlxd-4$}aKt5Zhty>qJ8-UKk9HMtt-u;gL&CZfiFl1`o z34k>^3b;~j;4Cw8P6>H=I<(-Q)^}*9BaFgLsrvu_BK*LhogKI;Rz^tfU>4dBWZIAE z&HlWzxa?H!L-5hovc7&k>#;v>p@`SeQ?xHx< zX7Kkr2@7i{54jUrUl*H)`qvqKBypTlQL!1_>%bBxcy3in3G!u%xZE!{yY37`hPV<$1*oA!>#=dR?%TYa}3b%(%0 zPy9RVHa`B{63mVR2MNYUYCTwsIXccP_4^H@PO{`76Fe;=6Mlm;1QIsUzzgS(ty_kG z855g&qvKGnz3R|D&F9`4LK){So?jEzwl*l-#fDVPg1W^x%uYK9fJ<5 z-|sHynsxA}6i?)umJJNk!PnHlXY}`$tIr&-^5a~h9J5b7A?_g;SANUe8j7Vr<@2mo zUAGJ>z<$7syN)}p(4F`gs5*nA1ntZ%oeE!jV;qDfYW@$PpIWm>G^XK!Jc$q~;|n*J zuM}d35)?oE1#H4!H*V#`;0)WidCM-N{>|(=4=a`*F=gH5Jh$ys&IoTq+)Efrb((*X zHf7E+X`_l5N=h955vn>h^Ly{ZS3?Xe(dgGj0D%IjKP!jpZeP&*Zp)YK`86Lu@<)Fg zl6h608fnu}prL_MQbI?OZ~cY_&c=$!q@SMB0Kv+thdZaf5(f5SRJ8mPNm*&Ychu48 zN*+Qfs+Vc+yGwN|pnm$zTLpKfqb9KE(pz;RXthJc+Jt_Evl@yte|j-5Uy8@D?S>m1 zHEuYlX{%Nl0g^q3|DG+q9J0Zf)H5#!g=l+w2Rt~bktl0gzO8jMIJ7mu*-Ysl7XXus z9;Fm*KSB{AOT!0>Tax|qH>b7y@YAhDEz3i`9hbIT?#y-k)pCt*wV9rBbX=K{W3uGHb0r9DrAvYv8l);H6e<4$#@VGUEuhid{qn)Bs!H*Ptlzf1OXwED$@SBR+Lg77Be3uV+zfL%pl)g)d=4eRTB>+4l{5(Z$*DmT)tF|=PHnd;IA`1Lw zYG%d~8XAVS4K~E7B!yjNwXGjlQ|@@LsrXVu?5#XKPi+X+iHf@1Dtt1C8aaNpgTZWi z16U$yxpa-IX?C9BMV$o~kR{%Zk{6<@dojttNjCbf5(`VTn!DhE9h4WnJ%e33=Tl*Z z4xT+%THMy96xcnW(QrR3#1EpNLx-RNySAP3Xi9GtQcl*=(l7l+mQ4ThxjVs6V@K02 zk(f4laZ}c6=%(&y6g3YzsYK_}*Vf*LGF&lL#le9>cQY)5ja!Vy9Zv%Y8)`NBZH16A zFf;ZUJiF+CaBzEkB4JE0ZsnES7UTr(1A{6Qiugvnm-l01bo|r`o7uT(h4u8zzO98$ z*ln#t-Pulj)M;L*efExCioc+91ys(ZW#$b8MF0cg8V1V%x<*IfeB-(b^L+l8j9?P zy-nFV1qzJL^V+^2`E9;~X!hr|mt}D~*80cP#;3bDbFEI*Q0C(2<89C}(WWwO<+;AY z_vzH@9F1SEs;jry|6YDtBgB2eAmL4A$qFCIr4e#ok6s+Zxu9*=v^u2n?o?jh^3cz7 zNLO-~>5YC^AAe$#uF7ki9{PudTs_7PKU`O6xXG~pM>>Z~-}|KuBD*g}H|$^)ZXI-h z+rdwo)UwJmbb}oMlTSF0r`4fw0sRbrJUo(1>kFywaM35ZQ>6=X=pB4|&;*g+G)mms znr&-XsPHe?T$bT%%hSBL4D%V-E>+a^SyR)|Nocw(p)5f}eK%Y-_xf4OsU3IRTe2^R zi}wx`3a#Y(NAh@5z9VM02uDW=Q*Bv~WVeB-{OPRsZ!*@XFt@G9PEB3Qbi`DB;GW(< zbG8vDC+AsB*B+q`BuHV;G{%}Hx?_rph?mUdaGuP2T~WS>nV=;5(|7z%!RynwEG+nU zc$vblXH|~H=01OuO#SqYn>Wh|g#?6#uEtFiU&pb{Bzq%L1U=Qci;FH)9?7cN(!W0g z`jz@3XA`0H6mzfk*-(=ey7hz5qg;F{iwf`%HWh@4$b+*Fw{pIs#8jpV%yR0JbU{2Wd zXi;ntTJtCKeR48RbAV|4dpi}*tGfrVIFZC7{scXS#tZza7IS;~CA;Zy?Rq6GEk6JS zz==_iyaZze!xA7qapP=XnDGP|{f@Wy2|YayU6sgy2Oxy3+q!jicJmlZ0?y8}VCun8 z48AaBmkwNlFBlPxg%`S_ZApA5kVS!u4N?nfeeMgP0q_Q;Noc^N{W{d;<$y&;z6Hor zEDUMX(0dc1~?t_x;e<*2fe7>GS42oBm{s3ySRfFfWe6s)_q%LiW}E|*BPoA4W) zJ4&!$3HhYA*NsgIViV%krmf8iN-QC<473$&fu#K0P`>jFKXK=U&XYnR*e`0`jVo@5 z_bH-bB4j&s34K!2;UoNd@OdL`(S`;Tfqr814ES#+q@~|E>$t81#Xiqwa4U{cNN7pV z5_+yxb7)c!O$X?_$e_?lAb^bf4hjWt8v&7jCp-IcphRO^`Tnf9`@8>yX!rbZHMb+} zg1*D9JuLS_Lzk)epHoyD4sOc6=m2%>K>y=>m=;srF^05}!*DwGP_7%I(0<|q6 zAjd^ItOmq`a|NwMie$h=P(;tL=J&%X3Bn{CysDWAGPWX+G!rkugPmga4*Q6KJABP* zbi&xArXV679j}uw0}O;`E8sdWTnvXa89@wfc$?LjiCj?2oXwa8I>zZphm=GUar%^!xE&iw1ERjANp zPltzqR}fwM1pvlvTmx1R@f#t+w%FL^U=ygWul~EWMGF7b;g!7YPW=KdQ$=RGc8Ofb zk9@xGZA!;)FXeU2n>|z1_}hhE1)7YHAkin(L$EeRs)U9gq2$Cp1Uye#BanpvTp@-F zU_8RiIT{~^Za(LpJujgVCxS|lHBjmih7WKCh$Kma8OXAN!DIAX#9Js<)V z6_t1BMDu^^?(T%nlj26c4SL+wB9q%FMEC%<5^=MJ6>Ox#k3D`yI1kRMs1O|oz8}me zaY9{yD=a|s>%g!yLpIpm0gppPsF>(l zK@`#aVJ4+EC6MU=8Ioq#1Dg;zG$H=LHLRiG77TlH9feaKvPpr&S`NzyIy_d=8w0ro zJi%)Pwg}b2AP{2NKr=G60`_XfQ(tKaxesYmK&mpl(Fyn0u5&qZu8_*Zms@dok){UB zAS59`ub6DmKZF(b0vrj%Jx?gda$~0LGI+WGl#`h5@k31>sL$oxmbUc;kyvz~+%pn< zu_0AM$RKD`262|~Jc5Guyln$SN~l%%etl@!Ky$qUv_`^Li?D4MM=UQaEN*TifFI}` z??RS>TtynU(Wm^dJcv{;Nhu}Nn%hVvn1Fr&g8W)8#>K>ZsgoDG3DGOTpBx+lt1t2N z2`Q@;{LR5WtXRxkvUW_bTydFyM!tDo%yps!^O_(6tM#YL3TxCB? z(jB?+^3nPF`i6$DAPZ6sJl_ua^nSF40S1V67LT?hqRIUvCPr7T3u95>xKZeA2Wc}_ zIfa5^dJ0|wt10lE-H)fV+k~maIfc`cXwr$JsBOFbN@AG;nHxu2?ds|ZxQ5(52VOD_dd-?} zB}-e_mqRsC4RhH@^LpFLX>V>0=%e*!GZ*I@aPMk4XUAf03n7rD3%BE*F1MpQMa)e|Ot)0jvcKd`C<>rmM?F%)rqdMASiE);lq6z`ngA zt%%d}9-F~&_+ixPAL42ecZILZhMX&rLk_;9U_O>>UkAVqKnX5U{%*grH1WayY+dfbUr!Pwc!|jLmcZ);t=-2@o@kd2-;T3Nx}p;KQ`ks+U7+D8y1U4-ID?+)VD*!$ z$6bXNBo3O~UkE{E-In_I+3)#vga5C=)J96#q zrPX2!!hk7A$U5!+Ehpt~R#vOM2)5kWjOWXst0H;w3ax*@l;5}UFKDw7jV&G-#GL3E zenn1KWEP|W8XCG7=9p7N$g77Aan|2JGc zBu5Ksb6f;8!ET(M5Y=922E|FO)XbYriX>|mn2iSd+K#<@O^!#w>I@2V31EWAAQ7)Y z0VcB*aO8bAb-&m&G5+&sDG0(OF~aBsRxn6$Dp7>fLR3?3?V!e53*fLbGBN_E;X2ru zl;~A&fartgqKyPYx&*3MTqKuNHHoAlA(%;;XX2-hS^x<~A%?|KB*pgsY2Uj}-F6*e zxKXmuTeS+MQ{B81xv`3Ia+l$bX4;6@0B@4s;PLGY3~nF<1$JRL(^rtIk+(Ak#KLo3 z5=Yg?UzkQ$kZsnILyI5CGw-DOHQbT*Dg!Nr+)T&e3N&=lLB$z^I0NapfZfQBW#Vq= zPoneyV!xQkp{{wtE>Y;Zh5VnDaY_m1-x79@&WO#i1O_SrSO)Xpacs78xJJS@bKit<$^hTla-Qg*7cnLgZQH!@58`M-Rrf3HX=St zyp^ij)l>vH_Uu^&l5w7OpTB~IA^eB3Rj-PTT3`(ZrMO>Fk(8D7$@uULLo=J*YT5Gs zHS*V?!br)&c$}};ukRWfx;BI$sep2y8ZJag=muKbaKym8hg0nbCI`r>J{GJe#ZNw@Xmw&i`ROK8-F@?toJp_jzr!mHrIS5PG8`HYgW?q9Cdt38_ zg9x%WWM0UFV_zwtHI@AEgWi}x95@$OWKoG>@J3Kdm|1HcG0md5;SxdRq(Shx_%9=WN?%dRRD}Bai;fmKu&q^ie9tZTSN6DUJHRE#Q_w$Jfer^nXJ%$Xx!& zn05wM!Dvxn_sJ&2A9^T*_Red#q7xbjzIpDLeMKcwH*}%VW!h~#nN-(Dnt>!~BP~xP zxUlUYrDO0^xMA3rgl5D#^Ul)cAmt;whl4Hyv{B0`g!+mso3v#y6Y&0@Ha(ZxQa_Wo zYrK^deoanx!5!{KWp^nudve^xwh4^dZM2B}1h~=N^>t$6L z7Z=j={=Pn^F9#=!J8)1FI_wrLr&^PK1C?NSJQDFb_ohxfePY1Gp^*m8HlZi@9BHRe zP_)h!->o|6&qme)m~O-{5)mxUcz>!=QaYR<5#{jkV>_A{vm#~q_fv?^0P;L&SWsWm z1W6N@$cZ=ta4lg|fP|tdRg>^pj7f01?$2Bx)x|jAeBML0<(DugY4`NwC(=5dHmt8?Lp`! z`V-SOhYoummcKeBebH5YQhpz1*ByLv`-RWDIx8g&i4!zPERdkyF1rrWsBvrFq3SE3 z(-8X}Bwj-;M-VzdTuN^F?GBaJ7Z^z0Koli<7t_Wx%_72MM&fKbrAS$m;w1tZ551J(52h0x_PLpl?`Tfbf0C z*B6Gh9%@(A5ei@mm|p`$af%%Xh8wEXmb-z04|)Rk!d`F%5fR=QevZ}6uqV|BUbXLD zm=HVq&3Di^4Ku^jtdi?)2FRIywVJs*S?1C>&x_evCqgd9*lAQD>fYve?@e$pe0jgApd6m>)|y|AX8LQilSe^&-nRC( z&c}JkgI>YWhq&i~04$8*pYIMTM!C)(MIqn?h(a5NCPXg)uKDQA(AD&{o`0Oss0hPw zWaQaT2bij?TXy+%7nyc}^Yd2|Kh_qGt`CBef1VL5D+Pu6D|j(tQivCHzR@TZP-{e1 z6qBitqdj5^jO6hUQ7UfUY=6l-ncxv5ia~qg5M&UX%XMMWwE|QKZ(w12KUv?oqqNbK zSPf@UNJ*+$QBhHC7>>D+$Ybn`tpp=*br0+F{^6cFP^*ys4rdy%H$TYOf;!|#yWLW9 zaGIMxvlQXuv-+4G07g9QX+T3{<>$9CUAmjqf%@U z^eD<)1k?ot58E>X?eeIA!Ai#M6b;$Ao?hh8A9R{RNFI@WHt(SXn?f7+Y2keJ!mK<6 zHjbr>e)BN6*aBoR=ojIYP}~qUgr|F>>DFV$)Z$Cz8xi;*p@8GxrQ>RNoM`<8HDcu5 z)To~;NcUEOXcCIM>j!H$A>=QYdMWb$D%m7rvgf zdl?NU#^*0~TMO3K)h$IOT76%(+S1RpFzC>hOWhwT0v=Y~U%X+Pm87Irx%{rw`U;nk zv8f&#@Y1l^yC;A|f|EpBh(WX%N}!Y~hGC#n5EM{TAPaoq=Q6$v``Tqs1c)b7X`FbX zG;rfT{aeMK4GIZpdJO4zsoDXq0|N`l0J$!V&;>QfV#YfT!?@rBiKC?FXhxsP(5PRo z|97h7!rykhbw?MK>9wT^iHW7vF2DF@`{Z4ZgZ*70O16lZ>x>#vzBV@6s&-*a8u7^R$%5|dBiHWLL?%y)5hW*zuHr)Ut)E4C^tvTq83k&yK7S#Uq3)R$PRA80f) zIUJQ$0!}@s7QD9Nf9va7i9Xwsne7YE4X1^a<)D*JJkKFEsAP7YX zD88CbqbFmln;sTq@JPhf5lGAK`@^f7b@zeNK>LTWpbNJrqHu!UF@=nSD912K!X*3h zwX^|8qU3Bjz3L@7&jko1h1A}>J31{S*sePl~ zA~~0$R?|NYu&3;^RS!&;mZ}8r@Z^e#eR?ddF*-eARffQ|hMIp|05;6~SnWJF7at#A znv)W?MaY`up0L}$m7;XVojOSRRq%(f%|gj44-NOp0%UeBAagACy}gM>5tI_}+kV`l zO^nE(su-Uz@DlbqN0{VugNK;gGMQXlJ48W`Ul!#uzZ{n*arV-qiW!U+6(b?;$?f|8 zEw5uCP)lhy>yw9nL{`@cRu$GU%R$g_-~#v)F6zAXMQ=k4KoWP9nSj_~!A>EjaiC(M zIi>l&CWN{3rKznzczkNYOSF^o?_RyGe@&J2*vSSx^>X6)!29;ptd>EWz~CUA%BSzk zSJy`O-#m$v?zsNxI~BjGstPVR0OsHQ)CXJSPBKc!Ghi;Ar&{ z!{o|drGl~e2=swQ8n1KaDeI8wL`QEpoPA9?D!fveLa|@oc=nvi&5s$D-?pgVV3UeZ zNs@3pwNhHTa);FVRP_&G<{b(>{YLIe(d9KA+%Gl!JxZT2y#Y!hb-}^U+xwF4tAhsG zTNrRyS7n#;*Q;-33VX)C^X1-j8t*D}UrUeQ6woOy<{0-DqDg}N6$l*f`oVrt-_P|r zmOCH!%C=xqPijwd3zV0j&3tvZO#d@Y10*Q*2Veem(U^}fPB>7#M7)yT%DcD&I|KIx zqYg1GM)`BZB>8fB;nr0!vnl_V{6Q!&`@x{~XWb-z&Y$)y zTIoM$J1YVyMklEb90)#iZsX3%jGwB(S8~}N*7nj;Mn(^oYWF=nluzSrdY6_0{w9i= z=X<92?UpDjJ78yj869<_uU+V_oK;mS)~&K$P64vJMlA)!Ze4PU+F8)Dr2IjfgBIC* z?ss?ArHA=-}D z@H#j~m$)928nL-{t*oP3-hhRHfpQ>J|K~6MW3fsvyDgVcZrx5Xz1!)y%AH7SYEv9+ z_4VzldU#oQf|=j=C!beMJOMYkP6i8s8vDs1)>D(4(2ov^Kdx*$Nf?MvL(iPB6EHOF z=Mc6F@$F|m~b1iu8 z#4Sm0ZvpHi-Xp$Bb`6ak$5rf?qrz9>v%i*F+w1fA@n!4;$?vz;fDYD)eQ=(u-aqL_ zwyMdpS2(eAa@GMU_a%nfH0+6E*-1b$=N*ug38^77g-%2#dW zT?cl2Y0rf{!s6@6QNlgow^yO5l^BrhHn_M@Ji$u8mkx(h@D*^#QLK>xN?zO7`_w@i zanGXOmHVyCcmBF-oLt4%y~iiUo~H~uy)LOy`l#*}bc97d*r;{!EJivxwVH+|E}~LO zR`%wia^%cU>Xbih=S79qo#1D*jD23)ss8R5^yTS=i95`HdHw5EZ}tF4)MlzRaNv75 z;jhXq_`%6?HF^WgyFwp!GcVY28|-`WF3fK?U&O9|JFv`mQBhLsj@~OWE#xsO182Vn z;#k7G8ykShu4quA@lvI8zAm@5i^Yun!}5#vW23rHt^P3v9Gd<%O^$CoiD$CUhQUiN zMM(#2Bd_hRllM+eRj5f>Em3NB`ghIxtV7rTQ!4yPO4j^0$B6&Ie~I5BM8P5Y8#c1F z&CQ?)AUgcJAHoN>`d~gCuH1bQJXDGSHp#}pfp7Kqk>RgH@_1@ngglm4EEZ?~z8C%u z{%;l@|Cj$oUux=0bhX9ooZn`lH4fC*tgXL5ZKghOK#9nSAAxq3UiQEWS}~wpAH9d* z+$RsJoS^)FzF?VjQ4{gmt1XL-&s(-zY-$adM`!P{YU}8j4@9{h*sZhhJ!rw_Ygnvl zcO&U~CadMH1+3*o|2@MRtvhrd z93&@+@WT#M26uSUO-`PenVA{wSeWaeP!LG5=au~CCk$t;jw*tVfbLflx;){p?8N}e z6UhZh04XnskRCw|MYc*XJtn<@j|g4#>AnScjU7*_=h6Z0LOQcF>-Ycpezj$P<~67u z2(h9Wd97A4IyXGKOF=PU#~EJ4NZ|S(-cD7xe2ru)R#EVk7ZVfOtyA}P)#}xyHH$76ls`jZT7kQ^-hDv~_^$ zD>VZWJD6mlqpiK8@@M!f0W~tr5_37wc7n8iWVC{%tLCu;+9Jy+Xb-Y8{$c8(%hqS2tcxA7|y{q%3r;TsT6t^u1znPh3>Q%Yd25s%j7Ymq_3- znU(0w;WCJCNCtRtdP49h0k~$Uy@)2@EzI@lD5NQQ;ld`Q2>5sOdLP&7qsep~GC1^a z(5$-*#lV5E5(i&EL&QKHm<#B5wFJ09zZ4)HTBhl6XUANWN2p!_n}eBvNe3@6n}CSR zFJ*n4f`fD7ZTW+Qt_|hi!+Hzhy~T=P~FqpwGhH zxEB$T!qx#=qXqOtWH3XDQWsz&qIM#pgPI!f5Q&#Uk>e!7ZC`F$hP6715 zWyr6MtCO9!$9P{%SqEBv8TbkA3UP@=zZL6PK|w)SX;^$A-**)POr!tgh@KZOT<&acemhDIW z>b5wafEL@ZLvmfsQYIBl7VCE}dv9vvu}+)*G}T_W8qXThXvPn%uAI-`U!+<_9YY^A z*E8mJ}IK^l-8ZE(Wd&-Lx@jM#J3x*BU%RaTYrvl&V%18hA zq@*MnM-S7rjY$O-J=I$G;&P90HqAZLD46VLDH)Sx=Xhl_rovK1={h ziVH>@{9&?CW4ul$!`>C3$gPSLN%C`@CB0jKZ6Uvg+L8)}o}ckQuc46w$hKcGYC7<} zUM7kWwh7lkljbZb+;&At$y=yzyGQND5q712Xx*|s1{FJ*76iZ7grXu1M32S|^1?d{ z+EP)NwmDC>S>~%Jrli!Pcnu5c#jGSn zDu83p&YhQQu0cxCxLb5|6^5H)OsWwkeyTz%hQO6=)gvbYL<2tlpRmWytEwi#EiOf~ zNR8{=^{)LVxZXs%(Y^9oJ)xon3bPk zP9Es*{{$uvMF{Hes;Yw72HOghv9%n2B*cAcZccajEt_m{)*)ZZc2OsjhIV^s&2Yv2E%rwF6+gQ;|9Zh-vn+vE&F?csP? zT*rV-0U`<8#QrE}`yCqR&RzBh+Q1=$p`8jkI?-@c)=j_YUBfRd+&U#FD0zpPDi`7K!H7Q)eHc7b zsUIC7HUh(q-7*{j2aTFK%{c*&zKNn!-r7??Ppbv;N&?m@NVQ$H1WD_XhsK?Df@bk? zrn2zaE}<|i6=wDW9gZ!k+P2>2`mtl^asKG-JqyYa{Z9`npigH)np2xOIKJQb^3pgx ze%P;bu-q%<`Pq&~Y4hD3D<&F0*Csq*Xnf>?ZE)>b{0VRyM(GooqxbIHCx-*!$G`x{ zMj;$>;hYI#vxboCN=V!oDlTwcHH2OuX3;XFfouezjjy=)*6KXm9%LvD)7Sm%*|SHDm_N>RL=y@$O^i7{Ty^A`t087coYhzg&(JKR#(je= zx$+7NA8R1rl+e6gdDS>nhC_$(8$K{7BqS*(M@eBJx%oPKBKscn_+U7gfh{JI%*2el z$m}w88Bekgi3gssuO5`VpPt6WU9GVTmy0_HU6W>tR!O@+LrV*{U&XGd>LcH-jr?{J zwC=qJbD}G}4{@|SZEdB~Yp3%FDNB)m{?l>ip5Ey-<9(Ae-4hO*sF%4Hmk4fKYcCwh zt@VtXA&)s^a=*sR+*YWxQ!!*PAv04E2IP1M5|KU9U4_jho&B|+KQoS-`HA3Z;l&{0 zQ9z-1KY{xS=wVW(xiil-d&Yi$YHdvh0#yF$mB%ZW!aaMWRxhIxJg1cUkk2x;#;~^r zYo0+?O6ud}e5YEOV56g&jp5rTzbyq;+-9ei#V3OHD(EtscTn;Ck0@FdC8eiO{qHir zCK&O0jZDJA_f?snjo*LU&eaMO|C0orNDRgzW7w3zYtbD1q-udp3dphJu%eQa7asLs zqYV}BsI7erl8L=Avq@{-ITEqJ1@V3~pqWie$vi444Mw*Lf%m9p8wSDXVgf^nHTQ)qeze8yvdcVsMJ|1De2AC; zfS4Q~UC-(C4GmX2xUSeHY=PulfneSJ&z1fK24_O9dHf^@UiS#Kf(fe`CB20<%(XA|PtGp41 z`0@k5E-u%9TB7W_`G7P+40=RL$rm4^eO1{rhGkPXHSK-(?)`f3(W;Pe;Q(QmGSt^U ztEdRgxBs_MJIsak?zZvUE~FO@GL8fqEDgwx*xphL!1y~6Jqo5JPg(}~kJmwdsewE$ zw2_0G`wUo=&+SH^#LP3DTJcLcB_RR!xM9KjwNmkLbZ$L^V93@s*CJvcmY}!b_O-Vm z-}Zhuz7)|Cr;`rPsWz<3>1n#+edI$RLS)Lk(9+V4VKcW04p`;yjblchw;YaH($~ARc8ek4g^Rvr05X|ue#?$?rWsnu%R(`}f zW>0HOLTrGWg5=uU5)e$|m%J`cQeMx$@Ku*r%P~HOHGn<#ROaCm#D_;#n1tTu zI9${W9$BGg8tUa6$H(DGNb)HM7!JbiXLm?t>}_U;(@IGfSbo1bm4#evuqj;v$tBsZ zXtQR68WSOGkNoGH_!(Z;Ci5R&zP`m@9WC~Pc2kGe`~)nwkAwG!KHIM69lwLx)9Cgy z7VFotl5hhyowSV1HcP<3K2ZEyy>TsT{E2Bu(m3EYd!5AWHwW~2z3-{~pbI1pWn29e z6&0^_Aj|CSUR*Sk#+QL&UG3>2A68G+cUqy!x0RKAIXcf9Y%WB1g6F`b zB(%6VncCi6YBc8e`~48HeevbSbZRl#ufy8G(Dk`Q6Vcyq<=>g+p7Bp_)INXy(}TjU zHGkiGfR`F)1NFfG4q0CJwm+x98|`#&%L^|e;4^of{o&%tK{>!h-p3%ZB3Ow(?87t$ zY4e1A(A#EJ&^C=uB`Bs_Xr885wU&JQLFFz;2=+Xl}yp#w0~pF&&|)T z6g4++bpoF`8|*_*IZofXk3!LT(S3${a(rALD;cT2xCv^|9e|NfdTm~Q6Rw%$wF@ml zX)=I(977fKKgLKoV3GwnQ2H{X=8Vr4pk&*!LcJ(blV)R3fiv_?wa_E6NdZ5B zuMuA}Ko?c9=~_QFDOhOKfa{uqMgqna_32@URO0pjWr0cx3t9_ah~Y zpHgFEPr?JHaR6$5tLfj>uBi*tX$v2*-8OCB{Pfwg1NaL!Uhhrg`|zWRb#d~_P~OV= zt)#r(K-R|r>C~$R-5AOKcF@MCz0h9SSP`5gjM1n>;C%Gx65E;Oi&tQK4>Q{EtG4>_ z@$p?yQ8^l~ft^YC)bNcy1rR_r-!gXoN7}r!TpV9~8xrPh>plf`HnuedMD5&%RGxgZ z(d(2K0hlE~3fMJj4(Ma9f( zsX{*=6cv>QY;mWS6~rLqM=j0@p9+8vh@6OuH=5x@Bjpb! z!~}e3uhkS6F0DYJ=u=_ab`6f$(&OM7tu)!64U;jQ>NfP!okzX20N0i<9$S zxi&^(2Vtdew0xQRX@REQj@pKOkFfCUb3K*mx%q=Etv)eAbJO09qgumxWL0{k&I}dE z!^@)P_gD{d;0ZWi^zLH$eTU5eIv$6KdS&*BVK49|b|<(lw2|(%WI-hkHpMf+Jhf_rxX_fF_h1Yt6Rg7=U~^C40K_y@v$ z&ZH$LAtAvakbBwU7U|MJB{+}Gry4c&YB#B%H*qWVR?iPqvq=F97QFT&kl2%9DoHQ5 z)^AUeBbW=2n)(NLIkWeiMIm9B##Fo`bHAe&@}ca2I2OAjrrPN{pJdo|5!tj&ck-DAxb=2Pn$EZUa+6`MpT-1a+a2oQ)p(X zPPb|_ZOb-JVC$>tTrVpn*AOG6qjR*Kyj8_sGD$h*-X}5c*|QdRH}B&te9b*G*j%Js z8N84AXH)Uq%0sz3v^WA(PL`J|JStf@nmM*{`}V`yI(dwE>djg9Nkc_feYUxVj}|x% zi|*AZ_mVGiV3&p4vZ|R5v7-}pEiFkMCQb2I&TQcp-Di5}HpcHzE_sVb zs(=2+1#swne0#h`f{xZ<9zMbc=Zm$=SDPT@Uz`J>n`{X7nEq3SvNT$GTjX26E^SU@&srb|@zDQB$>*;ea+l4j+0COdg1rm*vx=MoY=t9XV9 zoP#q5Uum0FPyeBV{J(5MXcyO+Ti6M5ew%EBlrK3p=!0G+jOR?Jwsnb1NJRT{$TM#o za1dq=+O{X?v-Y{Xw$wtEe8CHvkCDP+v=&V0xNN`6AX7s~c6cbm(fv*40!Des1u4HV zOn-wi-%KerG12H#O%rG4+nDjJtb-da{rdGw+Mj+vTEWI9*wCn&i{HjQXV^|_!|vDb zdd9Fp)%l*vGyS@D*VA*`&u~2oub7vCzhr_P(PAMq-Ud=GmikRkxOT2Fq!95xn;Q0`BL|IfT3Un@Z zse}7M^t!7)eHt*15V4iJdv}$mx99o!@XwZl+XP(-WoUY~7`~jHk97k4MGcMy3O7L?>ZdlV4JA@}}H^^hbBfzoc9Nz7Ai zOzPS#r+IJv1`{t1x9at z&bQdGJ25~1WSfY{5n(3gKldnlG03&*K`<{ZC3OzCE9j-{z-$!fuwKyZkwqR1cGa%) z-Mv-RMQR zVs~Y*eoty!=ph4tR@0ZXtB$bmkd>49^f2+qqZ)_!$^6_#Ze{BQp5FNQ(>mWxuU-xD z4P*{rd${+zc;oK9*E~Ne#KnI*oq%<^b%x$=6LPJ0Z%!-s)#~uR)fIMqo+v#*OY7B7-C}8j7Z-sUPd=kdIx48F1TK zF<4^bxH>>pII62K%N8N5lKS3z&F4WDo&qLD`h={kJ)!G1=m^`UXI}`pqISz|;l}4+ zJXfH&NC}`Bkv{z(2ATAxZ`BQeyRbDAW{=wETFaD?gbfuS2#qHf4h9N4PkZz7eq@)@ zh|oZ3fZPCNZ)H8n>1!GxkCAGigppRv%w(71$dtk_UjEzayi?wZ%8-4GpT&Le&3?|z zcJ7&Zy}nGMsG2$V)b@x7>GSRYvAk9C&fUKs%IDZ{I7A~-;Ghz-{P13uN}mUz5wRcC za{7MSmoIf3a=ECh8-sUa?KWhEClFkSymsC(2Tu`Aefq~&DuZ$ONdZ&GWid7MtM3c> zj;zo0SL1fi4|?_CN9_g8pS_{(SiSnY{&dzh5{}-siYXs-ts3C|MZclzBaVi^BizbX zdOZdm^Ea@-GFr+uvTTX#+WqcRTU&IoXjDdKhH2fKTXE*5*QNcK6;WD8b)>~0G#t!v zz1w!c@qy|_7C+YD4eNlH*ZSnJ(~{QLi(xU_Ydo`7mnTMX_$BR?0H`m z&@u?z;{rlVRbxF$n0Lm!My4;rYqx#hL}F+4!<4y|UbpH+cV7OuDa2yM;!S$dboE`7 z#c)&eu{8|4q~Gx;0OQN5q?m8VfrdXQTOl+*Y}Q^_yRYv#YMMT{nklP`LcD{7`xv%i zZhl6XvX)jFBvQ5ebY7`f+etbM6#N>f&tUgTo4JPdE9m@G`snA+o_>Bz=?=Gq8#y(j zm|zp1vMaU%r$XF7^82u%{hwZosXsmsvwFN37RcC7m`h06e;6emzH0ZM(6oUd8jFdborBHWcyE_SUEim#Cut-L{+5Ve z5*I5!%z6$3y*n@k~2Pib&zDccu_kqB8=zIvq12z*AF?U zjoS`nVZdfaKg6;QKo_Q%JZn+o*^K1==Iz@ZrHQY7|6e&Dngu_}C%!PDXJSf%XgU!l zZY1AE@r``D%v9`h=`xarlZ@`>1)uG<(-VwoT_x=ne+th_-{?{HxV1o1#l3HzX;vk} zCLjfmX|r`4wrYw^%JDqf9+xZ zkk^hr)%QCQgp2A5;@&b^Uc4$BQNCN?iI}i8YF?=I{wom_-i{dDz=W} zJOBOep5JqP>+InJcm6v6&-KE8p1AXW)DKQUz%ukzOhO{e<6i^G$^vZM>>`Cu?(w|) zm$*g6p?|J)sOL(=(XFe7f%yNs`}Kc4DgReS#8ozAi7I9MHGj5lCo`w|X~OTjzKv$2 z_I#!_V`d7Xw1(TZOv+CN5~d*kN>r2{i@ORCK&gU z!haE0mX||ro%-_&cM5I{-5${%Yz>1UD3rHz``zc%;VtNm*?45M(CXKG5n&HFzcpW! z8@<;1UjL^di;SQ@$N9{8pK^#r7s!+p^L@&Xp&IDrReDpr*Di89jS)=ys~6Y(X?|7hB*)KxLnk#IV4fb0KXzs~$^xFQh_(>WoVO zmH?_1{JouVg2(lN2$cy{CO|@AfrsMvt1s7&&b<9~E_W`zko!)AU*9i#;B@ zp_R_FE0*UoK^c?-F@%AR4!EknOSD1|Ry8$3kv##bqGw;$h^iX=x}|sOAZzK5&~Cn7 z049VN?w%itNO0gA!fmg*3J>6GPKXbue_jp!GDV#S zWYf~qYj28A0UQWGfG)6NwDAaM0H6{iq&^~GmRVn4->W14XN+8BfTi*TwDB{6Bu8<0 z*aJqyx}X2PLFZ0f(5%igUY<!m|QR=kD@~f&v|+zp$8nD;K{>(Pbnsay$0tr0?{? zlwsa|@?@+J>4`fgb>)IeB<1!lpZ(6I;Sj)jpKBpVOT z!`_QyXXWH5!M+nvgFwg*2e;->0TM9k-v`ydEd*uDm$b$g)KGk-F2MGZxC&g8xJ59WWoz_Vzm9vJJUi8q{}3zz3mxz-xp~4s>@AMsmDx-8xxi zrmTHJ&_J`CN8Zm&K0(X2sZ$LjETVhA=a_=d38>mfiM(}Bs?f3K<`pYB?%90c!$jtp zFB-9l3$?^V7YSSRBOYD{r!wVyBVcl9!*T;97e=V5p`<~EMYp>5K`_+ncGE(i0s~+= zw81ciQkhfs90)>*(1frBb4pELS2r1qBx*+{E8l~aq7e+477%1R5&wDo1s{;8p%kio z`3E_qk~mfB+WP!-R{P7%Chcp!x|Z~&h#1(8x;?;_{wUqPEenfR5fd8^ns!Ee;4~Am zbRdt2h)_d2*|!&EMRDqa`Nv%-Aw%}859MiSa(!r$iMb9Q>a)t~&z~m{o)JLw?gS{s z0vEXmfNg)M>>P}y^;>_xwG$_WMB%F>h2D%iY0P+2t`RiFN{V>ok9GqdY zii;b(Hb2+YCVK36TSzFLJO2cYZp2n3p`Pq@g_QKz3lmOGG8yU`&(LTmztcj0sR|^V zm~)f9AECu=A5b~iH6qQh@pGN2$ZlAc-$W9DnbSQ33Kh%CE&MkMf>iJfG|VLf{;TE< zaWY1brKXq%gXmNdu+aTA;X+93LnuW+^5LLvVG8Io3yf`W1rb2Cf<`esB0~HI9)Me9 zlz~tjEFE+q@wI7;J&W=IiM=~e>rfbAngGA*2)~J_oxz6~#yyv}p~ha!9HIHj@X`G0 zF_fAHuublPvP0D0B;q7z92AxNU{L4!UoCD~Ij@riNSBd{N=9<>z5AvaoMw8ur?F+p z5W4b5TT1$VMnlS51jRn6dnxB=UCmKLq0R-|1(2NpdbS+Eup1H-ECHtGytP%21y<*a zrg4$@1Yzsi$4g;Y89jp(iGhvciuq1U_8DCWjk`)+Q^x9}U{ZcI^#}zM)=R)u5^*|T z)(>dzs~0a^Kjk~4(XC*LzFf6~zR!gNMm(tc5Gn~Q{*d-V8Kd~-V! zj6kn3I|z45a;V^R1NDMXlQ&R2fHnzkdj9IKg%lUZ`um7XfO$gZj_}VAwjDxWk_HW~ z#xjJiUqQQbz#jzn1cYJ(?TuhxBBn)J>_ZoVX_fyfe$XHwpaNKy`9MS@LTj>4C_`s} zI-YrE7B?k;)NEnk5|}F2F3k*9T0%XI`GquPTg?>RghELif!ckAGe4gyMZ+BS8e}OV zu(m}<$K?grqRlg%^}B0SVWX{GYhI`WW6J1Q=6txdYInw~ zxORoYNp*vec-*J?(TiE>Z}dRAo$CKOrS> zZQwT4uZ;BdQ{}1k2|Oa48e+xcOGLrJ4(i5A=QC;cQmRupyByNTMx(B#)mDD_he7pT znh_mf$3Yp68!&Jn9PnFKm_c5R(T6?m?OS13Zmn%?>7yyGPy@3%I*dd^Q1FI@Jr+<6 zK!AWYasu0)4Z3e+6@lOj5=@hKH=}?v4CNVw775jUBy0d!UQz9D=(T__V#sq~#mKHQ`Z~CzyqUc5i~$R{ngpL=_-MDp?Ey z(IfFx3M(sXF?a$%@bjg*+H#)S2SA9l$MkE2wFOKgV2r6^U~PhJ0&4Oy^`6W$G-ml# zV%w_=-g$k6`nAJGzfB=DLPCLIo1&vdz`8Y*F?GQEE4IKuADGwLZ+xX-&FC&98||B( zovoN;*>QJM!NT%lHlfn7^UNilQVbp3{`D+{f*9#`}s7=-PLKWtp1UaSaqgug#zzIixyzQ90-&%Gx znM6Xkr%>gx6#2pClqrLF@%ezR+c|Ug#TcwEx2fc6(#H6ve&M1GaVZMzKh--(X#GMF ziJ_9)CB&{>cot9nulYABRB0X@eQG)U4a9dy{DI$Q3 z*C0)2{2KcK3&0fa4P4C8)x`h{`5K?bdxzBk%7|x0*5?Oaj)MiJR0`3QgmH=H4{SfOy9ByuY1+)XR{1Cy+aM`1!%p z^e%+O`g(dP)$G3N9(Z%0xkXdN#KqCzb`Qf9`4fmgi?4pzj*MZ%;t)u>08zCBz|9MO z{J2?9OD+wnxfC#LZ%25btb-W}Ko}6wV?i}x3RFANa(-Ygcf8jH4{~v( zCye*0!wG8-po#~Rvl!SnV1A>?Yn0apeczYB^bJL%QmYz%k|{l~Z9&M92CPk>L{H9? z3>tcPcxdCH6#^7EuD~Al5^74YKLcrEu*Rtb0fyk`Oy1t8(P)QkY1jUp_af;Io{=0C z1C%F*E5>p9(F+Jr5ym5&^P#1U0;}pGzx7+3U5I0Q3TVtx6zm#Z-)AFJQ!LQkjr;ri z5!MfEC=jIg#bnzLMUzNeVb%Haf#;!3*L%5usD>lP{M(HA_*xEJ^@H**&Cn6N-4o9-$&q0|qQA0)sBwjWY_ zEyzLAgItawAp@fXg9n7HZf}uODC4Nf+M4yNr(5&nb}u%T`DoD5<oB%Z7nt2y@7I%cGq`y9 zm+v#u*^D=|Jqj`9LhU}t-jV$9p(<)Jp`VeBqS#!K_~!hRy%dqch4K1U!NHAX#;GgC z$$=>7>X%dhOtigI0zLLDmN)da;p~!wg%Zu4Q_%8D$HpeTV0s>Sy>75ZGBPk^zJ03; z`hR6f7IyZ4hD|_L(S0{x!o~vHdplq$20wYy0`+hxNNpqrz~fm0I)WO}vI3Y_S&#uC z%PZu^r!}HQb6gBDu*RO#^Mp(Z;g5h__nVbEup*OaeEIx&A|%h(;ud`XI;wLj;XRc` z9XIv!D}yB9UJtB4$grW8{dvbV7japD>NUW$-+=QmS{u&!ZjOzHX6=r`@i@nMyFhR3 zBV0z^P0ZdLc-)pTFV+6c~HVD8RK0bJzIBu4=X}Q_Q z9)`pFE|mnX%8gvn3Nwu}H;)dNF^9bsh7*Ku-zMGAx31sos9f}=5IZYha>*@rYoXWQ zUc#h;geZmnWj9v_VgAENUEb(eu8jN8`GV~jX&L+vlGWqh9v@Ue!3Xz)kDsg$MH&=- zxz%oLsH4L%J!}U-#?%y^D7o!w8T8#mBo44=omy_2($@P(~xoFQ9=AtgP1Tjp4(Vpx9BgP|aW z#IlWIBXU$gA+Q;Dnd@Z`jxLo#u?$E$ik-LM_5q*fmtp`I3cxw9om*v9Gu%OQ=f$C) zyY>6K(j=Y|gDj%ExzfH!7hSi>*-oLaeg;1#2QDE9ISrylW`-ZC6p3)UjRY;CWxDY` zi&kSIHL!QJlIl;peOozolT2!jtVo5$YvfVclr^SpYH=zgdH=pO@~45ZgJB!nCgWOn zMQLEV`S`)yUgscyTqD(8>z|pbO8;VFvgN}I|LwnRL=jHEx$_H54tL<1@EgCqSZLGG z3R;Aq1Kw$y^pAsZPnQ?8OCa}!5kh!!_>3RVr7bQ1_pSyaOpHpg`?ziJP(k~BFMeuz znqKVFjE7X*K}!Cdzv-}2eX_~B8+TVYB)Oh`Nw!uaMK=2_rO;2bgvkqK=Grq?<}z{! zw2$%2EPD=;)Zow9cm&#gV@g=vEy}ul9vU5_N_a89&jT0m) z=IJ2yBUVEfmgf@R_|0+pApHvkm$_`v;W@0>3r7{X3||12C`4|9`u>N8ZOS*^cQ34` zg4qd$++pJG?MLdNMBRF@YN_S>I_fGs!|Ut-raluyxMl!bYcw5E2f}2L0`p(lzAImkz8zPdIbXY%rbUp`|5+oA{=lYk5COEhiNUTf1k}-pP zLrI`hwy%7rbI?)|Lve}c#6{!xTv^#T!wgOTH+;))PwihKvCe&4t?quaOsDbnbp&60 z{`XI5Jo0AQrvlAv)@`7X=C{${h);*|U{E{u7Su_KR|;ABt}+4-kiBL>| z*g?e5;586$!H3Pc{*|M%bL8Nh5lE1L!+P@Rm+MP2x84I^=Z35R$m^s%;fsM6%(d)X z(hTGUShz-tck~`#^Q%ylK$}1LO@fBtI(0K)5lYz*&Wbg94}W}kZTZylAagfNVFI^yMVgZVHdgW*;(My8Oe6QZ9p<+s4gSu;j6QHZornvX|C&CKs=R?6W*&7`t8V#ky3euDt?8 zK@A^?*aKT^~C;?0!jD`0oqm|){nj4r; z!_TWnjtajH0ZUK-Aiyea_sm=;aG`wQ`mh4<9am$19k3kNEJ$fTX(M z=O<=Umu&ezk+Az-09lA@nOYH#!QAR?^aYb-u*B_uw-^#JRh|d2vir&!2qPJ0ioRWc zUvXn~VG6pr=1mpTe60ZV1)($ls>s`4F~#>gK}CMxP0zVYJ8ABi-n?3a$_ng2pihKN z2rdb1G9G+(r~!!99S6#cf%O5@9DvWlK`930egH2$zG!=rmL^T!T)hipMck>+j<0xP z-ph$X0}qAY9onUX>Vj5|36gC@2qp3u;LBOJBTrE+hmO#0=lcYaJW{?LIs%5NA`wP; zpo&Dok5g(9c3*-SVLl!c{;^D^AW_w!)`MSeiX(#vAy&b!!SF72pE3lbip3F1D_&E( zfDhsHw5&`P*pIYws|ZOR49YqXrfL@4)H?;^L^aDDRclB&wu)aIN*4$tRP9t}XQu;& zw2X)pk}o~YV_7&j`Y8#zQ{R8*JeUWltfC_m-xU|NuRCd~W{O~>w+LLT_Xg6vGX7U- zuIda@?HxLz27iyhZ?LXq;_=zx)%5FDaj>);DT0?a zhb5(9|uIOer zM*CNV^Nt#{GOmO=9OzvI*Bp+9e61TJ(=_tZ=p!5gcsDF8EU$dUq}X}T0+tbk!V5Ja zNWQ_YUmx$`2$+t=D;dLI2!7Qh+<1@(Y-Bq1+EJ*|CpRSd;h}_QMYkF1dHcG;H4Yz>hSOh z6f(?z9WF;@NdEIJbD{lg@YEB(f{h$7)kCj#-VjD=q`!_T>*>$i6ld9g{td67kKO-p z{>>Jl6I&j=;C~MJTIKAsm6==jP_lLcxuAcJ zc4SHV=lGv4{=cv{`u+?3p>!J(0&g8IJN~&QcX!iXO7D}?R^wE~?F zo_N>R6u-wE8^RnqcB8o~aMxMBdO~~JpW80E{~3(fwljb(VwO6nqkm6t=l)hxJeB*z zJ2c_&pAXGGghfhwr0b^m=-45PZ3ZJ({Brn_xzAe0EajJdHSRNFL-B`qh9&dnLw2c9~W)`OpWzQA|H z+so^W@JV&|WCsIciS?8w*eDd^-ey_#v|z2vvXcekXoi%ZZ-T4NN^~Xz7!4qadO(0pdoJs#K!rv7*mH?C!eo}E%_;Y# z58Ow4FjuCDw=}bKb_mM-TonpoO#lQYRunyy)+dV!uTa2o7E=-As-6z^`sq;lFH?TLMU2He=Y9X-M$!fUPyWqLL> zq8UsF3-T#sz2d7as^PC9YOe}?L-cjY=t~o%DO^o;b#IHOt?t{K9x-~QtO?K0;HNb| z+;W>S(HwSa&12v9ncO? zOtRS!){6OFBK@)d-o29zrln@^(24!n!ej@P?6SV>8JShP5`fu@0l~_<#6u$H!i-e%{81EOvuK$C;yn4NyE>v@Ss%Hn%b74Znv*T zzeBf_6%Yc%Z}uctoe%7nd=uv9Ultw{DX3;4|3N^&K@PVzrI3*7Fs<}r-@yfK&DnxG zZw>Zy^n2WXBdf71&H9xC^@DJRBi2EorI@XrrCCB-US8Rl&rpLRr6S`Qei?(a7|F;q zIb9>LJWb5M#JjLOQs1XHJ69p26z-F17Q*6H)6{e(P$4CK^HqxGOhfPiiJGRHZQ1S* zqs1heS`_38%?i#9fP>t(-nv8%n=!lIP)uQ-4#q@$QD?>^o=4!_WZm4`@UoI6(WWh? zO)M)oc>$@Fmytyob+3jK)GntoBx~{G@DDoOPYSebcK=IlOD{~kK5Ulg& z`Es{oO-(VdEhmZRMuQ4b$rrQAt5BI(n-!~N^f6)vlUm`A+i#~55X+_#O_4mO?PaK0a`HeDWr8& zRhSeSHp)-#q1z{lUMe5<@x31H!5zsXTdzd*5mseAyiW^Dx!4fxH8plu(PxArYHe-W zF7#C`EG)G7sq-;NJ}xEpr4&|<@x7TC&Y-XLiOv}5IcQTZJN#HxZH7qrWT_SUq#4hV z#(T}7z|Zf14|x=FT+77zO67KsvYs5H0r}+b*YQMUUt*LB=kiir*4rPwjkvEJ{C>+b zVck9PZVOY~zeg^O*%{yg?AyCn2iOQeJLW=x89kydDDnIphLS;}-_53`R(bz@ScTXe$T$&l*%>vLk! z4mGun?WB-E>CIGj&D%w3X|@p2R~?#;&jo=ruL!ToAXP{$X5_HJmCp=CF1go~?k%tI zwy=nEb)Cu|ER3l z+_i22fp=4vP2Qs~9m8`wQqoT{G8ru?xMZs1^;Fw&X>+u>hyL6q0a0gX=k_c;6(}F* z0LNKBLz7MA4eyf|`12az)R?~Z;NrTMFR!TR6?1%1zHq8WUxk_n9>VE%QmqXxonR0u z9+P`=_~+76PEj0Gueh?x%1GndHrAtOB)FRjZZ7_ z94{+-AawNJ;hx+4hP1RSf_5Q)E_?|60Dqp&WkbYM%X;GZ=7MO{Qa^p=SvgL%U7rdP zKhNbiw_mR@YTm1NY<$d^%RqhHERL7uOYBkiXPV3C7CWf$C{&)` z&y?R$Yrx8=R~fo}$3~NVNsS#ieYxOE4;3omXh=AhfE>g-tq zE3FQD>URbt$C|+v#8TJ)I`Tb<)4uv)+KPxms7OB-SC^Du=pK=?t}65YdS+%J_EC51 zo^?-SVYc%wyF`1;b&p@Mth6ZNve(-X&)9_hPqnYY)TS^WSddMV#Z&E1ArXD@_q%Dy z49c;l!NI0;wC=;MO@HkY*u6{XQYqfg{VstNGXD_GJd*ZPi2l?~enY}nc{H=$p`k~Q zyfXFhRuX9BG)J+E?J#&hD)D}7e{{|Qo|O?+f+5x|X8&;XicR&zcSC+Z2EC@bb0Le} z9j|Q*vNhdCySD=UA>9w(%!h>A(%nT)&32`=J4j?Hs z&QN{tD1#c`RcvD7<<8~VlPLuxvf|s@w~Ac}hSj&u-$`JxY&nl{Gng9dd0D1~?Ys$6 zksyYn>^#_p^yz6c_nU)bMIT3CsnAF`XXH+7!&D|DsH&=k7x7+#?f>D!N3j&twm$8t z2>IHELc{3mr8#CMg-P13<`h`t%``}|N~`@xc?1PhghlF#?Yt|jg1^U_O`Yx4DsohL zl1`1la&rW~%BV?{eQLm=S);9NvrKrGpoQ&VBn}+97Q9C!tMqd^l`Lw$PW$e5_IzZw zH*T&`dpTr(Gt;1ry{~S~7s;)lOw#cIzRLRB4dEb<5Lze?%U$VhIC(=?V2)~$i^ z-Qm&ErvVUn_-JQMZ|~F%yxVvC1^tT`Ggmk+1BdpBUs*YWIX*(V$l;WhmVkX}*=Ogo zh8q#zd3rY`_f?ebYmC1>SPF7kFT*nNr4R{0=cX|8S0HUGpT;}yDZO}~2K z71dg*IAi*@Q)H^qpvG;J#(JL4Rgqh6!nS%Njs2&>Ew)I;0;jl6+=;;_S(+95=b;G~ zjEe1ti}H*K%IJ=w7X;~)mQ1MMWzOvN$Lbnd2v~ABXSlES-!?7ojq>+z%T?R$KGiz> ziUOCvROMe{Wa2FBJ%JlA$NIAk`7N%B)K=q(wwK6WtZY;{X~v&VVDk!nktRvsI8)HS zHP<5`y^IwHJFijRjyeJSty>EWz3XZFoz_2BBOrsMxQVKhM@LyCDo#EQc_uVDtTPO| zJ$m5#Mq!VoN2r0Nf24b(JKZ!>rqq7Ee=_1fe`#VrIok7=W$HvvJSLSqd%)mh$8~eU zhnz7DKhep>1IWs8pJaivvU>xoGY@aE!|X|b`}6nQSGq4JN*ZtpIqIp6xgn}ra$sL; zyJNjDOB!9w)1?tXska6Pju=L22H=KqG_c6QGA-~3@g!y4BPMmK! zM4Bi(;Hbr;bM=nS2z#i0z+HnWaYlolxjPkivsq`-0?Q+K@a8^#ZV!AsS3AqTtfL1$ zn<8iV;EBDeoLSz`ar)7*?j=|sik*hB&VltEE2)^L9)Csv&5-Udj8cj;JASO;bvX7r zrqGXh<`jfRsnjwH6~h>FxaFkDPBX_YKfcudSK6ukPrihXzRUFwf1BTS2sdVYljLs^ z@8dR~FWfidk((gDeo}9Al(A0DkRh|va@c8HGr;7HsAX0VFU##)QTMhK9SCkBsRG;n zC{rmecu|mZu}j1gi}6XVx0mATQtFm!JaNHckPnb> zaiJ_McEK*b69-FXw$UohrAJ16L1S~auQF^eRl=g`o-8N0H2migSDgxa0b_Qk?3>`5 z$^NibvG2|&koA<2skiuQ)+L7yCd2N)j#yc`GI{NhgkVfbiB6ie2l5P_)o*u1@pT9m z-d^sS*c0U+!oycI*+Ad=>ZL=wVBwq?6-Dn8c8L{)WO@wXD!OiDDvLn2SGj)Uow)Hn)P;@o7Y-9xAqPTGatwk_;Vwx zEP>Cah2&*H~eC-b$_mirsPXeJzUW}^6G7!g;fzWQA_HdT_q zj+)w?%B7!_yZ^dOW8wG6+9dGcr}kvGiU#E5a2=&wU^?pU<5Sxk<4gW?#-6Z1(qy_m zQ%+G&54LoLa)t-K{|n=`JZmB5V+u^loLd>^P~I;ju(9P(FgrUt=+2(j#edMAKY&hY zxxA&xDn$y{zp==Y-n|8P^dwnU{-A*_g0NuSVF?=Ax(@Bb=TiTrlA`$+{D%p7ySBvz zKu+@iBT(S~B69QR@c`JVo+m(4)m``Pn~VFu?pI_Pl=_S!T`T}va>_|Jd%X2|8g03B z>n)HLUi?29a(&)%#s8**D9c`0Dz}cRaDdpgu=HEQ4tc#48c|pl`)^Pv|Mip5{S|9zhTe%CypOK4~~*h`O-(Bo}>mhgO^g>jqJzndp+;!K}Y0&zn`V` z(4zHM5f}7JY*WZ5GEGbJ{x!{MXtZ``_7%%chl*pgCbl_Fe00rY&^c1^Q0;0Kthc6X)RYv!A&IiSV z%I%BTjF~omYW=uEDO*l`VNdDW@;`T~Di|URcH8o>=uP=sp?!zvV#rd2)p{3$ z#fp{vW`~C&#JL;8`vZ04?f553s^af+`&9nmbmX1a zyWGV_R+}1q$aTa1KaRcU9*|hQeR?ixetYM3GxpZNaQ{yswj9%{j08#UXPK{S|Ll_I zLoV6fvX|^!9lqtoA*C|=WVL}3TO-BYN87l=x5pVHM(y)t@G3s=Vn>s|LL@@5K&Vt; z9u3wRx80?F-{C52$A+;%GxLu}T}ynn4P8L)VZ`HY!e;aLeheGEZ;(-a#B1?9X_Q;{ zD*ol{_4?Q6z(6MpL}fgbb+F{oT3^U7TL^|STPhfQng>#r$9+U?xj<1=G8lk&DjnVx z!l7URMo0z;^8Q7x&|M}0ntnVrd5Jd$DcR=6q83sqLXJCX=MyUWi|l)kEps#%>h>yd zuxDxXj%6x-@)5|Ko-SV=SVR-vJ;c_PX2u6>CYZbrNI#Ohp~&0O)Z9ssZ+55P@VFec z#-I2{6XS#FcB7=I=n}W=uW~Xy?Wf|xuYKA*HA{B7&9^#D=ziF4c12-xAuA$p6QyQI z$xOorw(fpuVOTh3B-s-qR(XL#-KYB@qu~)kV%~Muc2B;9wE%NfWGTs<-<}_lG;l`6 z&S6i;OG>l;WYv%072ImwU;g+Xhg?kXzTiSDif+p+6hO?aaEpyHO+I811F)9bwquta z_>4zOFLxESBLsi2!$b`GfUz{6+$Bpv$&uzh)sY3H z%YK~u?Hsksmz$vi4F0S)L|K4Yo(7Bv9!TFYOVBkZ3uONoAZimcRWQoWm`^x6&!L+X zevO7Io!-Nq_CrJR(Q*zi2&4gjy!~okxj;5?+JAkWUUi_nsomPljq1HBqCs9^f^9k5 zzq-RGV1sktfu&BZLFz2ARbX#Z`P!Rkc$`hL>(L&wmknK95{Vu8GsL%zwU|rTU**1i zdwvoq^aDwk7L9PF+55+54~03v4=pT;I8=p{FVBxVPu#^<+gGp1siFq*Yd8X@h=0fR z`Kc*5uHOz?*>Go*-lX8#U@>6|_SC>FQgQLpl<^85LU)xS{rU&z0K{k zp&}R&Ea$F2AmBhLht#N{a~Y19jg9T`nC;5~N<>X(rxA$aZlQZYe+LpA#1{kp#=yAl z^OM8v=^CsED@=0=YWewypax8N+oz68}l7`_~=nK-22dF2Uunpvulq}U91Y!lEEb6ojEC)(W3h-_0z|#{q5-P((`CIa3g$f(p z_9lJcH6z{c8i$>Lr2{;_99aEdu7Jv_1s<$h9e)WJ&^Z802{_oO@RToVYHDJ>D>s*C zdX3JVI|pz>Clty-X2RwV1>z#F`RjBnqM5BL)!=>#K1zUY`-g)EFci=;R$vv8+};DK z7&M#)0&-rxdbL zGH+yleo^IkwL{RG01WNdFq>nlO-Oy@8e5L~5o@m^mVlW8J4 z-{)wa2%2qWdYNHQ!R_q#As?q>mA?WtjM@^0iQyK(Gc%VTLBYd~z~4C)PYwcqL%scD zPK9!!Vf zQcx(2_i-wcHt#i5rcW6Npjb5|;MJQdem?K~f9BB1* z*68r3A26p{ZMCxcighJC^7%3E>pu9c0F&z46l&Nx%Lb(CqEpxK z@ocM)YU^_M?WJq2%a=O~H#1uFX*duWjx<^(*~a^sS2j@-D%R1syh*>}k00+dR@%o5 z_$_myGB$4(ISi_5$x@)j-v(Fra)-l9hQEd~K9rc{!%{|^B|8&Dv?}rjI^DyeS|Pvr zGZ2&NJ6owCK|ceArAC4ECaHU!{rp-T+8-*H?vHh5573>F$>s?0-F5-$Puu(52pS73 zD~7O*Af}$ER-oM<^9RPesK@k`hA0sgSRT?~MNI;lb2hj=_Eh-e5f^z-Se_9Q(hieb zc@2aRVn;5PQzdlb3zKCVv?LIKJpnl7XyE=3drR`c$RrUwL8qX>M~*&1-vd2zD$f+c z9@Ex-ZnX{sIm93sq(R6hW#-ECQj|jwYBy{YC+X?wi=FyS5T$TRn0)qb4!@tW2_J|+ z(>!zP)Fm*+&2NhUX)$b00MgHgjYCgn&?ij6Bo9}9ACDSL0WGR1Vu;CiWPNQd69{P8 z5KyKDyWECd@3$2sVj&OKa;YGXL*mJICZUdZSbwN-9QLrG2*j12%^biH#IenHWlPg!wph8DLLJ~S6_3H-0 zN(CD<@5AZp-g4g(AW9)T8pnQ|K|emhj|6tJ#4}(A2COs?d9-(EvjMLfT=FKN|Es~} z%a6GLurviamOd=WVC`w{DEViJN{2$#SiMCu>#pTiBg>65C>F`muz)xztuR^Fgp|P- zZEkWhvvf0qo|nyW?#&}3$_D#9RR$I%XlZv(436;LOI1lI)ta=AV(Ftgi9O5B2vhJ? zlAtR!y7cgiX74E{jn=^+h0|&l73|*ME&AP8wae${tOgH7M20)`M#Q1p)1x|apBTY_ zFS<=;cb^UK9S2GcJYvj4^6V1LQD=Rezk4C;Xav7;3hA7?=z68| zWL_^v`-@MLK7M`R_$k;%=RfqiLz~cDD{NA-T0mgs!x<3YRZ@CnHxzN9|=s>3Utv^jRuXzHKA; zf@^uE)BNS>&AFVW{xrLm4MrC4)n=aFv5kr=b7jV^DPLa+4jP)}H%2RXM$HDglp5yu zWja?x=OG)z*WRXyC~nJ0u7?kUzWta?3S#%OfWFC=8vd3>CC+IfQ&ibe+mR<39L<&S z@rnKT6`teGmT!Bl3m7s@!!%8buBMGOnHy$RZ0_*M_U#kHSxqRwduJpif4=I>-X8+V z*Z7B~#e3nU5@p}t;~Xn;nWKE8349#+LX}r6wc;~AD`>IDQy`#cQ3&u$Qveo#543}B zOH|we!b4|xu?)N+lfHky4z$wzT7QV-U|UiHGr{t&VI28$6=Y55nviP&jdKuROJMqU z0DoCDV8aUva45(lP0D?W5k@drT<2aXxB$w$&bQW3iHsN%omtR{5qE3_rmykn6u34_ zMLXoZ-~!vuOh{FHrgBS=uc0i|3Hm_G)l(=$(F^GZvO1JQ*J{x4cLJ>z#J`rK0fMRE zPml!!&jl#lwME3m&FonX-A6|UB(M&tWjQpoH^70$)~(arGYeWLN6UETf!zC;J3wH_ zRBZ>jSsu`hka>OL#0g|gfF5jOZWG$o6YVLS1wr&2c@SzKM)o}=b3X_J(Bs$~*wiqy z!HHDHzA`rUSjGsf0HFNW)6~>Vt58%_gy^FabkAU;YB{^?(_;Q)4 z_pZ*;9jhG8dY0`YeCh!Va8Gb`&YwdGJjwJ&ghMS(|8yL)#XNMpe? zz^w>z7XgBL2e8cZ!4>@5B*zoT-N2Zc@}o!)-!A}s9znr;w1Wgo2#eIMR4XYiu8XkX zK%i0ovzN&|H=vVq9`KWEK7CSx?}R0z6ZT|H9JEFhtX`@G<_yd_%$Ms~Z=E4&1padl z?9MM|=Rh(jTy57i5{Auw(|%RRt~pV0-@$_&5Zs75bnroYn69j_-)#vX2h9a;w*Go_ zcXu~v#dP%bUqMa^`M{2uzW0PIEs)Y~LSF+!Du7s~?>fMIJTQ<-$i4=K$fW7Qrr6D! zeWfie7P`8(TJ51>T_Jb_p+5bl58k<5VB2ZYeN$Y_o&_84kF%TnF80AmyBmmUjpyR<}VW zGx~u~>CsB(XRXa#dIf(2D|QP0+1J{_k4ONF&2!2!(K*Be%#DxhylrFIM;3kx5wqX% z^U_`cI3Q(~J%iU&0?_@+=AU>c543_bZL$7*f6J4>OYJ*#bPhqbpmQTvC@sxex;Txk zM*HH8*M5HG*23PBAD2@+=6bN>2?1)HbL%k`3G)ScGp)1NBLXZ&$C=LXl`Y)ahZ2tQ z=MANnUKId}&7tCtY<2J7S69px_1>&i$jfiAiJUC09+6rtPH65NYg@g@-T3G=^mM#w ziq$yXL{vzri_9}1*n}_~JC-Tm=}CYc8Qpj1MNcs&_SnN2$dJy34dkNqEINY{DzYmm zzTfO}N;PM?i?!nN%1tnB*%~4wM~L`E9fs}2&=Uxz?HPLt3lPjn!=1!xqpMU{hA(9uHI z?p?TW!EGc&0u35nh%`Gu;lcax9rvU7UH27%djl$=IaCHq1;|?12hs4;^#7I~7 zq^0Gc2$2=u0!I>642u{#)uNnd_KdWv0w^-h6}y5|W2`A*PBI7vg1kI8bt5e!gI8YO z;>#CCy~!Ka)>0n_z%kwowjm6XjN7#&UxGv#E1J778GpaERTr=kLvJ$>P#RhK7x%n# z#KQgpEnw8{@1o0IG8=>(o;aQm*a5gqZ2Ce(D(q#5TsgsiC2FKA?3h$C^l_3Vi8}Rl zokOo*Y;SkjSo8@)AfS;xZ(m=TfuO?>a@X348cMaY{z7hs{MH=Dw$mN!Q!x^CN?aX` z|K4_lnIW-+x+oY@M$rKv^|)nW*b%t$ioW+Vh&UOmIVR@zlyY)@3J=&QDz(Lg#M?Ya7n@iMNd|_oF&y*yN|ruX3T;QD zC2X^NmM4>^^5*hN9KIE%hIIyB)yg$5eBVmiUEPO7wN~t2nuZQv=(tt<7g{=_R~EU7 zrG7;H}WRQZ$VUU@guXGkk5mGK;PQ4pO$j!Z(dSRd_<5KeUN(KzV7W(kx-z@cn68w1jL>P}9%K?oF?Jjn*^;^gy zA?QeOi-BenmF&j?Zc0Hq1MyBozmw;)uh8YDu7eTpp|fHc5n*^I%27J}oUR>?MGv z6=%x|WPMk@11TrsbFiD9J%?eIB}5s)LMt~Go)Y$ahaW=Tg520`Ujou(1^!>AVJ;ys z{j$4QtUAa_5E!hluTS;EB5Z8n>+N4_d-Qi9BKB!_;Splc(FADfrzGPk+4N&<&N-q1 zO?bgMiAgne=HMdlePZO4cWETSS7mTRNC#_pvpK$>Eb7~FlCn5lIi)bCuH*gQz#W`# zhCfa62+DZq1=?MC^H#VpJi!tn+|sePmei7J>uQT_JLUTqN?4Z`_RpcRnbW*F<&=t@ z+(#3utE)|46n`vso>R289-C|YA3Bp)?GyR-Cv!}YcD_}ly zQCWiGgzoL&avUf!nA;qD>O%>CqB>A$3110OcSq8F6;a!jzx$~}Y(A^GTGrxZ(AotN zd-HA0nxm1BQ`zYAH$b;w-@;V{h82?h#>Pwl&*OmwV#K(kQavFfBc3()dGm3{-e|W9 zHP0x(wCUb2@9K9`Q=m{nt9gop9?>Fr?OPMa;KzFmAh{wL zr*z7;+osqTUIDC21tR@l5FpV=Jti*ZWyY?8)acA)3120br z4qA8eKrv9@`Nw-O;Rc+2R+5@xX-YFx$#=|IWI z5bM{t#u2dg!QLDC3?PFS!FDs^eQFCT6z}gsLM^1|u{=poL1qoX15ziaZ~~XYV!a6I z`%}T90E$n>GA!nZDwyC9s}3oP7(@}( z3AQYPR(gozoco0K7ZG)6eFl)be!fMy$gF8NY(P@!n&6D`2EsM{$^d_qE-1>2Y#La? zU+MR07(@78Ru(*|fMBsPF;mPsroY_4ZdDgRuR6rc!l1$i0xabamNd{wcFt!w_2UqT zh_lNanqLSD^UV4nMIpeS^tH8*10M;_B@;el5K1b(!Uy6qH`&0GP!fXmMBYA=Pfp8}AM^sOI{tysnYPO%5* z1_C;Q{oHTz-HJG9l@3NX!Q>%<~ zKyqNEl;-J|K?8xd!9Z$Xo{AMLzS=EXzR~kiI4U+HEe_UaSy@?8hXEtfQl+!~0Ee2E z$q24^eqY!g_nn5YdFYLtrYo)pwhi|2iC!YcXicuJTiSn}OW@@dQF+RgCru!H7iah0 zu;4ddP8s8m$~Fz6Yg-#1Xoo5!m%+1HzZ*+Waf@>$c8$r-ODln!X_1y*mO*;FA4w&H zYZqa{E?2`FA5#-rkxwAV!i zs-!8EJiEhRbX`o$!S$gB*R1>q<;uds@?uz_m{o@@sZ%ZNGR9-JixV)u{ z>;o$sUuV!wfm@V_LdQL8f?H?5lDSW=uvvSVb3=3ytE3i`SmkDh4)Y%6$UB_`8sgB4{yh@T0lf+P*dO`>$ z5b_X`2+TM@F(hG3P!R>B#sMdVND332C#Mqz2#lwJPleF(0gi`!7!&6Zm}4L^2Xn>7 z21B;qzckMM{k#3{_r2eJexIlJ>kX(E6~2A885%-BT7y9*(Mu)lG0|Fe#EcvzIsBd) z9$lpeBN0;MIh6DeC09p2Y5X`TG2~|E+@s4e{=hKFQG1EVS+8VYCQNhjnTy9TkO&8W zNKw!&gh!JXdYiqZLx0%S?A442IchHSuL&?1{x&#RY~eb{5mxxuA&uR2d2bE@njy-v z^MB2F6KEN z>^xP^B*~NLS1tV^T zud+CKTXnlKOL4_I|DZhy8{=a?BDcM#XQDk&%!kBHY;rz_kUmna7ipR{(@S z7CcvQ1q(S)&K5z<2|XZ$+u-_SxGc7?fYDyOkDFqOArcB<7O)0>d0+sd=-bo9kIT%A z0?i8Z2A~OwGASQm+mOZ3i@5q zIDV%(Tq7<`#JE;*;7KH7rMh|D9zCP+?!uE7wWpG-`n}qZ`tc`qqI}_P;a`s2=OOy*D?32EcP2D;9g{1Z-uah}8 z<#*p@&M@#?_@ec&r9LcG_6|P^vA&W94eUO-OwjVrcK%YUbJ~_`O?~l#;XAv{l2)-; z{h=w2+wQ08M034zp4P2!rs#o~Eu@~=&@OtjLedv}Z&|$EvesXRik%;3M`iWQb}h@1 zl6#q<`VN0PiJGHP#wia<>iK*z&DVFi$3kPw*;{snEH+o|LQiSXvV&q1jU3&1R)s(@ zd`_j93|INP_od$0J@`Kq{PX(S7AN`N@7>-&zXZ&O6W3!AgS@Fi?kg=1=6J+VoSSmI zI-F+JO)4vwEM0}+s9Bw4lnrQQ=->K;Nji*}$hm-N1HMypVFBs`tZ~~+HsX|9j&xSK$$y>GaV$@qQ*zX*$ tW4*O!WLaO#$AaJdugT+WLEQ;c;{v7N=r7H~IYGoF0e(TgJn!i2e*qHq2P*&o literal 0 HcmV?d00001 diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index c65f31bf8b5..5e0305b99c4 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -10,7 +10,7 @@ import { e2eContainer } from "../inversify.config"; import { Driver } from "../driver/Driver"; -import { TYPES, CLASSES } from "../types"; +import { TYPES, CLASSES } from "../inversify.types"; import { DriverHelper } from "../utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; import { describe, after, test } from "mocha"; diff --git a/typescript-selenium/units/DriverHelperUnitTests.spec.ts b/typescript-selenium/units/DriverHelperUnitTests.spec.ts index 9b494ea89f8..c0c3ef6e7f5 100644 --- a/typescript-selenium/units/DriverHelperUnitTests.spec.ts +++ b/typescript-selenium/units/DriverHelperUnitTests.spec.ts @@ -10,7 +10,7 @@ import { e2eContainer } from "../inversify.config"; import { Driver } from "../driver/Driver"; -import { TYPES, CLASSES } from "../types"; +import { TYPES, CLASSES } from "../inversify.types"; import { DriverHelper } from "../utils/DriverHelper"; import { By, WebElementCondition, Condition } from "selenium-webdriver"; import { describe, after, Test } from "mocha"; diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 0a67d10b52f..6597211161b 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { Driver } from "../driver/Driver"; import { inject, injectable } from "inversify"; -import { TYPES } from "../types"; +import { TYPES } from "../inversify.types"; import { error } from 'selenium-webdriver'; import 'reflect-metadata'; import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement, WebElementCondition } from "selenium-webdriver"; diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 94924c75c19..5bfc18c081a 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -11,7 +11,7 @@ import { TestConstants } from '../../TestConstants'; import { injectable, inject } from 'inversify'; import { DriverHelper } from '../DriverHelper'; -import { CLASSES } from '../../types'; +import { CLASSES } from '../../inversify.types'; import 'reflect-metadata'; import * as rm from 'typed-rest-client/RestClient' From acfd277f6a96391c018e949f6f6bdaad235fdb83 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 16:31:23 +0300 Subject: [PATCH 30/57] Delete unit tests for 'DriverHelper' class Signed-off-by: Ihor Okhrimenko --- .../pagesource-wait_dashboard.html | 58 ----------- .../screenshot-wait_dashboard.png | Bin 108678 -> 0 bytes .../units/DriverHelperUnitTests.spec.ts | 95 ------------------ 3 files changed, 153 deletions(-) delete mode 100644 typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html delete mode 100644 typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png delete mode 100644 typescript-selenium/units/DriverHelperUnitTests.spec.ts diff --git a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html deleted file mode 100644 index 132a63c7202..00000000000 --- a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/pagesource-wait_dashboard.html +++ /dev/null @@ -1,58 +0,0 @@ -Eclipse Che | New Workspace
-
New Workspace
- -
Name
RAM
Blank
Default Blank Stack.
Ubuntu
JDK
Maven
Tomcat
2 GB
Java
Default Java Stack with JDK 8, Maven and Tomcat.
Ubuntu
JDK
Maven
Tomcat
2 GB
.NET
Default .NET 2.0.0 Stack with .NET Core SDK
Ubuntu
Dotnet
2 GB
Android
Default Android Stack with Java 1.8 and Android SDK
Centos
JDK
Maven
Android API
2 GB
C++
Default C++ Stack with C++, gcc 4.8.4, GNU Make 3.81.
Ubuntu
JDK
G++
GCC
Make
2 GB
Che 7
Workspace.next sidecars and Theia as IDE
Centos
OpenJDK
NodeJS
NPM
0.5 GB
Che 7 Dev
Workspace.next sidecars and Theia as IDE with the tooling for plugin development
Centos
OpenJDK
NodeJS
NPM
0.5 GB
Che 7 Theia dev
Development Theia as IDE, che-theia components, Theia plugins or plugin api.
Centos
NodeJS
NPM
0.5 GB
Eclipse Che
Utilities to build Che in Che with JDK 8 and Maven.
JDK
Maven
7 GB
Go
Default Go Stack with Go 1.10.2
Ubuntu
Go
2 GB
Go with Theia IDE
Default stack with Go 1.12.4 and Theia IDE
Debian
Go
0.5 GB
Java and MySQL with Theia IDE on Kubernetes
Multi Container Workspace - Java with MySQL database in Theia IDE
web/dev
0.5 GB
web/mysql
0.3 GB
Java Gradle
Java Stack with OpenJDK 11 and Gradle 5.2.1
Debian
OpenJDK
Gradle
0.5 GB
Java Maven
Default Java Stack with OpenJDK 11 and Maven 3.6
Debian
OpenJDK
Maven
0.5 GB
Node
Default Node Stack with Node 8
Ubuntu
NodeJS
NPM
Typerscript
2 GB
PHP
Default PHP Stack with PHP 7.0, most popular extensions.
Ubuntu
PHP
Composer
MySQL
Zend Debugger
Apache
2 GB
Python
Default Python Stack with Python 3.5.1, pip 8.1.1.
Ubuntu
Python
PIP
2 GB
Python with Theia IDE
Default stack with Python 3.7 and Theia IDE
CentOS
Python
PIP
0.5 GB
Rails
Default Rails Stack with Ruby 2.4.4, Rails 5.0.1, Bundler 1.16.2.
Ubuntu
Ruby
Rails
Bundler
2 GB
dev-machine
eclipse/ubuntu_jdk8
-
- - - -
- - GB -
- - -
- -
\ No newline at end of file diff --git a/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png b/typescript-selenium/report/E2E_Login_and_wait_dashboard_wait_dashboard/screenshot-wait_dashboard.png deleted file mode 100644 index 259db617f46725947f1565321b4c1aab3b339b58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108678 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 diff --git a/typescript-selenium/units/DriverHelperUnitTests.spec.ts b/typescript-selenium/units/DriverHelperUnitTests.spec.ts deleted file mode 100644 index c0c3ef6e7f5..00000000000 --- a/typescript-selenium/units/DriverHelperUnitTests.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -/********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - **********************************************************************/ - -import { e2eContainer } from "../inversify.config"; -import { Driver } from "../driver/Driver"; -import { TYPES, CLASSES } from "../inversify.types"; -import { DriverHelper } from "../utils/DriverHelper"; -import { By, WebElementCondition, Condition } from "selenium-webdriver"; -import { describe, after, Test } from "mocha"; -import { LoginPage } from "../pageobjects/login/LoginPage"; -import { Dashboard } from "../pageobjects/dashboard/Dashboard"; -import { expect, assert } from 'chai' -import { Workspaces } from "../pageobjects/dashboard/Workspaces"; - -Test - -const driver: Driver = e2eContainer.get(TYPES.Driver); -const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); -const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); -const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard) -const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces) - -suite("Test of 'DriverHelper' methods", async () => { - test("login", async () => { - await loginPage.login() - }) - - test("waitAllVisibility", async () => { - await driverHelper.waitAllVisibility([By.css("#dashboard-item"), By.css("#workspaces-item"), By.css("#stacks-item")], 20000) - }) - - test("isVisible", async () => { - const isVisible = await driverHelper.isVisible(By.css("#dashboard-item")) - expect(isVisible).to.be.true - }) - - test("isVisible", async () => { - const isVisible = await driverHelper.isVisible(By.css("#dashboard-item aaaa")) - expect(isVisible).to.be.false - }) - - test("waitVisibilityBoolean", async () => { - const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item")) - expect(isVisible).to.be.true - }) - - test("waitVisibilityBoolean", async () => { - const isVisible = await driverHelper.waitVisibilityBoolean(By.css("#dashboard-item aaaa")) - expect(isVisible).to.be.false - }) - - test("waitDisappearanceBoolean", async () => { - const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item")) - expect(isDisappeared).to.be.false - }) - - test("waitDisappearanceBoolean", async () => { - const isDisappeared = await driverHelper.waitDisappearanceBoolean(By.css("#dashboard-item aaaa")) - expect(isDisappeared).to.be.true - }) - - test("waitDisappearance", async () => { - await driverHelper.waitDisappearance(By.css("#dashboard-item aaaa")) - }) - - - test("click dashboard button", async () => { - await driverHelper.waitAndClick(By.css("#dashboard-item")) - }) - - test("waitAllDisappearance", async () => { - await driverHelper.waitAllDisappearance([By.css("#dashboard-item aaa"), By.css("#workspaces-item aaa"), By.css("#stacks-item aaa")], 5, 1000) - }) - - test("getElementAttribute", async () => { - const attributValue: string = await driverHelper.waitAndGetElementAttribute(By.css("#dashboard-item"), 'id') - expect(attributValue).to.be.equal('dashboard-item') - }) - - test("waitAttributeValue", async ()=>{ - await driverHelper.waitAttributeValue(By.css("#dashboard-item"), 'id', 'dashboard-item') - }) - -}) - -suiteTeardown("close browser", async () => { - driver.get().quit() -}) From fd8573c4a73156fe2644a90c41061dcc800da5af Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 16:36:29 +0300 Subject: [PATCH 31/57] Enable skipped tests execution Signed-off-by: Ihor Okhrimenko --- typescript-selenium/tests/HappyPath.spec.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 5e0305b99c4..14220e2b841 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -58,11 +58,10 @@ suite("E2E", async () => { await dashboard.waitLoader() await dashboard.waitLoaderDisappearance() await dashboard.waitPage() - expect(false).to.be.true }) }) - suite.skip("Create workspace and open IDE", async () => { + suite("Create workspace and open IDE", async () => { test("Go to 'New Workspace' page", async () => { await dashboard.clickWorkspacesButton() @@ -109,7 +108,7 @@ suite("E2E", async () => { }) }) - suite.skip("Work with IDE", async () => { + suite("Work with IDE", async () => { let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; let tabTitle: string = "HelloWorld.java"; let filePath: string = `${fileFolderPath}/${tabTitle}` @@ -140,7 +139,7 @@ suite("E2E", async () => { }) - suite.skip("Stop and remove workspace", async () => { + suite("Stop and remove workspace", async () => { test("Stop workspace", async () => { await dashboard.openDashboard() await dashboard.clickWorkspacesButton() @@ -164,7 +163,3 @@ suite("E2E", async () => { }) }) - -// suiteTeardown("close browser", async () => { -// driver.get().quit() -// }) From 571ee36ae5300a8ac2b9402d0cddb11b8c7e4072 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 16:43:41 +0300 Subject: [PATCH 32/57] Remove redundant npm command Signed-off-by: Ihor Okhrimenko --- typescript-selenium/README.md | 6 ++++++ typescript-selenium/package.json | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md index e69de29bb2d..e5962a8c178 100644 --- a/typescript-selenium/README.md +++ b/typescript-selenium/README.md @@ -0,0 +1,6 @@ +### Requirements + - node + - Chrome browser + +### Launch + - npm install && npm test \ No newline at end of file diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index 83148a59539..b1b90acb311 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -4,8 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --opts mocha.opts", - "units": "mocha --require ts-node/register --timeout 1200000 -u tdd --spec units/*.spec.ts" + "test": "mocha --opts mocha.opts" }, "author": "Ihor Okhrimenko (iokhrime@redhat.com)", "license": "ISC", From 61d58bde82d977fc6d2b59479060c6e58767133f Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 16:58:16 +0300 Subject: [PATCH 33/57] Add 'README' description Signed-off-by: Ihor Okhrimenko --- typescript-selenium/README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md index e5962a8c178..e540d6c3203 100644 --- a/typescript-selenium/README.md +++ b/typescript-selenium/README.md @@ -1,6 +1,25 @@ -### Requirements +# Requirements - node - Chrome browser -### Launch - - npm install && npm test \ No newline at end of file +# Default launch + - npm install && npm test + +# Custom launch +**Use environment variables (check default values in 'TestConstants.ts' file):** +### application url + - TS_SELENIUM_BASE_URL *( string )* + +### headless launch + - TS_SELENIUM_HEADLESS *( boolean )* + +### timeouts + - TS_SELENIUM_DEFAULT_ATTEMPTS *( number )* + - TS_SELENIUM_DEFAULT_POLLING *( number )* + - TS_SELENIUM_DEFAULT_TIMEOUT *( number )* + + - TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT *( number )* + - TS_SELENIUM_LOAD_PAGE_TIMEOUT *( number )* + - TS_SELENIUM_START_WORKSPACE_TIMEOUT *( number )* + - TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS *( number )* + - TS_SELENIUM_WORKSPACE_STATUS_POLLING *( number )* From 1d2673ec44679f151bc031a4ae4a075b3c65529b Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 17:09:48 +0300 Subject: [PATCH 34/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/ChromeDriver.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts index e11b711b42e..7abe50b3f6c 100644 --- a/typescript-selenium/driver/ChromeDriver.ts +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -21,20 +21,17 @@ export class ChromeDriver implements Driver { constructor() { const isHeadless: boolean = TestConstants.TS_SELENIUM_HEADLESS; - + let options: Options = new Options().addArguments('--no-sandbox') if (isHeadless) { - this.driver = new Builder() - .forBrowser('chrome') - .setChromeOptions(new Options().addArguments('--no-sandbox').addArguments('headless')) - .build(); - } else { - this.driver = new Builder() - .forBrowser('chrome') - .setChromeOptions(new Options().addArguments('--no-sandbox')) - .build(); + options = options.addArguments('headless') } + this.driver = new Builder() + .forBrowser('chrome') + .setChromeOptions(options) + .build(); + this.driver .manage() .window() From 5aab39a1f294f3f4ebfccce077052bf496ea6341 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 17:36:52 +0300 Subject: [PATCH 35/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index f9aa233acd1..68af52e51f9 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -10,7 +10,7 @@ function readEnvAndSetValue(envProperty: any, defaultValue: string | number | boolean): any { const propertyValue = envProperty - if (propertyValue === undefined || propertyValue === null) { + if (!propertyValue) { return defaultValue; } From 8bacf5e45a93573843130a93e0643e6a02c93e7f Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Tue, 23 Apr 2019 17:43:48 +0300 Subject: [PATCH 36/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 2 +- typescript-selenium/driver/CheReporter.ts | 2 +- typescript-selenium/driver/ChromeDriver.ts | 2 +- typescript-selenium/driver/Driver.ts | 2 +- typescript-selenium/inversify.config.ts | 2 +- typescript-selenium/inversify.types.ts | 2 +- typescript-selenium/pageobjects/dashboard/Dashboard.ts | 2 +- typescript-selenium/pageobjects/dashboard/NewWorkspace.ts | 2 +- typescript-selenium/pageobjects/dashboard/Workspaces.ts | 2 +- .../pageobjects/dashboard/workspace-details/WorkspaceDetails.ts | 2 +- .../dashboard/workspace-details/WorkspaceDetailsPlugins.ts | 2 +- typescript-selenium/pageobjects/ide/Editor.ts | 2 +- typescript-selenium/pageobjects/ide/Ide.ts | 2 +- typescript-selenium/pageobjects/ide/ProjectTree.ts | 2 +- typescript-selenium/pageobjects/login/LoginPage.ts | 2 +- typescript-selenium/pageobjects/login/SingleUserLoginPage.ts | 2 +- typescript-selenium/tests/HappyPath.spec.ts | 2 +- typescript-selenium/utils/DriverHelper.ts | 2 +- typescript-selenium/utils/NameGenerator.ts | 2 +- typescript-selenium/utils/workspace/TestWorkspaceUtil.ts | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 68af52e51f9..0ed0a90d60e 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index a16ac70df21..428b2805e3b 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts index 7abe50b3f6c..f01548142fc 100644 --- a/typescript-selenium/driver/ChromeDriver.ts +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/driver/Driver.ts b/typescript-selenium/driver/Driver.ts index ce159f6535a..d5ec431a51c 100644 --- a/typescript-selenium/driver/Driver.ts +++ b/typescript-selenium/driver/Driver.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 4d960a16b4a..12b0a457c05 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/inversify.types.ts b/typescript-selenium/inversify.types.ts index 5e55a388ca7..01ab190b956 100644 --- a/typescript-selenium/inversify.types.ts +++ b/typescript-selenium/inversify.types.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 99634aac406..7b2e9e2890f 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index e22db24d82b..63d7dcee2c9 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/typescript-selenium/pageobjects/dashboard/Workspaces.ts index ed3cee8fb9c..059ccbbf02e 100644 --- a/typescript-selenium/pageobjects/dashboard/Workspaces.ts +++ b/typescript-selenium/pageobjects/dashboard/Workspaces.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index 4eaa0b3f6a2..64f62c1cccd 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts index 19ab3b208a6..22e68b3832b 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index 5fa2e3284d8..50458f794b8 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index 2195b5d173e..539cf7061f4 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index 1f8c11e3cd3..36087aa46cc 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/login/LoginPage.ts b/typescript-selenium/pageobjects/login/LoginPage.ts index a06e80a55fa..0e38cb562f3 100644 --- a/typescript-selenium/pageobjects/login/LoginPage.ts +++ b/typescript-selenium/pageobjects/login/LoginPage.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index f7c52e14a24..374eae7f6d2 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 14220e2b841..a9bd250d9a3 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 6597211161b..2a673e30a1c 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/utils/NameGenerator.ts b/typescript-selenium/utils/NameGenerator.ts index 8c8d099f2c1..c64b5325b03 100644 --- a/typescript-selenium/utils/NameGenerator.ts +++ b/typescript-selenium/utils/NameGenerator.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 5bfc18c081a..b44eae0fc75 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2019 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 From 37e47261a8f2a0a8355a6c8478fd727e4b97da2a Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Wed, 24 Apr 2019 11:35:51 +0300 Subject: [PATCH 37/57] Do changes according to 'Codacy/PR Quality Review' result Signed-off-by: Ihor Okhrimenko --- typescript-selenium/README.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md index e540d6c3203..619a237b01b 100644 --- a/typescript-selenium/README.md +++ b/typescript-selenium/README.md @@ -1,25 +1,24 @@ -# Requirements - - node - - Chrome browser +## Requirements +- node +- "Chrome browser" -# Default launch - - npm install && npm test +## Default launch +- npm install && npm test -# Custom launch +## Custom launch **Use environment variables (check default values in 'TestConstants.ts' file):** ### application url - - TS_SELENIUM_BASE_URL *( string )* +- TS_SELENIUM_BASE_URL *( string )* ### headless launch - - TS_SELENIUM_HEADLESS *( boolean )* +- TS_SELENIUM_HEADLESS *( boolean )* ### timeouts - - TS_SELENIUM_DEFAULT_ATTEMPTS *( number )* - - TS_SELENIUM_DEFAULT_POLLING *( number )* - - TS_SELENIUM_DEFAULT_TIMEOUT *( number )* - - - TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT *( number )* - - TS_SELENIUM_LOAD_PAGE_TIMEOUT *( number )* - - TS_SELENIUM_START_WORKSPACE_TIMEOUT *( number )* - - TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS *( number )* - - TS_SELENIUM_WORKSPACE_STATUS_POLLING *( number )* +- TS_SELENIUM_DEFAULT_ATTEMPTS *( number )* +- TS_SELENIUM_DEFAULT_POLLING *( number )* +- TS_SELENIUM_DEFAULT_TIMEOUT *( number )* +- TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT *( number )* +- TS_SELENIUM_LOAD_PAGE_TIMEOUT *( number )* +- TS_SELENIUM_START_WORKSPACE_TIMEOUT *( number )* +- TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS *( number )* +- TS_SELENIUM_WORKSPACE_STATUS_POLLING *( number )* From 150145c34543b01cdc628ee30652e70f2b35217b Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 25 Apr 2019 10:22:32 +0300 Subject: [PATCH 38/57] Add editor methods Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 4 +- typescript-selenium/pageobjects/ide/Editor.ts | 44 ++++++++++-- typescript-selenium/tests/HappyPath.spec.ts | 29 ++++++-- typescript-selenium/utils/DriverHelper.ts | 69 ++++++++++++++++++- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 428b2805e3b..74391ecf61f 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -46,10 +46,10 @@ class CheReporter extends mocha.reporters.Spec { runner.on('end', async function (test: mocha.Test) { //ensure that fired events done - await driver.get().sleep(5000).catch(err => {throw err}) + // await driver.get().sleep(5000).catch(err => {throw err}) //close driver - await driver.get().quit().catch(err => { throw err }) + // await driver.get().quit().catch(err => { throw err }) }) diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index 50458f794b8..14d6676819b 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -12,7 +12,7 @@ import { injectable, inject } from 'inversify'; import { DriverHelper } from '../../utils/DriverHelper'; import { CLASSES } from '../../inversify.types'; import { TestConstants } from '../../TestConstants'; -import { By } from 'selenium-webdriver'; +import { By, WebElement } from 'selenium-webdriver'; @injectable() export class Editor { @@ -24,13 +24,21 @@ export class Editor { private static readonly EDITOR_LINES_CSS: string = ".lines-content .view-line"; private static readonly EDITOR_BODY_CSS: string = "#theia-main-content-panel .lines-content"; - private static readonly SUGGESTION_WIDGET_BODY_CSS: string = "div[widgetId='editor.widget.suggestWidget']" - private static readonly SUGGESTION_WIDGET_ROW_CSS: string = "div[widgetId='editor.widget.suggestWidget'] .monaco-list-row"; + private static readonly EDITOR_LINES_XPATH: string = "//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line']"; + private static readonly SUGGESTION_WIDGET_BODY_CSS: string = "div[widgetId='editor.widget.suggestWidget']"; private getEditorLineXpathLocator(lineNumber: number): string { return `(//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line'])[${lineNumber}]` } + private getSuggestionLineXpathLocator(suggestionText: string): string { + return `//div[@widgetId='editor.widget.suggestWidget']//*[@class='monaco-list-row' and text()='${suggestionText}']` + } + + private getTabXpathLocator(tabTitle: string): string { + return `//li[contains(@class, 'p-TabBar-tab')]//div[text()='${tabTitle}']`; + } + async waitSuggestionContainer(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(Editor.SUGGESTION_WIDGET_BODY_CSS), timeout) } @@ -39,8 +47,12 @@ export class Editor { await this.driverHelper.waitDisappearance(By.css(Editor.SUGGESTION_WIDGET_BODY_CSS), attempts, polling) } - private getTabXpathLocator(tabTitle: string): string { - return `//li[contains(@class, 'p-TabBar-tab')]//div[text()='${tabTitle}']`; + async waitSuggestion(suggestionText: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.xpath(this.getSuggestionLineXpathLocator(suggestionText)), timeout) + } + + async clickOnSuggestion(suggestionText: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.driverHelper.waitAndClick(By.xpath(this.getSuggestionLineXpathLocator(suggestionText)), timeout) } async waitTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { @@ -82,4 +94,26 @@ export class Editor { await this.waitEditorOpened(timeout); } + async getLineText(lineNumber: number, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise{ + const lineXpathLocator: By = By.xpath(this.getEditorLineXpathLocator(lineNumber)) + + const lineText = await this.driverHelper.waitAndGetText(lineXpathLocator, timeout) + return lineText + } + + async getEditorText(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise{ + const lines: Array = await this.driverHelper.waitAllPresence(By.xpath(Editor.EDITOR_LINES_XPATH), timeout) + const linesCapacity: number = lines.length + + let editorText = ""; + + for(let i = 1 ; i <= linesCapacity ; i++){ + await this.driverHelper.scrollTo(By.xpath(this.getEditorLineXpathLocator(i)), timeout) + const lineText: string = await this.getLineText(i, timeout) + editorText = editorText + lineText + "\n" + } + + return editorText; + } + } diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index a9bd250d9a3..4f6830d3f66 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -51,17 +51,36 @@ suite("E2E", async () => { suite("Login and wait dashboard", async () => { test("login", async () => { - await loginPage.login() + // await loginPage.login() + + let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; + let tabTitle: string = "HelloWorld.java"; + let filePath: string = `${fileFolderPath}/${tabTitle}` + + await driverHelper.navigateTo("http://che-che.192.168.99.100.nip.io/dashboard/#/ide/che/wksp-x91g"); + await ide.waitAndSwitchToIdeFrame() + await ide.waitIde() + + await projectTree.openProjectTreeContainer(); + await projectTree.waitProjectTreeContainer(); + await projectTree.waitProjectImported(sampleName, "src") + + await projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); + await editor.waitEditorAvailable(tabTitle); + await editor.clickOnTab(tabTitle); + await editor.waitEditorAvailable(tabTitle); + + }) - test("wait dashboard", async () => { + test.skip("wait dashboard", async () => { await dashboard.waitLoader() await dashboard.waitLoaderDisappearance() await dashboard.waitPage() }) }) - suite("Create workspace and open IDE", async () => { + suite.skip("Create workspace and open IDE", async () => { test("Go to 'New Workspace' page", async () => { await dashboard.clickWorkspacesButton() @@ -108,7 +127,7 @@ suite("E2E", async () => { }) }) - suite("Work with IDE", async () => { + suite.skip("Work with IDE", async () => { let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; let tabTitle: string = "HelloWorld.java"; let filePath: string = `${fileFolderPath}/${tabTitle}` @@ -139,7 +158,7 @@ suite("E2E", async () => { }) - suite("Stop and remove workspace", async () => { + suite.skip("Stop and remove workspace", async () => { test("Stop workspace", async () => { await dashboard.openDashboard() await dashboard.clickWorkspacesButton() diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 2a673e30a1c..130a5e379ed 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -10,7 +10,7 @@ import { Driver } from "../driver/Driver"; import { inject, injectable } from "inversify"; import { TYPES } from "../inversify.types"; -import { error } from 'selenium-webdriver'; +import { error, ActionSequence } from 'selenium-webdriver'; import 'reflect-metadata'; import { WebElementPromise, ThenableWebDriver, By, promise, until, WebElement, WebElementCondition } from "selenium-webdriver"; import { TestConstants } from "../TestConstants"; @@ -19,11 +19,13 @@ import { TestConstants } from "../TestConstants"; @injectable() export class DriverHelper { private readonly driver: ThenableWebDriver; + private readonly action: ActionSequence; constructor( @inject(TYPES.Driver) driver: Driver ) { this.driver = driver.get(); + this.action = new ActionSequence(this.driver); } public async isVisible(locator: By): Promise { @@ -91,6 +93,48 @@ export class DriverHelper { throw new Error(`Exceeded maximum visibility checkings attempts, problems with 'StaleElementReferenceError' of '${elementLocator}' element`) } + public async waitPresence(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING + + for (let i = 0; i < attempts; i++) { + try { + const webElement: WebElement = await this.driver.wait(until.elementLocated(elementLocator), timeout) + return webElement + } catch (err) { + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err; + } + } + + throw new Error(`Exceeded maximum presence checkings attempts, problems with 'StaleElementReferenceError' of '${elementLocator}' element`) + } + + public async waitAllPresence(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise> { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING + + for (let i = 0; i < attempts; i++) { + try { + const webElements: Array = await this.driver.wait(until.elementsLocated(elementLocator), timeout) + return webElements + } catch (err) { + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err; + } + } + + throw new Error(`Exceeded maximum presence checkings attempts, problems with 'StaleElementReferenceError' of '${elementLocator}' element`) + } + public async waitAllVisibility(locators: Array, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { for (const elementLocator of locators) { await this.waitVisibility(elementLocator, timeout) @@ -271,4 +315,27 @@ export class DriverHelper { await this.driver.navigate().to(url) } + public async scrollTo(elementLocator: By, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING + + for (let i = 0; i < attempts; i++) { + const element: WebElement = await this.waitPresence(elementLocator, timeout); + + try { + this.action.mouseMove(element).perform() + } catch (err) { + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling) + continue; + } + + throw err + } + } + + throw new Error(`Exceeded maximum mouse move attempts, for the '${elementLocator}' element`) + } + + } From db6ba5f35922c635f9cad46879b8174ef60bab12 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 25 Apr 2019 14:26:35 +0300 Subject: [PATCH 39/57] Add methods to the 'Editor' pageobject Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 8 ++--- typescript-selenium/pageobjects/ide/Editor.ts | 9 +++--- typescript-selenium/tests/HappyPath.spec.ts | 29 ++++--------------- typescript-selenium/utils/DriverHelper.ts | 10 ++++--- 4 files changed, 19 insertions(+), 37 deletions(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 74391ecf61f..e511912ae2d 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -45,11 +45,11 @@ class CheReporter extends mocha.reporters.Spec { }) runner.on('end', async function (test: mocha.Test) { - //ensure that fired events done - // await driver.get().sleep(5000).catch(err => {throw err}) + // ensure that fired events done + await driver.get().sleep(5000).catch(err => {throw err}) - //close driver - // await driver.get().quit().catch(err => { throw err }) + // close driver + await driver.get().quit().catch(err => { throw err }) }) diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index 14d6676819b..26f3ecf9d9e 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -12,7 +12,7 @@ import { injectable, inject } from 'inversify'; import { DriverHelper } from '../../utils/DriverHelper'; import { CLASSES } from '../../inversify.types'; import { TestConstants } from '../../TestConstants'; -import { By, WebElement } from 'selenium-webdriver'; +import { By, WebElement, Key } from 'selenium-webdriver'; @injectable() export class Editor { @@ -22,7 +22,6 @@ export class Editor { this.driverHelper = driverHelper } - private static readonly EDITOR_LINES_CSS: string = ".lines-content .view-line"; private static readonly EDITOR_BODY_CSS: string = "#theia-main-content-panel .lines-content"; private static readonly EDITOR_LINES_XPATH: string = "//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line']"; private static readonly SUGGESTION_WIDGET_BODY_CSS: string = "div[widgetId='editor.widget.suggestWidget']"; @@ -94,20 +93,20 @@ export class Editor { await this.waitEditorOpened(timeout); } - async getLineText(lineNumber: number, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise{ + async getLineText(lineNumber: number, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { const lineXpathLocator: By = By.xpath(this.getEditorLineXpathLocator(lineNumber)) const lineText = await this.driverHelper.waitAndGetText(lineXpathLocator, timeout) return lineText } - async getEditorText(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise{ + async getEditorText(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { const lines: Array = await this.driverHelper.waitAllPresence(By.xpath(Editor.EDITOR_LINES_XPATH), timeout) const linesCapacity: number = lines.length let editorText = ""; - for(let i = 1 ; i <= linesCapacity ; i++){ + for (let i = 1; i <= linesCapacity; i++) { await this.driverHelper.scrollTo(By.xpath(this.getEditorLineXpathLocator(i)), timeout) const lineText: string = await this.getLineText(i, timeout) editorText = editorText + lineText + "\n" diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 4f6830d3f66..a9bd250d9a3 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -51,36 +51,17 @@ suite("E2E", async () => { suite("Login and wait dashboard", async () => { test("login", async () => { - // await loginPage.login() - - let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; - let tabTitle: string = "HelloWorld.java"; - let filePath: string = `${fileFolderPath}/${tabTitle}` - - await driverHelper.navigateTo("http://che-che.192.168.99.100.nip.io/dashboard/#/ide/che/wksp-x91g"); - await ide.waitAndSwitchToIdeFrame() - await ide.waitIde() - - await projectTree.openProjectTreeContainer(); - await projectTree.waitProjectTreeContainer(); - await projectTree.waitProjectImported(sampleName, "src") - - await projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); - await editor.waitEditorAvailable(tabTitle); - await editor.clickOnTab(tabTitle); - await editor.waitEditorAvailable(tabTitle); - - + await loginPage.login() }) - test.skip("wait dashboard", async () => { + test("wait dashboard", async () => { await dashboard.waitLoader() await dashboard.waitLoaderDisappearance() await dashboard.waitPage() }) }) - suite.skip("Create workspace and open IDE", async () => { + suite("Create workspace and open IDE", async () => { test("Go to 'New Workspace' page", async () => { await dashboard.clickWorkspacesButton() @@ -127,7 +108,7 @@ suite("E2E", async () => { }) }) - suite.skip("Work with IDE", async () => { + suite("Work with IDE", async () => { let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; let tabTitle: string = "HelloWorld.java"; let filePath: string = `${fileFolderPath}/${tabTitle}` @@ -158,7 +139,7 @@ suite("E2E", async () => { }) - suite.skip("Stop and remove workspace", async () => { + suite("Stop and remove workspace", async () => { test("Stop workspace", async () => { await dashboard.openDashboard() await dashboard.clickWorkspacesButton() diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index 130a5e379ed..e8fdb83c0ae 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -19,13 +19,15 @@ import { TestConstants } from "../TestConstants"; @injectable() export class DriverHelper { private readonly driver: ThenableWebDriver; - private readonly action: ActionSequence; constructor( @inject(TYPES.Driver) driver: Driver ) { this.driver = driver.get(); - this.action = new ActionSequence(this.driver); + } + + public getAction(): ActionSequence { + return this.driver.actions() } public async isVisible(locator: By): Promise { @@ -323,7 +325,8 @@ export class DriverHelper { const element: WebElement = await this.waitPresence(elementLocator, timeout); try { - this.action.mouseMove(element).perform() + await this.getAction().mouseMove(element).perform() + return } catch (err) { if (err instanceof error.StaleElementReferenceError) { await this.wait(polling) @@ -337,5 +340,4 @@ export class DriverHelper { throw new Error(`Exceeded maximum mouse move attempts, for the '${elementLocator}' element`) } - } From be1d8413f2918c3e0644e923b4efc21e81230ba6 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 25 Apr 2019 14:33:51 +0300 Subject: [PATCH 40/57] Add switching to frame during project import wait Signed-off-by: Ihor Okhrimenko --- typescript-selenium/pageobjects/ide/ProjectTree.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index 36087aa46cc..eedc8870c84 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -165,9 +165,10 @@ export class ProjectTree { if (!isProjectFolderVisible) { await this.driverHelper.reloadPage() + await this.driverHelper.wait(triesPolling) + await this.ide.waitAndSwitchToIdeFrame() await this.ide.waitIde() await this.openProjectTreeContainer() - await this.driverHelper.wait(triesPolling) continue; } @@ -178,9 +179,10 @@ export class ProjectTree { if (!isProjectFolderVisible) { await this.driverHelper.reloadPage() + await this.driverHelper.wait(triesPolling) + await this.ide.waitAndSwitchToIdeFrame() await this.ide.waitIde() await this.openProjectTreeContainer() - await this.driverHelper.wait(triesPolling) continue; } From 29d98565825d63e485d365f263d94b3b08289266 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 25 Apr 2019 17:12:36 +0300 Subject: [PATCH 41/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- .gitignore | 3 ++- typescript-selenium/README.md | 4 ++-- typescript-selenium/TestConstants.ts | 7 ++++--- typescript-selenium/driver/CheReporter.ts | 8 ++++---- typescript-selenium/driver/ChromeDriver.ts | 2 +- .../pageobjects/dashboard/NewWorkspace.ts | 2 +- .../pageobjects/ide/ProjectTree.ts | 4 ++-- .../pageobjects/login/SingleUserLoginPage.ts | 4 +--- typescript-selenium/tests/HappyPath.spec.ts | 4 ++-- typescript-selenium/tsconfig.json | 2 +- typescript-selenium/utils/DriverHelper.ts | 4 ++-- typescript-selenium/utils/NameGenerator.ts | 8 ++++---- .../utils/workspace/TestWorkspaceUtil.ts | 15 ++++++++------- 13 files changed, 34 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index e60ad43338b..e716eb313f5 100644 --- a/.gitignore +++ b/.gitignore @@ -99,7 +99,8 @@ requirements.lock */cypress-tests/dist -# typescript-selenium module +# typescript-selenium module # +############################## */typescript-selenium/dist/ */typescript-selenium/node_modules/ */typescript-selenium/report/ diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md index 619a237b01b..e657e5d4bc3 100644 --- a/typescript-selenium/README.md +++ b/typescript-selenium/README.md @@ -1,6 +1,6 @@ ## Requirements -- node -- "Chrome browser" +- node 8.x +- "Chrome" browser 69.x or later ## Default launch - npm install && npm test diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 0ed0a90d60e..01f2a8ed97c 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -17,8 +17,6 @@ function readEnvAndSetValue(envProperty: any, defaultValue: string | number | bo return propertyValue } - - export const TestConstants = { TS_SELENIUM_HEADLESS: readEnvAndSetValue(process.env.TS_SELENIUM_HEADLESS, false), @@ -33,5 +31,8 @@ export const TestConstants = { TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: readEnvAndSetValue(process.env.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS, 90), TS_SELENIUM_WORKSPACE_STATUS_POLLING: readEnvAndSetValue(process.env.TS_SELENIUM_WORKSPACE_STATUS_POLLING, 10000), - TS_SELENIUM_BASE_URL: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, "http://che-che.192.168.99.100.nip.io") + TS_SELENIUM_BASE_URL: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, "http://che-che.192.168.99.100.nip.io"), + + TS_SELENIUM_RESOLUTION_WIDTH: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, 1920), + TS_SELENIUM_RESOLUTION_HEIGHT: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, 1080) } diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index e511912ae2d..44041e930f2 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -63,7 +63,7 @@ class CheReporter extends mocha.reporters.Spec { const screenshotFileName: string = `${testReportDirPath}/screenshot-${testTitle}.png` const pageSourceFileName: string = `${testReportDirPath}/pagesource-${testTitle}.html` - //create reporter dir if not exist + // create reporter dir if not exist await fs.exists(reportDirPath, async isDirExist => { if (!isDirExist) { await fs.mkdir(reportDirPath, err => { @@ -74,7 +74,7 @@ class CheReporter extends mocha.reporters.Spec { } }) - //create dir for collected data if not exist + // create dir for collected data if not exist await fs.exists(testReportDirPath, async isDirExist => { if (!isDirExist) { await fs.mkdir(testReportDirPath, err => { @@ -85,13 +85,13 @@ class CheReporter extends mocha.reporters.Spec { } }) - //take screenshot and write to file + // take screenshot and write to file const screenshot: string = await driver.get().takeScreenshot().catch(err => { throw err }); const screenshotStream = fs.createWriteStream(screenshotFileName) screenshotStream.write(new Buffer(screenshot, 'base64')) screenshotStream.end() - //take pagesource and write to file + // take pagesource and write to file const pageSource: string = await driver.get().getPageSource().catch(err => { throw err }) const pageSourceStream = fs.createWriteStream(pageSourceFileName) pageSourceStream.write(new Buffer(pageSource)) diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts index f01548142fc..01c18659441 100644 --- a/typescript-selenium/driver/ChromeDriver.ts +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -35,7 +35,7 @@ export class ChromeDriver implements Driver { this.driver .manage() .window() - .setSize(1920, 1080) + .setSize(TestConstants.TS_SELENIUM_RESOLUTION_WIDTH, TestConstants.TS_SELENIUM_RESOLUTION_HEIGHT) } get(): ThenableWebDriver { diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index 63d7dcee2c9..da186b97b46 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -67,7 +67,7 @@ export class NewWorkspace { await this.driverHelper.waitAndClick(By.xpath(NewWorkspace.CREATE_AND_OPEN_BUTTON_XPATH), timeout) - //check that the workspace has started to boot + // check that the workspace has started to boot await this.driverHelper.waitVisibility(ideFrameLocator, timeout) } diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index eedc8870c84..9d9229e0ae6 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -141,12 +141,12 @@ export class ProjectTree { paths.push(currentPath); }) - //expand each project tree item + // expand each project tree item for (const path of paths) { await this.expandItem(path, timeout) } - //open file + // open file await this.clickOnItem(`${pathToItem}/${fileName}`, timeout) } diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index 374eae7f6d2..887112ce7d9 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -15,7 +15,6 @@ import { TYPES } from "../../inversify.types"; import { Driver } from "../../driver/Driver"; import { TestConstants } from "../../TestConstants"; - @injectable() export class SingleUserLoginPage implements LoginPage { private readonly driver: ThenableWebDriver; @@ -26,11 +25,10 @@ export class SingleUserLoginPage implements LoginPage { this.driver = driver.get(); } - async login() { await this.driver .navigate() .to(TestConstants.TS_SELENIUM_BASE_URL) } -} \ No newline at end of file +} diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index a9bd250d9a3..1146db422ba 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -123,14 +123,14 @@ suite("E2E", async () => { await projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); }) - //unskip after resolving issue https://github.com/eclipse/che/issues/12904 + // unskip after resolving issue https://github.com/eclipse/che/issues/12904 test.skip("Check \"Java Language Server\" initialization by statusbar", async () => { await ide.waitStatusBarContains("Starting Java Language Server") await ide.waitStatusBarContains("100% Starting Java Language Server") await ide.waitStatusBarTextAbcence("Starting Java Language Server") }) - //unskip after resolving issue https://github.com/eclipse/che/issues/12904 + // unskip after resolving issue https://github.com/eclipse/che/issues/12904 test.skip("Check \"Java Language Server\" initialization by suggestion invoking", async () => { await editor.waitEditorAvailable(tabTitle); await editor.clickOnTab(tabTitle); diff --git a/typescript-selenium/tsconfig.json b/typescript-selenium/tsconfig.json index 4edaf08ae43..118e41b280c 100644 --- a/typescript-selenium/tsconfig.json +++ b/typescript-selenium/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "commonjs", "strict": true, "esModuleInterop": true, diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index e8fdb83c0ae..b33aea9bcb5 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -40,7 +40,7 @@ export class DriverHelper { } } - public async wait(miliseconds: number): Promise { + public async wait(miliseconds: number) { await this.driver.sleep(miliseconds) } @@ -151,7 +151,7 @@ export class DriverHelper { } } - public async waitAllDisappearance(locators: Array, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING): Promise { + public async waitAllDisappearance(locators: Array, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { for (const elementLocator of locators) { await this.waitDisappearance(elementLocator, attempts, polling) } diff --git a/typescript-selenium/utils/NameGenerator.ts b/typescript-selenium/utils/NameGenerator.ts index c64b5325b03..ca9bbca3a10 100644 --- a/typescript-selenium/utils/NameGenerator.ts +++ b/typescript-selenium/utils/NameGenerator.ts @@ -10,12 +10,12 @@ export class NameGenerator { public static generate(prefix: string, randomLength: number): string { - let possibleCharacters: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - let i: number; + const possibleCharacters: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const possibleCharactersLength: number = possibleCharacters.length let randomPart: string = ""; - for (i = 0; i < randomLength; i++) { - let currentRandomIndex: number = Math.floor(Math.random() * Math.floor(52)); + for (let i = 0; i < randomLength; i++) { + let currentRandomIndex: number = Math.floor(Math.random() * Math.floor(possibleCharactersLength)); randomPart += possibleCharacters[currentRandomIndex]; } diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index b44eae0fc75..5ed3dcb7a61 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -15,7 +15,11 @@ import { CLASSES } from '../../inversify.types'; import 'reflect-metadata'; import * as rm from 'typed-rest-client/RestClient' - +export enum WorkspaceStatus { + RUNNING = 'RUNNING', + STOPPED = 'STOPPED', + STARTING = 'STARTING' +} @injectable() export class TestWorkspaceUtil { @@ -29,9 +33,6 @@ export class TestWorkspaceUtil { const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${workspaceNamespace}:${workspaceName}`; const attempts: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS; const polling: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING; - const runningWorkspaceStatus: string = 'RUNNING'; - const stoppedWorkspaceStatus: string = 'STOPPED'; - const startingWorkspaceStatus: string = 'STARTING'; const rest: rm.RestClient = new rm.RestClient('rest-samples') @@ -47,15 +48,15 @@ export class TestWorkspaceUtil { const workspaceStatus: string = await response.result.status - if (workspaceStatus === runningWorkspaceStatus) { + if (workspaceStatus === WorkspaceStatus.RUNNING) { return; } - if (workspaceStatus === startingWorkspaceStatus) { + if (workspaceStatus === WorkspaceStatus.STARTING) { isWorkspaceStarting = true; } - if ((workspaceStatus === stoppedWorkspaceStatus) && isWorkspaceStarting) { + if ((workspaceStatus === WorkspaceStatus.STOPPED) && isWorkspaceStarting) { throw new Error("Workspace starting process is crushed") } From 65d45e68a0ff56bb0892bd92c344f22145b22247 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 25 Apr 2019 17:42:41 +0300 Subject: [PATCH 42/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- .../pageobjects/dashboard/Dashboard.ts | 8 +------- .../pageobjects/dashboard/NewWorkspace.ts | 8 +------- .../pageobjects/dashboard/Workspaces.ts | 9 +-------- .../dashboard/workspace-details/WorkspaceDetails.ts | 10 +--------- .../workspace-details/WorkspaceDetailsPlugins.ts | 8 +------- typescript-selenium/pageobjects/ide/Editor.ts | 6 +----- typescript-selenium/pageobjects/ide/Ide.ts | 12 ++---------- typescript-selenium/pageobjects/ide/ProjectTree.ts | 11 ++--------- .../pageobjects/login/SingleUserLoginPage.ts | 12 +++--------- typescript-selenium/utils/DriverHelper.ts | 4 +--- .../utils/workspace/TestWorkspaceUtil.ts | 6 +----- 11 files changed, 15 insertions(+), 79 deletions(-) diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 7b2e9e2890f..300f6ee6423 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -17,19 +17,13 @@ import { TestConstants } from "../../TestConstants"; @injectable() export class Dashboard { - private readonly driverHelper: DriverHelper; - private static readonly DASHBOARD_BUTTON_CSS: string = "#dashboard-item"; private static readonly WORKSPACES_BUTTON_CSS: string = "#workspaces-item"; private static readonly STACKS_BUTTON_CSS: string = "#stacks-item"; private static readonly FACTORIES_BUTTON_CSS: string = "#factories-item"; private static readonly LOADER_PAGE_CSS: string = ".main-page-loader" - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper - ) { - this.driverHelper = driverHelper; - } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } async openDashboard(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.navigateTo(TestConstants.TS_SELENIUM_BASE_URL) diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index da186b97b46..78368214079 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -17,8 +17,6 @@ import 'reflect-metadata'; @injectable() export class NewWorkspace { - private readonly driverHelper: DriverHelper; - private static readonly CHE_7_STACK_CSS: string = "div[data-stack-id='che7-preview']"; private static readonly SELECTED_CHE_7_STACK_CSS: string = ".stack-selector-item-selected[data-stack-id='che7-preview']" private static readonly CREATE_AND_OPEN_BUTTON_XPATH: string = "(//che-button-save-flat[@che-button-title='Create & Open']/button)[1]" @@ -28,11 +26,7 @@ export class NewWorkspace { private static readonly NAME_FIELD_CSS: string = "#workspace-name-input"; - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper - ) { - this.driverHelper = driverHelper; - } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } async selectCreateWorkspaceAndProceedEditing(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const createAndProceedEditingButtonLocator: By = By.xpath("//span[text()='Create & Proceed Editing']") diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/typescript-selenium/pageobjects/dashboard/Workspaces.ts index 059ccbbf02e..854b9720946 100644 --- a/typescript-selenium/pageobjects/dashboard/Workspaces.ts +++ b/typescript-selenium/pageobjects/dashboard/Workspaces.ts @@ -17,18 +17,11 @@ import { By } from "selenium-webdriver"; @injectable() export class Workspaces { - private readonly driverHelper: DriverHelper; private static readonly TITLE: string = ".che-toolbar-title-label"; private static readonly ADD_WORKSPACE_BUTTON_CSS: string = "#add-item-button"; private static readonly START_STOP_WORKSPACE_TIMEOUT: number = TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper - ) { - this.driverHelper = driverHelper; - } - - + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } private getWorkspaceListItemLocator(workspaceName: string): string { return `#ws-name-${workspaceName}` diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index 64f62c1cccd..14fab25dece 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -17,9 +17,6 @@ import { By } from "selenium-webdriver"; @injectable() export class WorkspaceDetails { - - private readonly driverHelper: DriverHelper; - private static readonly RUN_BUTTON_CSS: string = "#run-workspace-button[che-button-title='Run']"; private static readonly OPEN_BUTTON_CSS: string = "#open-in-ide-button[che-button-title='Open']"; private static readonly SELECTED_TAB_BUTTON_XPATH: string = "md-tabs-canvas[role='tablist'] md-tab-item[aria-selected='true']"; @@ -27,12 +24,7 @@ export class WorkspaceDetails { private static readonly ENABLED_SAVE_BUTTON_CSS: string = "button[name='save-button'][aria-disabled='false']"; private static readonly WORKSPACE_DETAILS_LOADER_CSS: string = "workspace-details-overview md-progress-linear"; - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper - ) { - this.driverHelper = driverHelper; - } - + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } private getWorkspaceTitleCssLocator(workspaceName: string): string { return `che-row-toolbar[che-title='${workspaceName}']` diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts index 22e68b3832b..1ef1a44af54 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -17,13 +17,7 @@ import { By } from "selenium-webdriver"; @injectable() export class WorkspaceDetailsPlugins { - private readonly driverHelper: DriverHelper; - - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper - ) { - this.driverHelper = driverHelper; - } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } private getPluginListItemCssLocator(pluginName: string): string { return `.plugin-item div[plugin-item-name='${pluginName}']` diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index 26f3ecf9d9e..f98e43ffb67 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -16,11 +16,7 @@ import { By, WebElement, Key } from 'selenium-webdriver'; @injectable() export class Editor { - private readonly driverHelper: DriverHelper; - - constructor(@inject(CLASSES.DriverHelper) driverHelper: DriverHelper) { - this.driverHelper = driverHelper - } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } private static readonly EDITOR_BODY_CSS: string = "#theia-main-content-panel .lines-content"; private static readonly EDITOR_LINES_XPATH: string = "//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line']"; diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index 539cf7061f4..6020e5f6196 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -17,10 +17,6 @@ import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; @injectable() export class Ide { - - private readonly driverHelper: DriverHelper; - private readonly testWorkspaceUtil: TestWorkspaceUtil; - private static readonly TOP_MENU_PANEL_CSS: string = "#theia-app-shell #theia-top-panel .p-MenuBar-content"; private static readonly LEFT_CONTENT_PANEL_CSS: string = "#theia-left-content-panel"; public static readonly EXPLORER_BUTTON_XPATH: string = "(//ul[@class='p-TabBar-content']//li[@title='Explorer'])[1]"; @@ -29,12 +25,8 @@ export class Ide { private static readonly IDE_IFRAME_CSS: string = "iframe#ide-application-iframe"; constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper, - @inject(CLASSES.TestWorkspaceUtil) testWorkspaceUtil: TestWorkspaceUtil - ) { - this.driverHelper = driverHelper; - this.testWorkspaceUtil = testWorkspaceUtil; - } + @inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + @inject(CLASSES.TestWorkspaceUtil) private readonly testWorkspaceUtil: TestWorkspaceUtil) { } async waitAndSwitchToIdeFrame(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitAndSwitchToFrame(By.css(Ide.IDE_IFRAME_CSS), timeout) diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index 9d9229e0ae6..a0257444daf 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -17,16 +17,9 @@ import { By } from 'selenium-webdriver'; @injectable() export class ProjectTree { - private readonly driverHelper: DriverHelper; - private readonly ide: Ide; - constructor( - @inject(CLASSES.DriverHelper) driverHelper: DriverHelper, - @inject(CLASSES.Ide) ide: Ide - ) { - this.driverHelper = driverHelper - this.ide = ide - } + @inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + @inject(CLASSES.Ide) private readonly ide: Ide) { } private static readonly PROJECT_TREE_CONTAINER_CSS: string = "#theia-left-side-panel .theia-TreeContainer"; diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index 887112ce7d9..5fb9590d972 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -17,18 +17,12 @@ import { TestConstants } from "../../TestConstants"; @injectable() export class SingleUserLoginPage implements LoginPage { - private readonly driver: ThenableWebDriver; - constructor( - @inject(TYPES.Driver) driver: Driver - ) { - this.driver = driver.get(); - } + @inject(TYPES.Driver) private readonly driver: Driver) { } async login() { - await this.driver - .navigate() - .to(TestConstants.TS_SELENIUM_BASE_URL) + const webDriver: ThenableWebDriver = this.driver.get() + await webDriver.navigate().to(TestConstants.TS_SELENIUM_BASE_URL) } } diff --git a/typescript-selenium/utils/DriverHelper.ts b/typescript-selenium/utils/DriverHelper.ts index b33aea9bcb5..ba856198b6a 100644 --- a/typescript-selenium/utils/DriverHelper.ts +++ b/typescript-selenium/utils/DriverHelper.ts @@ -20,9 +20,7 @@ import { TestConstants } from "../TestConstants"; export class DriverHelper { private readonly driver: ThenableWebDriver; - constructor( - @inject(TYPES.Driver) driver: Driver - ) { + constructor(@inject(TYPES.Driver) driver: Driver) { this.driver = driver.get(); } diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 5ed3dcb7a61..8ea76050e48 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -23,11 +23,7 @@ export enum WorkspaceStatus { @injectable() export class TestWorkspaceUtil { - private readonly driverHelper: DriverHelper; - - constructor(@inject(CLASSES.DriverHelper) driverHelper: DriverHelper) { - this.driverHelper = driverHelper; - } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } public async waitRunningStatus(workspaceNamespace: string, workspaceName: string) { const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${workspaceNamespace}:${workspaceName}`; From 2042f73e803b5cdced048cc12631e53f17be43af Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 25 Apr 2019 17:55:10 +0300 Subject: [PATCH 43/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- .gitignore | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index e716eb313f5..39e037b14c9 100644 --- a/.gitignore +++ b/.gitignore @@ -83,9 +83,12 @@ docs/assets/imgs plugins/plugin-terminal-ui/node_modules/ plugins/plugin-terminal-ui/build/ requirements.lock +typescript-selenium/dist/ +typescript-selenium/node_modules/ +typescript-selenium/report/ # Cypress modules # -################## +################### */cypress/screenshots */cypress/videos */node_modules @@ -97,10 +100,3 @@ requirements.lock */.grunt */.lock-wscript */cypress-tests/dist - - -# typescript-selenium module # -############################## -*/typescript-selenium/dist/ -*/typescript-selenium/node_modules/ -*/typescript-selenium/report/ From dcf5a00ed6796507daeae8999bc9cc6fe333d107 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 10:56:30 +0300 Subject: [PATCH 44/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 74 ++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 01f2a8ed97c..b2f2f41b550 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -8,31 +8,65 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -function readEnvAndSetValue(envProperty: any, defaultValue: string | number | boolean): any { - const propertyValue = envProperty - if (!propertyValue) { - return defaultValue; - } +export const TestConstants = { + /** + * Base URL of the application which should be checked + */ + TS_SELENIUM_BASE_URL: process.env.TS_SELENIUM_BASE_URL || "http://che-che.192.168.99.100.nip.io", - return propertyValue -} + /** + * Run browser in "Headless" (hiden) mode, "false" by default. + */ + TS_SELENIUM_HEADLESS: process.env.TS_SELENIUM_HEADLESS === 'true', -export const TestConstants = { - TS_SELENIUM_HEADLESS: readEnvAndSetValue(process.env.TS_SELENIUM_HEADLESS, false), + /** + * Browser width resolution, "1920" by default. + */ + TS_SELENIUM_RESOLUTION_WIDTH: Number(process.env.TS_SELENIUM_BASE_URL) || 1920, + + /** + * Browser height resolution, "1080" by default. + */ + TS_SELENIUM_RESOLUTION_HEIGHT: Number(process.env.TS_SELENIUM_BASE_URL) || 1080, + + /** + * Timeout in milliseconds waiting for workspace start, "240 000" by default. + */ + TS_SELENIUM_START_WORKSPACE_TIMEOUT: Number(process.env.TS_SELENIUM_START_WORKSPACE_TIMEOUT) || 240000, + + /** + * Timeout in milliseconds waiting for page load, "120 000" by default. + */ + TS_SELENIUM_LOAD_PAGE_TIMEOUT: Number(process.env.TS_SELENIUM_LOAD_PAGE_TIMEOUT) || 120000, + + /** + * Timeout in milliseconds waiting for language server initialization, "180 000" by default. + */ + TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT: Number(process.env.TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT) || 180000, + + /** + * Default timeout for most of the waitings, "20 000" by default. + */ + TS_SELENIUM_DEFAULT_TIMEOUT: Number(process.env.TS_SELENIUM_DEFAULT_TIMEOUT) || 20000, - TS_SELENIUM_START_WORKSPACE_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_START_WORKSPACE_TIMEOUT, 240000), - TS_SELENIUM_LOAD_PAGE_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_LOAD_PAGE_TIMEOUT, 120000), - TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT, 180000), + /** + * Default ammount of tries, "5" by default. + */ + TS_SELENIUM_DEFAULT_ATTEMPTS: Number(process.env.TS_SELENIUM_DEFAULT_ATTEMPTS) || 5, - TS_SELENIUM_DEFAULT_TIMEOUT: readEnvAndSetValue(process.env.TS_SELENIUM_DEFAULT_TIMEOUT, 20000), - TS_SELENIUM_DEFAULT_ATTEMPTS: readEnvAndSetValue(process.env.TS_SELENIUM_DEFAULT_ATTEMPTS, 5), - TS_SELENIUM_DEFAULT_POLLING: readEnvAndSetValue(process.env.TS_SELENIUM_DEFAULT_POLLING, 1000), + /** + * Default delay in milliseconds between tries, "1000" by default. + */ + TS_SELENIUM_DEFAULT_POLLING: Number(process.env.TS_SELENIUM_DEFAULT_POLLING) || 1000, - TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: readEnvAndSetValue(process.env.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS, 90), - TS_SELENIUM_WORKSPACE_STATUS_POLLING: readEnvAndSetValue(process.env.TS_SELENIUM_WORKSPACE_STATUS_POLLING, 10000), + /** + * Amount of tries for checking workspace status. + */ + TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: Number(process.env.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS) || 90, - TS_SELENIUM_BASE_URL: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, "http://che-che.192.168.99.100.nip.io"), + /** + * Delay in milliseconds between checking workspace status tries. + */ + TS_SELENIUM_WORKSPACE_STATUS_POLLING: Number(process.env.TS_SELENIUM_WORKSPACE_STATUS_POLLING) || 10000 - TS_SELENIUM_RESOLUTION_WIDTH: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, 1920), - TS_SELENIUM_RESOLUTION_HEIGHT: readEnvAndSetValue(process.env.TS_SELENIUM_BASE_URL, 1080) } From a9cd9e8637e9a7b02e98e1c39e6fea8b59ce9cfd Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 11:23:42 +0300 Subject: [PATCH 45/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 44041e930f2..b7d44975f20 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -46,10 +46,10 @@ class CheReporter extends mocha.reporters.Spec { runner.on('end', async function (test: mocha.Test) { // ensure that fired events done - await driver.get().sleep(5000).catch(err => {throw err}) + await driver.get().sleep(5000) // close driver - await driver.get().quit().catch(err => { throw err }) + await driver.get().quit() }) @@ -86,13 +86,13 @@ class CheReporter extends mocha.reporters.Spec { }) // take screenshot and write to file - const screenshot: string = await driver.get().takeScreenshot().catch(err => { throw err }); + const screenshot: string = await driver.get().takeScreenshot(); const screenshotStream = fs.createWriteStream(screenshotFileName) screenshotStream.write(new Buffer(screenshot, 'base64')) screenshotStream.end() // take pagesource and write to file - const pageSource: string = await driver.get().getPageSource().catch(err => { throw err }) + const pageSource: string = await driver.get().getPageSource() const pageSourceStream = fs.createWriteStream(pageSourceFileName) pageSourceStream.write(new Buffer(pageSource)) pageSourceStream.end() From 8a78567fed9a538721cd49fc129d682f99ceed94 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 12:35:11 +0300 Subject: [PATCH 46/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/README.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md index e657e5d4bc3..c8586d6016c 100644 --- a/typescript-selenium/README.md +++ b/typescript-selenium/README.md @@ -6,19 +6,4 @@ - npm install && npm test ## Custom launch -**Use environment variables (check default values in 'TestConstants.ts' file):** -### application url -- TS_SELENIUM_BASE_URL *( string )* - -### headless launch -- TS_SELENIUM_HEADLESS *( boolean )* - -### timeouts -- TS_SELENIUM_DEFAULT_ATTEMPTS *( number )* -- TS_SELENIUM_DEFAULT_POLLING *( number )* -- TS_SELENIUM_DEFAULT_TIMEOUT *( number )* -- TS_SELENIUM_LANGUAGE_SERVER_START_TIMEOUT *( number )* -- TS_SELENIUM_LOAD_PAGE_TIMEOUT *( number )* -- TS_SELENIUM_START_WORKSPACE_TIMEOUT *( number )* -- TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS *( number )* -- TS_SELENIUM_WORKSPACE_STATUS_POLLING *( number )* +- Use environment variables which described in the **```'TestConstants.ts'```** file From d26129886f2352e94a6ec722bbc8782dd3d6cab0 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 12:41:26 +0300 Subject: [PATCH 47/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index b7d44975f20..d34de03a283 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -101,4 +101,4 @@ class CheReporter extends mocha.reporters.Spec { } } -module.exports = CheReporter; +export = CheReporter; From 3a4ae9ec9d208d4f5b5a8947092275a802fe148a Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 12:42:08 +0300 Subject: [PATCH 48/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/CheReporter.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index d34de03a283..87b12e7477f 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -40,7 +40,6 @@ class CheReporter extends mocha.reporters.Spec { ######################################################## ` - console.log(launchInformation) }) @@ -52,7 +51,6 @@ class CheReporter extends mocha.reporters.Spec { await driver.get().quit() }) - runner.on('fail', async function (test: mocha.Test) { const reportDirPath: string = './report' From 48d5d0f7ed1831522775782bc02dc3a7403a6450 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 15:08:32 +0300 Subject: [PATCH 49/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/mocha.opts | 1 + typescript-selenium/package-lock.json | 14 +++++++------- typescript-selenium/package.json | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/typescript-selenium/mocha.opts b/typescript-selenium/mocha.opts index 96925abbfd7..c6e86d8ba91 100644 --- a/typescript-selenium/mocha.opts +++ b/typescript-selenium/mocha.opts @@ -2,4 +2,5 @@ --timeout 1200000 --reporter 'driver/CheReporter.ts' -u tdd +--bail --spec tests/*.spec.ts diff --git a/typescript-selenium/package-lock.json b/typescript-selenium/package-lock.json index 7052b4a7b66..a8dc0b780b9 100644 --- a/typescript-selenium/package-lock.json +++ b/typescript-selenium/package-lock.json @@ -833,9 +833,9 @@ "dev": true }, "js-yaml": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", - "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -1005,9 +1005,9 @@ } }, "mocha": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz", - "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -1019,7 +1019,7 @@ "glob": "7.1.3", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.13.0", + "js-yaml": "3.13.1", "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", diff --git a/typescript-selenium/package.json b/typescript-selenium/package.json index b1b90acb311..75778eb7fe2 100644 --- a/typescript-selenium/package.json +++ b/typescript-selenium/package.json @@ -15,7 +15,7 @@ "@types/selenium-webdriver": "^3.0.16", "chai": "^4.2.0", "chromedriver": "^2.46.0", - "mocha": "^6.1.3", + "mocha": "^6.1.4", "selenium-webdriver": "^3.6.0", "ts-node": "^8.0.3", "typed-rest-client": "^1.2.0", From fa58f3cb7e6c367e51407c5fc02a841e076e73c4 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 26 Apr 2019 17:01:58 +0300 Subject: [PATCH 50/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- .../pageobjects/dashboard/NewWorkspace.ts | 3 ++- .../pageobjects/login/SingleUserLoginPage.ts | 9 ++++++--- typescript-selenium/tests/HappyPath.spec.ts | 15 +++------------ .../utils/workspace/TestWorkspaceUtil.ts | 11 +++++------ 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index 78368214079..bdd8b89abfc 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -47,7 +47,8 @@ export class NewWorkspace { async clickOnChe7Stack(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const che7StackLocator: By = By.css(NewWorkspace.CHE_7_STACK_CSS) - await this.driverHelper.waitAndClick(che7StackLocator) + await this.driverHelper.waitAndClick(che7StackLocator, timeout) + await this.waitChe7StackSelected(timeout) } async waitChe7StackSelected(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts index 5fb9590d972..923d7d42b8b 100644 --- a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts +++ b/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts @@ -11,18 +11,21 @@ import "reflect-metadata"; import { LoginPage } from "./LoginPage"; import { injectable, inject } from "inversify"; import { ThenableWebDriver } from "selenium-webdriver"; -import { TYPES } from "../../inversify.types"; +import { TYPES, CLASSES } from "../../inversify.types"; import { Driver } from "../../driver/Driver"; import { TestConstants } from "../../TestConstants"; +import { Dashboard } from "../dashboard/Dashboard"; @injectable() export class SingleUserLoginPage implements LoginPage { constructor( - @inject(TYPES.Driver) private readonly driver: Driver) { } + @inject(TYPES.Driver) private readonly driver: Driver, + @inject(CLASSES.Dashboard) private readonly dashboard: Dashboard) { } - async login() { + async login(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { const webDriver: ThenableWebDriver = this.driver.get() await webDriver.navigate().to(TestConstants.TS_SELENIUM_BASE_URL) + await this.dashboard.waitPage(timeout) } } diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 1146db422ba..b6d829e670c 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -53,38 +53,29 @@ suite("E2E", async () => { test("login", async () => { await loginPage.login() }) - - test("wait dashboard", async () => { - await dashboard.waitLoader() - await dashboard.waitLoaderDisappearance() - await dashboard.waitPage() - }) }) suite("Create workspace and open IDE", async () => { - - test("Go to 'New Workspace' page", async () => { + test(`Create a '${workspaceName}' workspace`, async () => { + await dashboard.waitPage() await dashboard.clickWorkspacesButton() await workspaces.clickAddWorkspaceButton() - }) - test(`Create a '${workspaceName}' workspace`, async () => { await newWorkspace.typeWorkspaceName(workspaceName) await newWorkspace.clickOnChe7Stack() - await newWorkspace.waitChe7StackSelected() await newWorkspace.clickOnAddOrImportProjectButton() await newWorkspace.enableSampleCheckbox(sampleName) await newWorkspace.clickOnAddButton() await newWorkspace.waitProjectAdding(sampleName) await newWorkspace.selectCreateWorkspaceAndProceedEditing() + await workspaceDetails.waitPage(workspaceName); }) test("Add 'Java Language Support' plugin to workspace", async () => { const javaPluginName: string = "Language Support for Java(TM)"; const execPlugin: string = "Che machine-exec Service"; - await workspaceDetails.waitPage(workspaceName); await workspaceDetails.waitTabSelected('Overview') await workspaceDetails.clickOnTab('Plugins') await workspaceDetails.waitTabSelected('Plugins') diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index 8ea76050e48..dd58b406917 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -23,19 +23,18 @@ export enum WorkspaceStatus { @injectable() export class TestWorkspaceUtil { - constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + private readonly rest: rm.RestClient = new rm.RestClient('rest-samples')) { } - public async waitRunningStatus(workspaceNamespace: string, workspaceName: string) { - const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${workspaceNamespace}:${workspaceName}`; + public async waitRunningStatus(namespace: string, workspaceName: string) { + const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${namespace}:${workspaceName}`; const attempts: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS; const polling: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING; - const rest: rm.RestClient = new rm.RestClient('rest-samples') - for (let i = 0; i < attempts; i++) { let isWorkspaceStarting: boolean = false; - const response: rm.IRestResponse = await rest.get(workspaceStatusApiUrl) + const response: rm.IRestResponse = await this.rest.get(workspaceStatusApiUrl) if (response.statusCode !== 200) { await this.driverHelper.wait(polling) From 19e0434102d69c7ebb943d29a3d942b9dc164fa6 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 2 May 2019 15:44:17 +0300 Subject: [PATCH 51/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/TestConstants.ts | 12 ++- typescript-selenium/driver/CheReporter.ts | 2 + .../pageobjects/dashboard/Dashboard.ts | 26 +++++- .../pageobjects/dashboard/NewWorkspace.ts | 87 ++++++++++++++++++- .../workspace-details/WorkspaceDetails.ts | 35 ++++++-- .../WorkspaceDetailsPlugins.ts | 33 ++++++- typescript-selenium/pageobjects/ide/Ide.ts | 6 +- typescript-selenium/tests/HappyPath.spec.ts | 72 +++++---------- .../utils/workspace/TestWorkspaceUtil.ts | 38 +++++--- 9 files changed, 233 insertions(+), 78 deletions(-) diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index b2f2f41b550..400a9784256 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -67,6 +67,16 @@ export const TestConstants = { /** * Delay in milliseconds between checking workspace status tries. */ - TS_SELENIUM_WORKSPACE_STATUS_POLLING: Number(process.env.TS_SELENIUM_WORKSPACE_STATUS_POLLING) || 10000 + TS_SELENIUM_WORKSPACE_STATUS_POLLING: Number(process.env.TS_SELENIUM_WORKSPACE_STATUS_POLLING) || 10000, + + /** + * Amount of tries for checking plugin precence. + */ + TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS: Number(process.env.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS) || 20, + + /** + * Delay in milliseconds between checking plugin precence. + */ + TS_SELENIUM_PLUGIN_PRECENCE_POLLING: Number(process.env.TS_SELENIUM_PLUGIN_PRECENCE_POLLING) || 2000, } diff --git a/typescript-selenium/driver/CheReporter.ts b/typescript-selenium/driver/CheReporter.ts index 87b12e7477f..f4713b8e01f 100644 --- a/typescript-selenium/driver/CheReporter.ts +++ b/typescript-selenium/driver/CheReporter.ts @@ -37,6 +37,8 @@ class CheReporter extends mocha.reporters.Spec { TS_SELENIUM_START_WORKSPACE_TIMEOUT: ${TestConstants.TS_SELENIUM_START_WORKSPACE_TIMEOUT} TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: ${TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS} TS_SELENIUM_WORKSPACE_STATUS_POLLING: ${TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING} + TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS: ${TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS} + TS_SELENIUM_PLUGIN_PRECENCE_POLLING: ${TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_POLLING} ######################################################## ` diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/typescript-selenium/pageobjects/dashboard/Dashboard.ts index 300f6ee6423..f7e6d69b644 100644 --- a/typescript-selenium/pageobjects/dashboard/Dashboard.ts +++ b/typescript-selenium/pageobjects/dashboard/Dashboard.ts @@ -14,6 +14,7 @@ import { Driver } from "../../driver/Driver"; import { WebElementCondition, By } from "selenium-webdriver"; import { DriverHelper } from "../../utils/DriverHelper"; import { TestConstants } from "../../TestConstants"; +import { Workspaces } from "./Workspaces"; @injectable() export class Dashboard { @@ -23,7 +24,30 @@ export class Dashboard { private static readonly FACTORIES_BUTTON_CSS: string = "#factories-item"; private static readonly LOADER_PAGE_CSS: string = ".main-page-loader" - constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + @inject(CLASSES.Workspaces) private readonly workspaces: Workspaces) { } + + async stopWorkspaceByUI(workspaceName: string) { + await this.openDashboard() + await this.clickWorkspacesButton() + await this.workspaces.waitPage() + await this.workspaces.waitWorkspaceListItem(workspaceName) + await this.workspaces.waitWorkspaceWithRunningStatus(workspaceName) + await this.workspaces.clickOnStopWorkspaceButton(workspaceName) + await this.workspaces.waitWorkspaceWithStoppedStatus(workspaceName) + } + + async deleteWorkspaceByUI(workspaceName: string) { + await this.openDashboard() + await this.clickWorkspacesButton() + await this.workspaces.waitPage() + await this.workspaces.waitWorkspaceListItem(workspaceName) + await this.workspaces.clickWorkspaceListItem(workspaceName); + await this.workspaces.clickDeleteButtonOnWorkspaceDetails(); + await this.workspaces.clickConfirmDeletionButton(); + await this.workspaces.waitPage() + await this.workspaces.waitWorkspaceListItemAbcence(workspaceName); + } async openDashboard(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.navigateTo(TestConstants.TS_SELENIUM_BASE_URL) diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts index bdd8b89abfc..de5b68bfd19 100644 --- a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts +++ b/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts @@ -13,6 +13,11 @@ import { CLASSES } from "../../inversify.types"; import { TestConstants } from "../../TestConstants"; import { By } from "selenium-webdriver"; import 'reflect-metadata'; +import { Dashboard } from "./Dashboard"; +import { Workspaces } from "./Workspaces"; +import { Ide } from "../ide/Ide"; +import { TestWorkspaceUtil, WorkspaceStatus } from "../../utils/workspace/TestWorkspaceUtil"; +import { WorkspaceDetails } from "./workspace-details/WorkspaceDetails"; @injectable() @@ -24,9 +29,88 @@ export class NewWorkspace { private static readonly ADD_OR_IMPORT_PROJECT_BUTTON_CSS: string = ".add-import-project-toggle-button"; private static readonly ADD_BUTTON_CSS: string = "button[aria-disabled='false'][name='addButton']"; private static readonly NAME_FIELD_CSS: string = "#workspace-name-input"; + private static readonly TITLE_CSS: string = "#New_Workspace"; + private static readonly VISIBLE_LOADER_CSS = "md-progress-linear.create-workspace-progress[aria-hidden='false']"; + private static readonly HIDDEN_LOADER_CSS = "md-progress-linear.create-workspace-progress[aria-hidden='true']"; + private static readonly PROJECT_SOURCE_FORM_CSS = "#project-source-selector .project-source-selector-popover[aria-hidden='false']" - constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + @inject(CLASSES.Dashboard) private readonly dashboard: Dashboard, + @inject(CLASSES.Workspaces) private readonly workspaces: Workspaces, + @inject(CLASSES.TestWorkspaceUtil) private readonly testWorkspaceUtil: TestWorkspaceUtil, + @inject(CLASSES.WorkspaceDetails) private readonly workspaceDetails: WorkspaceDetails) { } + + private getStackCssLocator(dataStackId: string): string { + return `div[data-stack-id='${dataStackId}']` + } + + private getSelectedStackCssLocator(dataStackId: string) { + return `div.stack-selector-item-selected[data-stack-id='${dataStackId}']` + } + + private async prepareWorkspace(workspaceName: string, dataStackId: string, sampleName: string) { + await this.typeWorkspaceName(workspaceName) + await this.selectStack(dataStackId) + await this.clickOnAddOrImportProjectButton() + await this.enableSampleCheckbox(sampleName) + await this.confirmProjectAdding(sampleName) + } + + async createAndRunWorkspace(namespace: string, workspaceName: string, dataStackId: string, sampleName: string) { + await this.prepareWorkspace(workspaceName, dataStackId, sampleName) + await this.clickOnCreateAndOpenButton() + + await this.driverHelper.waitVisibility(By.css(Ide.ACTIVATED_IDE_IFRAME_CSS)) + await this.testWorkspaceUtil.waitWorkspaceStatus(namespace, workspaceName, WorkspaceStatus.STARTING) + } + + async createWorkspaceAndProceedEditing(workspaceName: string, dataStackId: string, sampleName: string) { + await this.prepareWorkspace(workspaceName, dataStackId, sampleName) + await this.selectCreateWorkspaceAndProceedEditing() + + await this.workspaceDetails.waitPage(workspaceName); + } + + async confirmProjectAdding(sampleName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.clickOnAddButton(timeout) + await this.waitProjectAdding(sampleName, timeout) + } + + async waitProjectSourceForm(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(NewWorkspace.PROJECT_SOURCE_FORM_CSS), timeout) + } + + async selectStack(dataStackId: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const stackLocator: By = By.css(this.getStackCssLocator(dataStackId)) + + await this.driverHelper.waitAndClick(stackLocator, timeout) + await this.waitStackSelection(dataStackId, timeout) + } + + async waitStackSelection(dataStackId: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const selectedStackLocator: By = By.css(this.getSelectedStackCssLocator(dataStackId)) + + await this.driverHelper.waitAndClick(selectedStackLocator, timeout) + } + + async openPageByUI() { + await this.dashboard.waitPage() + await this.dashboard.clickWorkspacesButton() + await this.workspaces.clickAddWorkspaceButton() + + await this.waitPage() + } + + async waitPage(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitVisibility(By.css(NewWorkspace.NAME_FIELD_CSS), timeout) + await this.driverHelper.waitVisibility(By.css(NewWorkspace.TITLE_CSS), timeout) + await this.waitLoaderAbsence(timeout) + } + + async waitLoaderAbsence(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + await this.driverHelper.waitPresence(By.css(NewWorkspace.HIDDEN_LOADER_CSS), timeout) + } async selectCreateWorkspaceAndProceedEditing(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const createAndProceedEditingButtonLocator: By = By.xpath("//span[text()='Create & Proceed Editing']") @@ -70,6 +154,7 @@ export class NewWorkspace { const addOrImportProjectButtonLocator: By = By.css(NewWorkspace.ADD_OR_IMPORT_PROJECT_BUTTON_CSS) await this.driverHelper.waitAndClick(addOrImportProjectButtonLocator, timeout) + await this.waitProjectSourceForm(timeout) } async waitSampleCheckboxEnabling(sampleName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts index 14fab25dece..b21492807f1 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts @@ -13,6 +13,8 @@ import { CLASSES } from "../../../inversify.types"; import 'reflect-metadata'; import { TestConstants } from "../../../TestConstants"; import { By } from "selenium-webdriver"; +import { Ide } from "../../ide/Ide"; +import { TestWorkspaceUtil, WorkspaceStatus } from "../../../utils/workspace/TestWorkspaceUtil"; @injectable() @@ -24,7 +26,8 @@ export class WorkspaceDetails { private static readonly ENABLED_SAVE_BUTTON_CSS: string = "button[name='save-button'][aria-disabled='false']"; private static readonly WORKSPACE_DETAILS_LOADER_CSS: string = "workspace-details-overview md-progress-linear"; - constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + @inject(CLASSES.TestWorkspaceUtil) private readonly testWorkspaceUtil: TestWorkspaceUtil) { } private getWorkspaceTitleCssLocator(workspaceName: string): string { return `che-row-toolbar[che-title='${workspaceName}']` @@ -42,18 +45,24 @@ export class WorkspaceDetails { await this.driverHelper.waitDisappearance(By.css(WorkspaceDetails.WORKSPACE_DETAILS_LOADER_CSS), attempts, polling) } - async waitSaveButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + private async waitSaveButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.ENABLED_SAVE_BUTTON_CSS), timeout) } - async waitSaveButtonDisappearance(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { + private async waitSaveButtonDisappearance(attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, polling = TestConstants.TS_SELENIUM_DEFAULT_POLLING) { await this.driverHelper.waitDisappearance(By.css(WorkspaceDetails.SAVE_BUTTON_CSS), attempts, polling) } - async clickOnSaveButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + private async clickOnSaveButton(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.ENABLED_SAVE_BUTTON_CSS), timeout) } + async saveChanges() { + await this.waitSaveButton() + await this.clickOnSaveButton() + await this.waitSaveButtonDisappearance() + } + async waitPage(workspaceName: string, timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.waitWorkspaceTitle(workspaceName, timeout); await this.waitOpenButton(timeout); @@ -80,10 +89,17 @@ export class WorkspaceDetails { await this.driverHelper.waitVisibility(By.css(WorkspaceDetails.OPEN_BUTTON_CSS), timeout) } - async clickOnOpenButton(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + private async clickOnOpenButton(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { await this.driverHelper.waitAndClick(By.css(WorkspaceDetails.OPEN_BUTTON_CSS), timeout) } + async openWorkspace(namespace: string, workspaceName: string, timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + await this.clickOnOpenButton(timeout) + + await this.driverHelper.waitVisibility(By.css(Ide.ACTIVATED_IDE_IFRAME_CSS)) + await this.testWorkspaceUtil.waitWorkspaceStatus(namespace, workspaceName, WorkspaceStatus.STARTING) + } + async waitTabsPresence(timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { const workspaceDetailsTabs: Array = ["Overview", "Projects", "Containers", "Servers", "Env Variables", "Volumes", "Config", "SSH", "Plugins", "Editors"]; @@ -95,17 +111,22 @@ export class WorkspaceDetails { } } - async clickOnTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + private async clickOnTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const workspaceDetailsTabLocator: By = By.xpath(this.getTabXpathLocator(tabTitle)) await this.driverHelper.waitAndClick(workspaceDetailsTabLocator, timeout) } - async waitTabSelected(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + private async waitTabSelected(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const selectedTabLocator: By = By.xpath(this.getSelectedTabXpathLocator(tabTitle)) await this.driverHelper.waitVisibility(selectedTabLocator, timeout) } + async selectTab(tabTitle: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.clickOnTab(tabTitle, timeout) + await this.waitTabSelected(tabTitle, timeout) + } + } diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts index 1ef1a44af54..96081229096 100644 --- a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts +++ b/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts @@ -13,11 +13,15 @@ import 'reflect-metadata'; import { CLASSES } from "../../../inversify.types"; import { TestConstants } from "../../../TestConstants"; import { By } from "selenium-webdriver"; +import { WorkspaceDetails } from "./WorkspaceDetails"; +import { TestWorkspaceUtil, WorkspaceStatus } from "../../../utils/workspace/TestWorkspaceUtil"; @injectable() export class WorkspaceDetailsPlugins { - constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { } + constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, + @inject(CLASSES.WorkspaceDetails) private readonly workspaceDetails: WorkspaceDetails, + @inject(CLASSES.TestWorkspaceUtil) private readonly testWorkspaceUtil: TestWorkspaceUtil) { } private getPluginListItemCssLocator(pluginName: string): string { return `.plugin-item div[plugin-item-name='${pluginName}']` @@ -33,19 +37,40 @@ export class WorkspaceDetailsPlugins { await this.driverHelper.waitVisibility(pluginListItemLocator, timeout) } - async clickOnPluginListItemSwitcher(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + async enablePlugin(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.waitPluginDisabling(pluginName, timeout) + await this.clickOnPluginListItemSwitcher(pluginName, timeout) + await this.waitPluginEnabling(pluginName, timeout) + } + + async disablePlugin(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + await this.waitPluginEnabling(pluginName, timeout) + await this.clickOnPluginListItemSwitcher(pluginName, timeout) + await this.waitPluginDisabling(pluginName, timeout) + } + + async addPluginAndOpenWorkspace(namespace: string, workspaceName: string, pluginName: string, pluginId: string) { + await this.workspaceDetails.selectTab('Plugins') + await this.enablePlugin(pluginName) + await this.workspaceDetails.saveChanges() + await this.workspaceDetails.openWorkspace(namespace, workspaceName) + await this.testWorkspaceUtil.waitWorkspaceStatus(namespace, workspaceName, WorkspaceStatus.RUNNING) + await this.testWorkspaceUtil.waitPluginAdding(namespace, workspaceName, pluginId) + } + + private async clickOnPluginListItemSwitcher(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const pluginListItemSwitcherLocator = By.css(this.getPluginListItemSwitcherCssLocator(pluginName)) await this.driverHelper.waitAndClick(pluginListItemSwitcherLocator, timeout) } - async waitPluginEnabling(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + private async waitPluginEnabling(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const enabledPluginSwitcherLocator: By = By.css(`${this.getPluginListItemCssLocator(pluginName)} md-switch[aria-checked='true']`) await this.driverHelper.waitVisibility(enabledPluginSwitcherLocator, timeout) } - async waitPluginDisabling(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + private async waitPluginDisabling(pluginName: string, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const disabledPluginSwitcherLocator: By = By.css(`${this.getPluginListItemCssLocator(pluginName)} md-switch[aria-checked='false']`) await this.driverHelper.waitVisibility(disabledPluginSwitcherLocator, timeout) diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/typescript-selenium/pageobjects/ide/Ide.ts index 6020e5f6196..be27c96f6c1 100644 --- a/typescript-selenium/pageobjects/ide/Ide.ts +++ b/typescript-selenium/pageobjects/ide/Ide.ts @@ -12,7 +12,7 @@ import { injectable, inject } from "inversify"; import { CLASSES } from "../../inversify.types"; import { TestConstants } from "../../TestConstants"; import { By } from "selenium-webdriver"; -import { TestWorkspaceUtil } from "../../utils/workspace/TestWorkspaceUtil"; +import { TestWorkspaceUtil, WorkspaceStatus } from "../../utils/workspace/TestWorkspaceUtil"; @injectable() @@ -23,6 +23,7 @@ export class Ide { public static readonly SELECTED_EXPLORER_BUTTON_XPATH: string = "(//ul[@class='p-TabBar-content']//li[@title='Explorer' and contains(@class, 'p-mod-current')])[1]" private static readonly PRELOADER_CSS: string = ".theia-preload"; private static readonly IDE_IFRAME_CSS: string = "iframe#ide-application-iframe"; + public static readonly ACTIVATED_IDE_IFRAME_CSS: string = "#ide-iframe-window[aria-hidden='false']" constructor( @inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, @@ -45,7 +46,8 @@ export class Ide { } async waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string, timeout = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { - await this.testWorkspaceUtil.waitRunningStatus(workspaceNamespace, workspaceName) + await this.waitAndSwitchToIdeFrame(timeout) + await this.testWorkspaceUtil.waitWorkspaceStatus(workspaceNamespace, workspaceName, WorkspaceStatus.RUNNING) await this.waitIde(timeout) } diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index b6d829e670c..d45871da796 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -33,6 +33,8 @@ import * as mocha from 'mocha' const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; const sampleName: string = "console-java-simple"; +const pluginId: string = "org.eclipse.che.vscode-redhat.java"; +const javaPluginName: string = "Language Support for Java(TM)"; const driver: Driver = e2eContainer.get(TYPES.Driver); const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); @@ -45,6 +47,7 @@ const workspaceDetailsPlugins: WorkspaceDetailsPlugins = e2eContainer.get(CLASSE const ide: Ide = e2eContainer.get(CLASSES.Ide) const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree) const editor: Editor = e2eContainer.get(CLASSES.Editor) +const testWorkspaceUtil: TestWorkspaceUtil = e2eContainer.get(CLASSES.TestWorkspaceUtil) suite("E2E", async () => { @@ -55,48 +58,19 @@ suite("E2E", async () => { }) }) - suite("Create workspace and open IDE", async () => { - test(`Create a '${workspaceName}' workspace`, async () => { - await dashboard.waitPage() - await dashboard.clickWorkspacesButton() - await workspaces.clickAddWorkspaceButton() - - await newWorkspace.typeWorkspaceName(workspaceName) - await newWorkspace.clickOnChe7Stack() - await newWorkspace.clickOnAddOrImportProjectButton() - await newWorkspace.enableSampleCheckbox(sampleName) - await newWorkspace.clickOnAddButton() - await newWorkspace.waitProjectAdding(sampleName) - - await newWorkspace.selectCreateWorkspaceAndProceedEditing() - await workspaceDetails.waitPage(workspaceName); + suite("Create workspace and add plugin", async () => { + test("Open 'New Workspace' page", async () => { + await newWorkspace.openPageByUI(); }) - test("Add 'Java Language Support' plugin to workspace", async () => { - const javaPluginName: string = "Language Support for Java(TM)"; - const execPlugin: string = "Che machine-exec Service"; - - await workspaceDetails.waitTabSelected('Overview') - await workspaceDetails.clickOnTab('Plugins') - await workspaceDetails.waitTabSelected('Plugins') - - - await workspaceDetailsPlugins.waitPluginEnabling(execPlugin) - await workspaceDetailsPlugins.waitPluginDisabling(javaPluginName) - await workspaceDetailsPlugins.clickOnPluginListItemSwitcher(javaPluginName) - await workspaceDetailsPlugins.waitPluginEnabling(javaPluginName) - - await workspaceDetails.waitSaveButton() - await workspaceDetails.clickOnSaveButton() - await workspaceDetails.waitSaveButtonDisappearance() - - await workspaceDetails.clickOnOpenButton() + test(`Create a '${workspaceName}' workspace and proceed editing`, async () => { + await newWorkspace.createWorkspaceAndProceedEditing(workspaceName, 'che7-preview', sampleName) }) - test("Wait IDE availability", async () => { - await ide.waitAndSwitchToIdeFrame() - await ide.waitWorkspaceAndIde(namespace, workspaceName); + test("Add 'Java Language Support' plugin to workspace", async () => { + await workspaceDetailsPlugins.addPluginAndOpenWorkspace(namespace, workspaceName, javaPluginName, pluginId) }) + }) suite("Work with IDE", async () => { @@ -104,9 +78,15 @@ suite("E2E", async () => { let tabTitle: string = "HelloWorld.java"; let filePath: string = `${fileFolderPath}/${tabTitle}` + test("Wait IDE availability", async () => { + await ide.waitWorkspaceAndIde(namespace, workspaceName) + }) + test("Open project tree container", async () => { await projectTree.openProjectTreeContainer(); - await projectTree.waitProjectTreeContainer(); + }) + + test("Wait project imported", async () => { await projectTree.waitProjectImported(sampleName, "src") }) @@ -132,23 +112,11 @@ suite("E2E", async () => { suite("Stop and remove workspace", async () => { test("Stop workspace", async () => { - await dashboard.openDashboard() - await dashboard.clickWorkspacesButton() - await workspaces.waitPage() - await workspaces.waitWorkspaceListItem(workspaceName) - await workspaces.waitWorkspaceWithRunningStatus(workspaceName) - await workspaces.clickOnStopWorkspaceButton(workspaceName) - await workspaces.waitWorkspaceWithStoppedStatus(workspaceName) + await dashboard.stopWorkspaceByUI(workspaceName) }) test("Delete workspace", async () => { - await workspaces.waitPage() - await workspaces.waitWorkspaceListItem(workspaceName) - await workspaces.clickWorkspaceListItem(workspaceName); - await workspaces.clickDeleteButtonOnWorkspaceDetails(); - await workspaces.clickConfirmDeletionButton(); - await workspaces.waitPage() - await workspaces.waitWorkspaceListItemAbcence(workspaceName); + await dashboard.deleteWorkspaceByUI(workspaceName) }) }) diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts index dd58b406917..8af9fd3285f 100644 --- a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts +++ b/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts @@ -26,14 +26,12 @@ export class TestWorkspaceUtil { constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, private readonly rest: rm.RestClient = new rm.RestClient('rest-samples')) { } - public async waitRunningStatus(namespace: string, workspaceName: string) { + public async waitWorkspaceStatus(namespace: string, workspaceName: string, expectedWorkspaceStatus: WorkspaceStatus) { const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${namespace}:${workspaceName}`; const attempts: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS; const polling: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING; for (let i = 0; i < attempts; i++) { - let isWorkspaceStarting: boolean = false; - const response: rm.IRestResponse = await this.rest.get(workspaceStatusApiUrl) if (response.statusCode !== 200) { @@ -43,22 +41,42 @@ export class TestWorkspaceUtil { const workspaceStatus: string = await response.result.status - if (workspaceStatus === WorkspaceStatus.RUNNING) { + if (workspaceStatus === expectedWorkspaceStatus) { return; } - if (workspaceStatus === WorkspaceStatus.STARTING) { - isWorkspaceStarting = true; + await this.driverHelper.wait(polling) + } + + throw new Error(`Exceeded the maximum number of checking attempts, workspace status is different to '${expectedWorkspaceStatus}'`) + } + + public async waitPluginAdding(namespace: string, workspaceName: string, pluginName: string) { + const workspaceStatusApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace/${namespace}:${workspaceName}`; + const attempts: number = TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS; + const polling: number = TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_POLLING; + + for (let i = 0; i < attempts; i++) { + const response: rm.IRestResponse = await this.rest.get(workspaceStatusApiUrl) + + if (response.statusCode !== 200) { + await this.driverHelper.wait(polling) + continue + } + + const machines: string = JSON.stringify(response.result.runtime.machines); + const isPluginPresent: boolean = machines.search(pluginName) > 0; + + if (isPluginPresent) { + break; } - if ((workspaceStatus === WorkspaceStatus.STOPPED) && isWorkspaceStarting) { - throw new Error("Workspace starting process is crushed") + if (i === attempts - 1) { + throw new Error(`Exceeded maximum tries attempts, the '${pluginName}' plugin is not present in the workspace runtime.`) } await this.driverHelper.wait(polling) } - - throw new Error('Exceeded the maximum number of checking attempts, workspace has not been run') } } From b4ce61978e664159dc4881062ea0f2289765ab79 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Thu, 2 May 2019 15:52:45 +0300 Subject: [PATCH 52/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/tests/HappyPath.spec.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index d45871da796..99a9655e8e2 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -9,26 +9,15 @@ **********************************************************************/ import { e2eContainer } from "../inversify.config"; -import { Driver } from "../driver/Driver"; import { TYPES, CLASSES } from "../inversify.types"; -import { DriverHelper } from "../utils/DriverHelper"; -import { By, WebElementCondition, Condition } from "selenium-webdriver"; -import { describe, after, test } from "mocha"; import { LoginPage } from "../pageobjects/login/LoginPage"; import { Dashboard } from "../pageobjects/dashboard/Dashboard"; -import { expect, assert } from 'chai' -import { Workspaces } from "../pageobjects/dashboard/Workspaces"; import { NameGenerator } from "../utils/NameGenerator"; import { NewWorkspace } from "../pageobjects/dashboard/NewWorkspace"; -import { WorkspaceDetails } from "../pageobjects/dashboard/workspace-details/WorkspaceDetails"; import { WorkspaceDetailsPlugins } from "../pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins"; -import { Request, post, get } from "selenium-webdriver/http"; -import { TestWorkspaceUtil } from "../utils/workspace/TestWorkspaceUtil"; import { Ide } from "../pageobjects/ide/Ide"; import { ProjectTree } from "../pageobjects/ide/ProjectTree"; import { Editor } from "../pageobjects/ide/Editor"; -import { TestConstants } from "../TestConstants"; -import * as mocha from 'mocha' const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; @@ -36,24 +25,18 @@ const sampleName: string = "console-java-simple"; const pluginId: string = "org.eclipse.che.vscode-redhat.java"; const javaPluginName: string = "Language Support for Java(TM)"; -const driver: Driver = e2eContainer.get(TYPES.Driver); -const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper); const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard); -const workspaces: Workspaces = e2eContainer.get(CLASSES.Workspaces); const newWorkspace: NewWorkspace = e2eContainer.get(CLASSES.NewWorkspace); -const workspaceDetails: WorkspaceDetails = e2eContainer.get(CLASSES.WorkspaceDetails); const workspaceDetailsPlugins: WorkspaceDetailsPlugins = e2eContainer.get(CLASSES.WorkspaceDetailsPlugins) const ide: Ide = e2eContainer.get(CLASSES.Ide) const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree) const editor: Editor = e2eContainer.get(CLASSES.Editor) -const testWorkspaceUtil: TestWorkspaceUtil = e2eContainer.get(CLASSES.TestWorkspaceUtil) - suite("E2E", async () => { suite("Login and wait dashboard", async () => { - test("login", async () => { + test("Login", async () => { await loginPage.login() }) }) From 1302bdcae2a1e517b4a134459ce282616eef7890 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Fri, 3 May 2019 10:33:42 +0300 Subject: [PATCH 53/57] Do changes according to changed java plugin id Signed-off-by: Ihor Okhrimenko --- typescript-selenium/pageobjects/ide/ProjectTree.ts | 8 +++++++- typescript-selenium/tests/HappyPath.spec.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index a0257444daf..02ebf4d30b3 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -14,12 +14,14 @@ import { CLASSES } from '../../inversify.types'; import { Ide } from './Ide'; import { TestConstants } from '../../TestConstants'; import { By } from 'selenium-webdriver'; +import { Editor } from './Editor'; @injectable() export class ProjectTree { constructor( @inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, - @inject(CLASSES.Ide) private readonly ide: Ide) { } + @inject(CLASSES.Ide) private readonly ide: Ide, + @inject(CLASSES.Editor) private readonly editor: Editor) { } private static readonly PROJECT_TREE_CONTAINER_CSS: string = "#theia-left-side-panel .theia-TreeContainer"; @@ -141,6 +143,10 @@ export class ProjectTree { // open file await this.clickOnItem(`${pathToItem}/${fileName}`, timeout) + + // check file appearance in the editor + await this.editor.waitEditorOpened(timeout) + await this.editor.waitTab(fileName) } async waitProjectImported(projectName: string, rootSubItem: string, attempts = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS, diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 99a9655e8e2..aa4fe0e9f9e 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -22,7 +22,7 @@ import { Editor } from "../pageobjects/ide/Editor"; const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; const sampleName: string = "console-java-simple"; -const pluginId: string = "org.eclipse.che.vscode-redhat.java"; +const pluginId: string = "redhat/java/0.38.0"; const javaPluginName: string = "Language Support for Java(TM)"; const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); From 659d2e56e97c1076e72690527f78b9ccdf7b4f0a Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 6 May 2019 14:58:07 +0300 Subject: [PATCH 54/57] Add new methods to the 'Editor' pageobject Signed-off-by: Ihor Okhrimenko --- typescript-selenium/driver/ChromeDriver.ts | 5 +- typescript-selenium/pageobjects/ide/Editor.ts | 51 +++++++++++++----- .../pageobjects/ide/ProjectTree.ts | 9 +--- typescript-selenium/tests/HappyPath.spec.ts | 53 +++++++++++++++++-- 4 files changed, 94 insertions(+), 24 deletions(-) diff --git a/typescript-selenium/driver/ChromeDriver.ts b/typescript-selenium/driver/ChromeDriver.ts index 01c18659441..ce6783e0860 100644 --- a/typescript-selenium/driver/ChromeDriver.ts +++ b/typescript-selenium/driver/ChromeDriver.ts @@ -21,7 +21,10 @@ export class ChromeDriver implements Driver { constructor() { const isHeadless: boolean = TestConstants.TS_SELENIUM_HEADLESS; - let options: Options = new Options().addArguments('--no-sandbox') + let options: Options = new Options() + .addArguments('--no-sandbox') + .addArguments("--disable-web-security") + .addArguments("--allow-running-insecure-content") if (isHeadless) { options = options.addArguments('headless') diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/typescript-selenium/pageobjects/ide/Editor.ts index f98e43ffb67..7919e79c1c1 100644 --- a/typescript-selenium/pageobjects/ide/Editor.ts +++ b/typescript-selenium/pageobjects/ide/Editor.ts @@ -21,6 +21,7 @@ export class Editor { private static readonly EDITOR_BODY_CSS: string = "#theia-main-content-panel .lines-content"; private static readonly EDITOR_LINES_XPATH: string = "//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line']"; private static readonly SUGGESTION_WIDGET_BODY_CSS: string = "div[widgetId='editor.widget.suggestWidget']"; + private static readonly EDITOR_INTERACTION_СSS: string = ".monaco-editor textarea"; private getEditorLineXpathLocator(lineNumber: number): string { return `(//div[contains(@class,'lines-content')]//div[@class='view-lines']/div[@class='view-line'])[${lineNumber}]` @@ -89,26 +90,50 @@ export class Editor { await this.waitEditorOpened(timeout); } - async getLineText(lineNumber: number, timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { - const lineXpathLocator: By = By.xpath(this.getEditorLineXpathLocator(lineNumber)) + async getLineText(lineNumber: number): Promise { + const lineIndex: number = lineNumber - 1; + const editorText: string = await this.getEditorText(); + const editorLines: string[] = editorText.split('\n'); + const editorLine = editorLines[lineIndex] + '\n'; - const lineText = await this.driverHelper.waitAndGetText(lineXpathLocator, timeout) - return lineText + return editorLine; } - async getEditorText(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT): Promise { - const lines: Array = await this.driverHelper.waitAllPresence(By.xpath(Editor.EDITOR_LINES_XPATH), timeout) - const linesCapacity: number = lines.length + async getEditorText(): Promise { + const interactionContainerLocator: By = By.css(Editor.EDITOR_INTERACTION_СSS); - let editorText = ""; + const editorText: string = await this.driverHelper.waitAndGetValue(interactionContainerLocator) + return editorText; + } + + async moveCursorToLineAndChar(line: number, char: number) { + // set cursor to the 1:1 position + await this.performKeyCombination(Key.chord(Key.CONTROL, Key.HOME)); - for (let i = 1; i <= linesCapacity; i++) { - await this.driverHelper.scrollTo(By.xpath(this.getEditorLineXpathLocator(i)), timeout) - const lineText: string = await this.getLineText(i, timeout) - editorText = editorText + lineText + "\n" + // for ensuring that cursor has been set to the 1:1 position + await this.driverHelper.wait(1000); + + // move cursor to line + for (let i = 1; i < line; i++) { + await this.performKeyCombination(Key.ARROW_DOWN); } - return editorText; + // move cursor to char + for (let i = 1; i < char; i++) { + await this.performKeyCombination(Key.ARROW_RIGHT); + } + } + + private async performKeyCombination(text: string) { + const interactionContainerLocator: By = By.css(Editor.EDITOR_INTERACTION_СSS); + + await this.driverHelper.type(interactionContainerLocator, text); + } + + async type(text: string, line: number) { + await this.moveCursorToLineAndChar(line, 1) + await this.performKeyCombination(text) + } } diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/typescript-selenium/pageobjects/ide/ProjectTree.ts index 02ebf4d30b3..4dd73762916 100644 --- a/typescript-selenium/pageobjects/ide/ProjectTree.ts +++ b/typescript-selenium/pageobjects/ide/ProjectTree.ts @@ -37,16 +37,14 @@ export class ProjectTree { return `${this.getExpandIconCssLocator(itemPath)}:not(.theia-mod-collapsed)`; } - private getExpandIconCssLocator(itemPath: string) { + private getExpandIconCssLocator(itemPath: string): string { return `div[data-node-id='/projects:/projects${itemPath}']`; } - private getTreeItemCssLocator(itemPath: string) { + private getTreeItemCssLocator(itemPath: string): string { return `.theia-TreeNode[title='/projects${itemPath}']` } - - async openProjectTreeContainer(timeout = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { const selectedExplorerButtonLocator: By = By.xpath(Ide.SELECTED_EXPLORER_BUTTON_XPATH) @@ -156,10 +154,7 @@ export class ProjectTree { const rootItemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}`)); const rootSubitemLocator: By = By.css(this.getTreeItemCssLocator(`/${projectName}/${rootSubItem}`)); - - for (let i = 0; i < attempts; i++) { - const isProjectFolderVisible = await this.driverHelper.waitVisibilityBoolean(rootItemLocator, attempts, visibilityItemPolling) if (!isProjectFolderVisible) { diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index aa4fe0e9f9e..796eac94673 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -18,12 +18,16 @@ import { WorkspaceDetailsPlugins } from "../pageobjects/dashboard/workspace-deta import { Ide } from "../pageobjects/ide/Ide"; import { ProjectTree } from "../pageobjects/ide/ProjectTree"; import { Editor } from "../pageobjects/ide/Editor"; +import { DriverHelper } from "../utils/DriverHelper"; +import { By, Key } from "selenium-webdriver"; const workspaceName: string = NameGenerator.generate("wksp-test-", 5); const namespace: string = "che"; const sampleName: string = "console-java-simple"; const pluginId: string = "redhat/java/0.38.0"; const javaPluginName: string = "Language Support for Java(TM)"; +const fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; +const tabTitle: string = "HelloWorld.java"; const loginPage: LoginPage = e2eContainer.get(TYPES.LoginPage); const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard); @@ -33,7 +37,52 @@ const ide: Ide = e2eContainer.get(CLASSES.Ide) const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree) const editor: Editor = e2eContainer.get(CLASSES.Editor) -suite("E2E", async () => { +const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper) + +suite('Editor', async () => { + test('Prepare', async () => { + await driverHelper.navigateTo("http://che-che.192.168.99.100.nip.io/dashboard/#/ide/che/wksp-rffi") + await ide.waitWorkspaceAndIde('che', 'wksp-rffi') + await projectTree.openProjectTreeContainer() + await projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); + }) + + test('Work with editor', async () => { + + await editor.type(" RRRRRRRRRRRRR ", 3); + + let text: string = ""; + + text = text + await editor.getLineText(3); + text = text + await editor.getLineText(3); + text = text + await editor.getLineText(3); + text = text + await editor.getLineText(3); + text = text + await editor.getLineText(3); + text = text + await editor.getLineText(3); + + console.log(text); + + // await editor.clickOnTab(tabTitle) + + // console.log("===>>>> ", 2) + // await editor.waitTabFocused(tabTitle) + + // console.log("===>>>> ", 3) + // await driverHelper.wait(3000) + + // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") + // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") + // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") + // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") + + // console.log("===>>>> ", 5) + + await driverHelper.wait(60000) + }) + +}) + +suite.skip("E2E", async () => { suite("Login and wait dashboard", async () => { test("Login", async () => { @@ -57,8 +106,6 @@ suite("E2E", async () => { }) suite("Work with IDE", async () => { - let fileFolderPath: string = `${sampleName}/src/main/java/org/eclipse/che/examples`; - let tabTitle: string = "HelloWorld.java"; let filePath: string = `${fileFolderPath}/${tabTitle}` test("Wait IDE availability", async () => { From 3e0223c4ebbac4c9c69ace3c047c27600acaee05 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 6 May 2019 15:16:33 +0300 Subject: [PATCH 55/57] Rewert changesrelated to cypress module Signed-off-by: Ihor Okhrimenko --- .../cypress/pageobjects/ide/ProjectTree.ts | 16 ++++++++-------- typescript-selenium/inversify.config.ts | 2 -- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts b/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts index e121c4427d7..2de63f422e2 100644 --- a/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts +++ b/cypress-tests/cypress/pageobjects/ide/ProjectTree.ts @@ -32,14 +32,6 @@ export class ProjectTree { return `${this.getExpandIconLocator(itemPath)}:not(.theia-mod-collapsed)`; } - private getExpandIconLocator(itemPath: string) { - return `div[data-node-id='/projects:/projects${itemPath}']`; - } - - private getTreeItemLocator(itemPath: string) { - return `.theia-TreeNode[title='/projects${itemPath}']` - } - openProjectTreeContainer() { cy.get(Ide.EXPLORER_BUTTON) .should('be.visible') @@ -100,6 +92,14 @@ export class ProjectTree { cy.get(selectedItemLocator).should('be.visible'); } + private getExpandIconLocator(itemPath: string) { + return `div[data-node-id='/projects:/projects${itemPath}']`; + } + + private getTreeItemLocator(itemPath: string) { + return `.theia-TreeNode[title='/projects${itemPath}']` + } + expandItem(itemPath: string) { let expandIconLocator: string = this.getExpandIconLocator(itemPath); let treeItemLocator: string = this.getTreeItemLocator(itemPath); diff --git a/typescript-selenium/inversify.config.ts b/typescript-selenium/inversify.config.ts index 12b0a457c05..25e15613bee 100644 --- a/typescript-selenium/inversify.config.ts +++ b/typescript-selenium/inversify.config.ts @@ -40,6 +40,4 @@ e2eContainer.bind(CLASSES.TestWorkspaceUtil).to(TestWorkspace e2eContainer.bind(CLASSES.ProjectTree).to(ProjectTree).inSingletonScope(); e2eContainer.bind(CLASSES.Editor).to(Editor).inSingletonScope(); - - export { e2eContainer } From b3128c16f2162ef2926640196215e92f01406c93 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 6 May 2019 15:43:10 +0300 Subject: [PATCH 56/57] Do changes according to review Signed-off-by: Ihor Okhrimenko --- typescript-selenium/README.md | 16 +++++++- typescript-selenium/TestConstants.ts | 2 +- typescript-selenium/tests/HappyPath.spec.ts | 45 +-------------------- 3 files changed, 17 insertions(+), 46 deletions(-) diff --git a/typescript-selenium/README.md b/typescript-selenium/README.md index c8586d6016c..ecbe22d8386 100644 --- a/typescript-selenium/README.md +++ b/typescript-selenium/README.md @@ -1,9 +1,23 @@ + +# Module for launch E2E tests related to Che 7 + ## Requirements + - node 8.x - "Chrome" browser 69.x or later +- deployed Che 7 with accessible URL + +## Before launch + +**Perform commands:** + +- ```export TS_SELENIUM_BASE_URL=``` +- ```npm install``` ## Default launch -- npm install && npm test + +- ```npm test``` ## Custom launch + - Use environment variables which described in the **```'TestConstants.ts'```** file diff --git a/typescript-selenium/TestConstants.ts b/typescript-selenium/TestConstants.ts index 400a9784256..49a699be51c 100644 --- a/typescript-selenium/TestConstants.ts +++ b/typescript-selenium/TestConstants.ts @@ -12,7 +12,7 @@ export const TestConstants = { /** * Base URL of the application which should be checked */ - TS_SELENIUM_BASE_URL: process.env.TS_SELENIUM_BASE_URL || "http://che-che.192.168.99.100.nip.io", + TS_SELENIUM_BASE_URL: process.env.TS_SELENIUM_BASE_URL || "http://sample-url", /** * Run browser in "Headless" (hiden) mode, "false" by default. diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/typescript-selenium/tests/HappyPath.spec.ts index 796eac94673..602962f4341 100644 --- a/typescript-selenium/tests/HappyPath.spec.ts +++ b/typescript-selenium/tests/HappyPath.spec.ts @@ -39,50 +39,7 @@ const editor: Editor = e2eContainer.get(CLASSES.Editor) const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper) -suite('Editor', async () => { - test('Prepare', async () => { - await driverHelper.navigateTo("http://che-che.192.168.99.100.nip.io/dashboard/#/ide/che/wksp-rffi") - await ide.waitWorkspaceAndIde('che', 'wksp-rffi') - await projectTree.openProjectTreeContainer() - await projectTree.expandPathAndOpenFile(fileFolderPath, tabTitle); - }) - - test('Work with editor', async () => { - - await editor.type(" RRRRRRRRRRRRR ", 3); - - let text: string = ""; - - text = text + await editor.getLineText(3); - text = text + await editor.getLineText(3); - text = text + await editor.getLineText(3); - text = text + await editor.getLineText(3); - text = text + await editor.getLineText(3); - text = text + await editor.getLineText(3); - - console.log(text); - - // await editor.clickOnTab(tabTitle) - - // console.log("===>>>> ", 2) - // await editor.waitTabFocused(tabTitle) - - // console.log("===>>>> ", 3) - // await driverHelper.wait(3000) - - // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") - // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") - // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") - // await driverHelper.executeJavaScript("document.getElementById('theia-main-content-panel').dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, which: 40 }))") - - // console.log("===>>>> ", 5) - - await driverHelper.wait(60000) - }) - -}) - -suite.skip("E2E", async () => { +suite("E2E", async () => { suite("Login and wait dashboard", async () => { test("Login", async () => { From f4e5ca5b99cde7fbec62ac6536a303a8b36f2ab4 Mon Sep 17 00:00:00 2001 From: Ihor Okhrimenko Date: Mon, 6 May 2019 15:51:00 +0300 Subject: [PATCH 57/57] Rename 'typescript-selenium' module to 'e2e' module Signed-off-by: Ihor Okhrimenko --- .gitignore | 6 +++--- {typescript-selenium => e2e}/README.md | 0 {typescript-selenium => e2e}/TestConstants.ts | 0 {typescript-selenium => e2e}/driver/CheReporter.ts | 0 {typescript-selenium => e2e}/driver/ChromeDriver.ts | 0 {typescript-selenium => e2e}/driver/Driver.ts | 0 {typescript-selenium => e2e}/inversify.config.ts | 0 {typescript-selenium => e2e}/inversify.types.ts | 0 {typescript-selenium => e2e}/mocha.opts | 0 {typescript-selenium => e2e}/package-lock.json | 0 {typescript-selenium => e2e}/package.json | 0 .../pageobjects/dashboard/Dashboard.ts | 0 .../pageobjects/dashboard/NewWorkspace.ts | 0 .../pageobjects/dashboard/Workspaces.ts | 0 .../dashboard/workspace-details/WorkspaceDetails.ts | 0 .../dashboard/workspace-details/WorkspaceDetailsPlugins.ts | 0 {typescript-selenium => e2e}/pageobjects/ide/Editor.ts | 0 {typescript-selenium => e2e}/pageobjects/ide/Ide.ts | 0 {typescript-selenium => e2e}/pageobjects/ide/ProjectTree.ts | 0 {typescript-selenium => e2e}/pageobjects/login/LoginPage.ts | 0 .../pageobjects/login/SingleUserLoginPage.ts | 0 {typescript-selenium => e2e}/tests/HappyPath.spec.ts | 0 {typescript-selenium => e2e}/tsconfig.json | 0 {typescript-selenium => e2e}/utils/DriverHelper.ts | 0 {typescript-selenium => e2e}/utils/NameGenerator.ts | 0 .../utils/workspace/TestWorkspaceUtil.ts | 0 26 files changed, 3 insertions(+), 3 deletions(-) rename {typescript-selenium => e2e}/README.md (100%) rename {typescript-selenium => e2e}/TestConstants.ts (100%) rename {typescript-selenium => e2e}/driver/CheReporter.ts (100%) rename {typescript-selenium => e2e}/driver/ChromeDriver.ts (100%) rename {typescript-selenium => e2e}/driver/Driver.ts (100%) rename {typescript-selenium => e2e}/inversify.config.ts (100%) rename {typescript-selenium => e2e}/inversify.types.ts (100%) rename {typescript-selenium => e2e}/mocha.opts (100%) rename {typescript-selenium => e2e}/package-lock.json (100%) rename {typescript-selenium => e2e}/package.json (100%) rename {typescript-selenium => e2e}/pageobjects/dashboard/Dashboard.ts (100%) rename {typescript-selenium => e2e}/pageobjects/dashboard/NewWorkspace.ts (100%) rename {typescript-selenium => e2e}/pageobjects/dashboard/Workspaces.ts (100%) rename {typescript-selenium => e2e}/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts (100%) rename {typescript-selenium => e2e}/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts (100%) rename {typescript-selenium => e2e}/pageobjects/ide/Editor.ts (100%) rename {typescript-selenium => e2e}/pageobjects/ide/Ide.ts (100%) rename {typescript-selenium => e2e}/pageobjects/ide/ProjectTree.ts (100%) rename {typescript-selenium => e2e}/pageobjects/login/LoginPage.ts (100%) rename {typescript-selenium => e2e}/pageobjects/login/SingleUserLoginPage.ts (100%) rename {typescript-selenium => e2e}/tests/HappyPath.spec.ts (100%) rename {typescript-selenium => e2e}/tsconfig.json (100%) rename {typescript-selenium => e2e}/utils/DriverHelper.ts (100%) rename {typescript-selenium => e2e}/utils/NameGenerator.ts (100%) rename {typescript-selenium => e2e}/utils/workspace/TestWorkspaceUtil.ts (100%) diff --git a/.gitignore b/.gitignore index 39e037b14c9..24afd46eed2 100644 --- a/.gitignore +++ b/.gitignore @@ -83,9 +83,9 @@ docs/assets/imgs plugins/plugin-terminal-ui/node_modules/ plugins/plugin-terminal-ui/build/ requirements.lock -typescript-selenium/dist/ -typescript-selenium/node_modules/ -typescript-selenium/report/ +e2e/dist/ +e2e/node_modules/ +e2e/report/ # Cypress modules # ################### diff --git a/typescript-selenium/README.md b/e2e/README.md similarity index 100% rename from typescript-selenium/README.md rename to e2e/README.md diff --git a/typescript-selenium/TestConstants.ts b/e2e/TestConstants.ts similarity index 100% rename from typescript-selenium/TestConstants.ts rename to e2e/TestConstants.ts diff --git a/typescript-selenium/driver/CheReporter.ts b/e2e/driver/CheReporter.ts similarity index 100% rename from typescript-selenium/driver/CheReporter.ts rename to e2e/driver/CheReporter.ts diff --git a/typescript-selenium/driver/ChromeDriver.ts b/e2e/driver/ChromeDriver.ts similarity index 100% rename from typescript-selenium/driver/ChromeDriver.ts rename to e2e/driver/ChromeDriver.ts diff --git a/typescript-selenium/driver/Driver.ts b/e2e/driver/Driver.ts similarity index 100% rename from typescript-selenium/driver/Driver.ts rename to e2e/driver/Driver.ts diff --git a/typescript-selenium/inversify.config.ts b/e2e/inversify.config.ts similarity index 100% rename from typescript-selenium/inversify.config.ts rename to e2e/inversify.config.ts diff --git a/typescript-selenium/inversify.types.ts b/e2e/inversify.types.ts similarity index 100% rename from typescript-selenium/inversify.types.ts rename to e2e/inversify.types.ts diff --git a/typescript-selenium/mocha.opts b/e2e/mocha.opts similarity index 100% rename from typescript-selenium/mocha.opts rename to e2e/mocha.opts diff --git a/typescript-selenium/package-lock.json b/e2e/package-lock.json similarity index 100% rename from typescript-selenium/package-lock.json rename to e2e/package-lock.json diff --git a/typescript-selenium/package.json b/e2e/package.json similarity index 100% rename from typescript-selenium/package.json rename to e2e/package.json diff --git a/typescript-selenium/pageobjects/dashboard/Dashboard.ts b/e2e/pageobjects/dashboard/Dashboard.ts similarity index 100% rename from typescript-selenium/pageobjects/dashboard/Dashboard.ts rename to e2e/pageobjects/dashboard/Dashboard.ts diff --git a/typescript-selenium/pageobjects/dashboard/NewWorkspace.ts b/e2e/pageobjects/dashboard/NewWorkspace.ts similarity index 100% rename from typescript-selenium/pageobjects/dashboard/NewWorkspace.ts rename to e2e/pageobjects/dashboard/NewWorkspace.ts diff --git a/typescript-selenium/pageobjects/dashboard/Workspaces.ts b/e2e/pageobjects/dashboard/Workspaces.ts similarity index 100% rename from typescript-selenium/pageobjects/dashboard/Workspaces.ts rename to e2e/pageobjects/dashboard/Workspaces.ts diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts b/e2e/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts similarity index 100% rename from typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts rename to e2e/pageobjects/dashboard/workspace-details/WorkspaceDetails.ts diff --git a/typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts b/e2e/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts similarity index 100% rename from typescript-selenium/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts rename to e2e/pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins.ts diff --git a/typescript-selenium/pageobjects/ide/Editor.ts b/e2e/pageobjects/ide/Editor.ts similarity index 100% rename from typescript-selenium/pageobjects/ide/Editor.ts rename to e2e/pageobjects/ide/Editor.ts diff --git a/typescript-selenium/pageobjects/ide/Ide.ts b/e2e/pageobjects/ide/Ide.ts similarity index 100% rename from typescript-selenium/pageobjects/ide/Ide.ts rename to e2e/pageobjects/ide/Ide.ts diff --git a/typescript-selenium/pageobjects/ide/ProjectTree.ts b/e2e/pageobjects/ide/ProjectTree.ts similarity index 100% rename from typescript-selenium/pageobjects/ide/ProjectTree.ts rename to e2e/pageobjects/ide/ProjectTree.ts diff --git a/typescript-selenium/pageobjects/login/LoginPage.ts b/e2e/pageobjects/login/LoginPage.ts similarity index 100% rename from typescript-selenium/pageobjects/login/LoginPage.ts rename to e2e/pageobjects/login/LoginPage.ts diff --git a/typescript-selenium/pageobjects/login/SingleUserLoginPage.ts b/e2e/pageobjects/login/SingleUserLoginPage.ts similarity index 100% rename from typescript-selenium/pageobjects/login/SingleUserLoginPage.ts rename to e2e/pageobjects/login/SingleUserLoginPage.ts diff --git a/typescript-selenium/tests/HappyPath.spec.ts b/e2e/tests/HappyPath.spec.ts similarity index 100% rename from typescript-selenium/tests/HappyPath.spec.ts rename to e2e/tests/HappyPath.spec.ts diff --git a/typescript-selenium/tsconfig.json b/e2e/tsconfig.json similarity index 100% rename from typescript-selenium/tsconfig.json rename to e2e/tsconfig.json diff --git a/typescript-selenium/utils/DriverHelper.ts b/e2e/utils/DriverHelper.ts similarity index 100% rename from typescript-selenium/utils/DriverHelper.ts rename to e2e/utils/DriverHelper.ts diff --git a/typescript-selenium/utils/NameGenerator.ts b/e2e/utils/NameGenerator.ts similarity index 100% rename from typescript-selenium/utils/NameGenerator.ts rename to e2e/utils/NameGenerator.ts diff --git a/typescript-selenium/utils/workspace/TestWorkspaceUtil.ts b/e2e/utils/workspace/TestWorkspaceUtil.ts similarity index 100% rename from typescript-selenium/utils/workspace/TestWorkspaceUtil.ts rename to e2e/utils/workspace/TestWorkspaceUtil.ts