├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── bin └── orchestrator ├── digram.png ├── package-lock.json ├── package.json ├── readme.md └── src ├── analyseReport.js ├── checker.js ├── helper.js ├── logger.js ├── orchestrator.js └── orchestrator.json /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Demo 2 | on: [push] 3 | jobs: 4 | Explore-GitHub-Actions: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 8 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 9 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 10 | - name: Check out repository code 11 | uses: actions/checkout@v2 12 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." 13 | - run: echo "🖥️ The workflow is now ready to test your code on the runner." 14 | - name: List files in the repository 15 | run: | 16 | ls ${{ github.workspace }} 17 | - run: echo "🍏 This job's status is ${{ job.status }}." 18 | 19 | - name: 🚀 Exeucte the test cases 20 | run: | 21 | git clone https://github.com/0xIslamTaha/orchestrator-public-use-case.git 22 | cd orchestrator-public-use-case 23 | npm i 24 | npx orchestrator --config orchestrator.json 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | 5 | ## [1.1.2] - Tue Oct 19 6 | ### Added 7 | - feat(report): Export the executionTimeReprots per browser 8 | - fix(path): orchestrator can accept relative paths 9 | 10 | 11 | ## [1.1.1] - Thu Sep 16 12 | ### Added 13 | - feat(subdir): support the nested direction specs folders (#7). -------------------------------------------------------------------------------- /bin/orchestrator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require = require('esm')(module /*, options*/); 4 | require('../src/orchestrator.js').orchestrator(process.argv); -------------------------------------------------------------------------------- /digram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xIslamTaha/orchestrator/25acdf087d7dd5a8d0b7b4da53176699ae854eb9/digram.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xislamtaha/orchestrator", 3 | "version": "0.1.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/color-name": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", 10 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" 11 | }, 12 | "ansi-regex": { 13 | "version": "5.0.0", 14 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 15 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 16 | }, 17 | "ansi-styles": { 18 | "version": "4.2.1", 19 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", 20 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", 21 | "requires": { 22 | "@types/color-name": "^1.1.1", 23 | "color-convert": "^2.0.1" 24 | } 25 | }, 26 | "arg": { 27 | "version": "4.1.3", 28 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 29 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 30 | }, 31 | "balanced-match": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 34 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 35 | }, 36 | "brace-expansion": { 37 | "version": "1.1.11", 38 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 39 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 40 | "requires": { 41 | "balanced-match": "^1.0.0", 42 | "concat-map": "0.0.1" 43 | } 44 | }, 45 | "camelcase": { 46 | "version": "5.3.1", 47 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 48 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" 49 | }, 50 | "chalk": { 51 | "version": "2.4.2", 52 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 53 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 54 | "requires": { 55 | "ansi-styles": "^3.2.1", 56 | "escape-string-regexp": "^1.0.5", 57 | "supports-color": "^5.3.0" 58 | }, 59 | "dependencies": { 60 | "ansi-styles": { 61 | "version": "3.2.1", 62 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 63 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 64 | "requires": { 65 | "color-convert": "^1.9.0" 66 | } 67 | }, 68 | "color-convert": { 69 | "version": "1.9.3", 70 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 71 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 72 | "requires": { 73 | "color-name": "1.1.3" 74 | } 75 | }, 76 | "color-name": { 77 | "version": "1.1.3", 78 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 79 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 80 | } 81 | } 82 | }, 83 | "cliui": { 84 | "version": "6.0.0", 85 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 86 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 87 | "requires": { 88 | "string-width": "^4.2.0", 89 | "strip-ansi": "^6.0.0", 90 | "wrap-ansi": "^6.2.0" 91 | } 92 | }, 93 | "color-convert": { 94 | "version": "2.0.1", 95 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 96 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 97 | "requires": { 98 | "color-name": "~1.1.4" 99 | } 100 | }, 101 | "color-name": { 102 | "version": "1.1.4", 103 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 104 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 105 | }, 106 | "concat-map": { 107 | "version": "0.0.1", 108 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 109 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 110 | }, 111 | "dateformat": { 112 | "version": "3.0.3", 113 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", 114 | "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" 115 | }, 116 | "decamelize": { 117 | "version": "1.2.0", 118 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 119 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 120 | }, 121 | "emoji-regex": { 122 | "version": "8.0.0", 123 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 124 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 125 | }, 126 | "escape-html": { 127 | "version": "1.0.3", 128 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 129 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 130 | }, 131 | "escape-string-regexp": { 132 | "version": "1.0.5", 133 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 134 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 135 | }, 136 | "esm": { 137 | "version": "3.2.25", 138 | "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", 139 | "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" 140 | }, 141 | "find-up": { 142 | "version": "4.1.0", 143 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 144 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 145 | "requires": { 146 | "locate-path": "^5.0.0", 147 | "path-exists": "^4.0.0" 148 | } 149 | }, 150 | "fs-extra": { 151 | "version": "7.0.1", 152 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", 153 | "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", 154 | "requires": { 155 | "graceful-fs": "^4.1.2", 156 | "jsonfile": "^4.0.0", 157 | "universalify": "^0.1.0" 158 | } 159 | }, 160 | "fs.realpath": { 161 | "version": "1.0.0", 162 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 163 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 164 | }, 165 | "fsu": { 166 | "version": "1.1.1", 167 | "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", 168 | "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==" 169 | }, 170 | "get-caller-file": { 171 | "version": "2.0.5", 172 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 173 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 174 | }, 175 | "glob": { 176 | "version": "7.1.6", 177 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 178 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 179 | "requires": { 180 | "fs.realpath": "^1.0.0", 181 | "inflight": "^1.0.4", 182 | "inherits": "2", 183 | "minimatch": "^3.0.4", 184 | "once": "^1.3.0", 185 | "path-is-absolute": "^1.0.0" 186 | } 187 | }, 188 | "graceful-fs": { 189 | "version": "4.2.4", 190 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 191 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" 192 | }, 193 | "has-flag": { 194 | "version": "3.0.0", 195 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 196 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 197 | }, 198 | "inflight": { 199 | "version": "1.0.6", 200 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 201 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 202 | "requires": { 203 | "once": "^1.3.0", 204 | "wrappy": "1" 205 | } 206 | }, 207 | "inherits": { 208 | "version": "2.0.4", 209 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 210 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 211 | }, 212 | "interpret": { 213 | "version": "1.4.0", 214 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 215 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" 216 | }, 217 | "is-fullwidth-code-point": { 218 | "version": "3.0.0", 219 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 220 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 221 | }, 222 | "js-tokens": { 223 | "version": "4.0.0", 224 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 225 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 226 | }, 227 | "jsonfile": { 228 | "version": "4.0.0", 229 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 230 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 231 | "requires": { 232 | "graceful-fs": "^4.1.6" 233 | } 234 | }, 235 | "locate-path": { 236 | "version": "5.0.0", 237 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 238 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 239 | "requires": { 240 | "p-locate": "^4.1.0" 241 | } 242 | }, 243 | "lodash.isfunction": { 244 | "version": "3.0.9", 245 | "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", 246 | "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" 247 | }, 248 | "loose-envify": { 249 | "version": "1.4.0", 250 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 251 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 252 | "requires": { 253 | "js-tokens": "^3.0.0 || ^4.0.0" 254 | } 255 | }, 256 | "minimatch": { 257 | "version": "3.0.4", 258 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 259 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 260 | "requires": { 261 | "brace-expansion": "^1.1.7" 262 | } 263 | }, 264 | "mochawesome-merge": { 265 | "version": "4.1.0", 266 | "resolved": "https://registry.npmjs.org/mochawesome-merge/-/mochawesome-merge-4.1.0.tgz", 267 | "integrity": "sha512-cDMzSmYu1dRKcr+ZrjjUEuXSiirU8LTG6R8hrAPlZ7zy1EeL7LLpi+a156obxzqh8quTWmYxKtUbTF2PQt0l7A==", 268 | "requires": { 269 | "fs-extra": "^7.0.1", 270 | "glob": "^7.1.6", 271 | "uuid": "^3.3.2", 272 | "yargs": "^15.3.1" 273 | } 274 | }, 275 | "mochawesome-report-generator": { 276 | "version": "5.1.0", 277 | "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-5.1.0.tgz", 278 | "integrity": "sha512-5cI4Jh+sD+jIxc7q94961vnm/6VKDI7TFUPt9dps6oAc4y4WMpEeeOlmgKKM81q2eGaviNUYw+acFalGK6EJ9g==", 279 | "requires": { 280 | "chalk": "^2.4.2", 281 | "dateformat": "^3.0.2", 282 | "escape-html": "^1.0.3", 283 | "fs-extra": "^7.0.0", 284 | "fsu": "^1.0.2", 285 | "lodash.isfunction": "^3.0.8", 286 | "opener": "^1.4.2", 287 | "prop-types": "^15.7.2", 288 | "tcomb": "^3.2.17", 289 | "tcomb-validation": "^3.3.0", 290 | "validator": "^10.11.0", 291 | "yargs": "^13.2.2" 292 | }, 293 | "dependencies": { 294 | "ansi-regex": { 295 | "version": "4.1.0", 296 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 297 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 298 | }, 299 | "ansi-styles": { 300 | "version": "3.2.1", 301 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 302 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 303 | "requires": { 304 | "color-convert": "^1.9.0" 305 | } 306 | }, 307 | "cliui": { 308 | "version": "5.0.0", 309 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 310 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 311 | "requires": { 312 | "string-width": "^3.1.0", 313 | "strip-ansi": "^5.2.0", 314 | "wrap-ansi": "^5.1.0" 315 | } 316 | }, 317 | "color-convert": { 318 | "version": "1.9.3", 319 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 320 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 321 | "requires": { 322 | "color-name": "1.1.3" 323 | } 324 | }, 325 | "color-name": { 326 | "version": "1.1.3", 327 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 328 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 329 | }, 330 | "emoji-regex": { 331 | "version": "7.0.3", 332 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 333 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" 334 | }, 335 | "find-up": { 336 | "version": "3.0.0", 337 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 338 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 339 | "requires": { 340 | "locate-path": "^3.0.0" 341 | } 342 | }, 343 | "is-fullwidth-code-point": { 344 | "version": "2.0.0", 345 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 346 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 347 | }, 348 | "locate-path": { 349 | "version": "3.0.0", 350 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 351 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 352 | "requires": { 353 | "p-locate": "^3.0.0", 354 | "path-exists": "^3.0.0" 355 | } 356 | }, 357 | "p-locate": { 358 | "version": "3.0.0", 359 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 360 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 361 | "requires": { 362 | "p-limit": "^2.0.0" 363 | } 364 | }, 365 | "path-exists": { 366 | "version": "3.0.0", 367 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 368 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" 369 | }, 370 | "string-width": { 371 | "version": "3.1.0", 372 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 373 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 374 | "requires": { 375 | "emoji-regex": "^7.0.1", 376 | "is-fullwidth-code-point": "^2.0.0", 377 | "strip-ansi": "^5.1.0" 378 | } 379 | }, 380 | "strip-ansi": { 381 | "version": "5.2.0", 382 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 383 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 384 | "requires": { 385 | "ansi-regex": "^4.1.0" 386 | } 387 | }, 388 | "wrap-ansi": { 389 | "version": "5.1.0", 390 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 391 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 392 | "requires": { 393 | "ansi-styles": "^3.2.0", 394 | "string-width": "^3.0.0", 395 | "strip-ansi": "^5.0.0" 396 | } 397 | }, 398 | "yargs": { 399 | "version": "13.3.2", 400 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", 401 | "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", 402 | "requires": { 403 | "cliui": "^5.0.0", 404 | "find-up": "^3.0.0", 405 | "get-caller-file": "^2.0.1", 406 | "require-directory": "^2.1.1", 407 | "require-main-filename": "^2.0.0", 408 | "set-blocking": "^2.0.0", 409 | "string-width": "^3.0.0", 410 | "which-module": "^2.0.0", 411 | "y18n": "^4.0.0", 412 | "yargs-parser": "^13.1.2" 413 | } 414 | }, 415 | "yargs-parser": { 416 | "version": "13.1.2", 417 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 418 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 419 | "requires": { 420 | "camelcase": "^5.0.0", 421 | "decamelize": "^1.2.0" 422 | } 423 | } 424 | } 425 | }, 426 | "object-assign": { 427 | "version": "4.1.1", 428 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 429 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 430 | }, 431 | "once": { 432 | "version": "1.4.0", 433 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 434 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 435 | "requires": { 436 | "wrappy": "1" 437 | } 438 | }, 439 | "opener": { 440 | "version": "1.5.1", 441 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", 442 | "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==" 443 | }, 444 | "p-limit": { 445 | "version": "2.3.0", 446 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 447 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 448 | "requires": { 449 | "p-try": "^2.0.0" 450 | } 451 | }, 452 | "p-locate": { 453 | "version": "4.1.0", 454 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 455 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 456 | "requires": { 457 | "p-limit": "^2.2.0" 458 | } 459 | }, 460 | "p-try": { 461 | "version": "2.2.0", 462 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 463 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 464 | }, 465 | "path-exists": { 466 | "version": "4.0.0", 467 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 468 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 469 | }, 470 | "path-is-absolute": { 471 | "version": "1.0.1", 472 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 473 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 474 | }, 475 | "path-parse": { 476 | "version": "1.0.6", 477 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 478 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 479 | }, 480 | "prop-types": { 481 | "version": "15.7.2", 482 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 483 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 484 | "requires": { 485 | "loose-envify": "^1.4.0", 486 | "object-assign": "^4.1.1", 487 | "react-is": "^16.8.1" 488 | } 489 | }, 490 | "react-is": { 491 | "version": "16.13.1", 492 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 493 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" 494 | }, 495 | "rechoir": { 496 | "version": "0.6.2", 497 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 498 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 499 | "requires": { 500 | "resolve": "^1.1.6" 501 | } 502 | }, 503 | "require-directory": { 504 | "version": "2.1.1", 505 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 506 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 507 | }, 508 | "require-main-filename": { 509 | "version": "2.0.0", 510 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 511 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" 512 | }, 513 | "resolve": { 514 | "version": "1.17.0", 515 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", 516 | "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", 517 | "requires": { 518 | "path-parse": "^1.0.6" 519 | } 520 | }, 521 | "set-blocking": { 522 | "version": "2.0.0", 523 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 524 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 525 | }, 526 | "shelljs": { 527 | "version": "0.8.4", 528 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", 529 | "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", 530 | "requires": { 531 | "glob": "^7.0.0", 532 | "interpret": "^1.0.0", 533 | "rechoir": "^0.6.2" 534 | } 535 | }, 536 | "string-width": { 537 | "version": "4.2.0", 538 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 539 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 540 | "requires": { 541 | "emoji-regex": "^8.0.0", 542 | "is-fullwidth-code-point": "^3.0.0", 543 | "strip-ansi": "^6.0.0" 544 | } 545 | }, 546 | "strip-ansi": { 547 | "version": "6.0.0", 548 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 549 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 550 | "requires": { 551 | "ansi-regex": "^5.0.0" 552 | } 553 | }, 554 | "supports-color": { 555 | "version": "5.5.0", 556 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 557 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 558 | "requires": { 559 | "has-flag": "^3.0.0" 560 | } 561 | }, 562 | "tcomb": { 563 | "version": "3.2.29", 564 | "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", 565 | "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==" 566 | }, 567 | "tcomb-validation": { 568 | "version": "3.4.1", 569 | "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", 570 | "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", 571 | "requires": { 572 | "tcomb": "^3.0.0" 573 | } 574 | }, 575 | "universalify": { 576 | "version": "0.1.2", 577 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 578 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" 579 | }, 580 | "uuid": { 581 | "version": "3.4.0", 582 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 583 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 584 | }, 585 | "validator": { 586 | "version": "10.11.0", 587 | "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", 588 | "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" 589 | }, 590 | "which-module": { 591 | "version": "2.0.0", 592 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 593 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 594 | }, 595 | "wrap-ansi": { 596 | "version": "6.2.0", 597 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 598 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 599 | "requires": { 600 | "ansi-styles": "^4.0.0", 601 | "string-width": "^4.1.0", 602 | "strip-ansi": "^6.0.0" 603 | } 604 | }, 605 | "wrappy": { 606 | "version": "1.0.2", 607 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 608 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 609 | }, 610 | "y18n": { 611 | "version": "4.0.0", 612 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 613 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" 614 | }, 615 | "yargs": { 616 | "version": "15.4.1", 617 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 618 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 619 | "requires": { 620 | "cliui": "^6.0.0", 621 | "decamelize": "^1.2.0", 622 | "find-up": "^4.1.0", 623 | "get-caller-file": "^2.0.1", 624 | "require-directory": "^2.1.1", 625 | "require-main-filename": "^2.0.0", 626 | "set-blocking": "^2.0.0", 627 | "string-width": "^4.2.0", 628 | "which-module": "^2.0.0", 629 | "y18n": "^4.0.0", 630 | "yargs-parser": "^18.1.2" 631 | } 632 | }, 633 | "yargs-parser": { 634 | "version": "18.1.3", 635 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 636 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 637 | "requires": { 638 | "camelcase": "^5.0.0", 639 | "decamelize": "^1.2.0" 640 | } 641 | } 642 | } 643 | } 644 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xislamtaha/orchestrator", 3 | "version": "2.1.0", 4 | "description": "Execute cypress specs in parallel across multiple docker containers", 5 | "main": "src/orchestrator.js", 6 | "scripts": { 7 | "test": "echo \"No test specified\"" 8 | }, 9 | "bin": { 10 | "orchestrator": "bin/orchestrator", 11 | "@0xIslamTaha/orchestrator": "bin/orchestrator" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "license": "MIT", 17 | "dependencies": { 18 | "arg": "^4.1.3", 19 | "esm": "^3.2.25", 20 | "mochawesome-merge": "^4.1.0", 21 | "mochawesome-report-generator": "^5.1.0", 22 | "shelljs": "^0.8.4" 23 | }, 24 | "devDependencies": {}, 25 | "files": [ 26 | "bin/", 27 | "src/" 28 | ], 29 | "keywords": [ 30 | "cypress", 31 | "parallel execution", 32 | "chrome", 33 | "firefox", 34 | "parallel", 35 | "cypress parallel" 36 | ], 37 | "homepage": "https://github.com/0xIslamTaha/orchestrator", 38 | "author": { 39 | "name": "Islam Taha", 40 | "email": "islamtaha2012@gmail.com", 41 | "url": "https://twitter.com/0xIslamTaha" 42 | } 43 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 🔥 orchestrator 🔥 2 | ![orchestrator](digram.png) 3 | 4 | Orchestrator executes all cypress specs across n parallel docker containers based on a configuration file. 5 | 6 | ## 😎 Orchestrator Tutorial: 7 | 1- [Cypress parallelization with the Orchestrator — part 1](https://0xislamtaha.medium.com/cypress-parallelization-with-the-orchestrator-part-1-255989094deb) 8 | 9 | 2- [Cypress parallelization with the Orchestrator — part 2 — ShowCase](https://0xislamtaha.medium.com/cypress-parallelization-with-the-orchestrator-part-2-showcase-c78202b17c7a) 10 | 11 | ## 😍 Usecases: 12 | Check the following repo as a public use case. 13 | - [Orchestrator-Public-Use-Case](https://github.com/0xIslamTaha/orchestrator-public-use-case) 14 | 15 | ## ♟️ Orchestrator mechanism: 16 | 17 | * Pares a config file. 18 | * Create (config.parallelizm * config.browsers.length) containers in parallel. 19 | * Recursively list all the specs files 20 | * Split all the specs across all those machines based on their execution time. 21 | * Collect all the execution json reports from those containers. 22 | * Down all the running containers. 23 | * Generate one HTML report that has all specs execution results. 24 | * Analyse the execution time for each spec. 25 | * Generate the execution time reports per browser under ExecutionTimeReport dir. 26 | * In the next run, The orchestrator will split the test cases based on this execution time report to reduce the execution time. 27 | 28 | 29 | ## 🏹 The Splitting mechanism: 30 | The orchestrator can measure and report the execution time for each spec per browser. It will report it as `mochawesome-report/specsExecutionTime-chrome.json` file. If you provided this path as `specsExecutionTimePath` in the next run, The orchestrator will split the specs-based on its execution time to minimize the total execution time 🚀. 31 | 32 | ## ⌨️ Operating Systems: 33 | - Linux: working out of the box. 34 | - MacOS: please exeucte `brew install coreutils` command. 35 | - Windows 10: fully supported via [WSL](https://docs.microsoft.com/en-us/windows/wsl/install). 36 | 37 | 38 | ## 👌 Installation: 39 | * Install from npm 40 | ```bash 41 | npm -g install @0xislamtaha/orchestrator 42 | ``` 43 | 44 | * Install from Github branch 45 | ```bash 46 | npm -g install 0xislamtaha/orchestrator 47 | ``` 48 | 49 | ## 🔑 Requirements to use orchestrator: 50 | 1- docker-compose file with a cypress service. here is an example of it. 51 | 52 | ```yml 53 | 54 | version: '3.8' 55 | services: 56 | cypress-container: 57 | image: 0xislamtaha/cypress-snapshot-image:latest 58 | network_mode: "bridge" 59 | volumes: 60 | - ./cypress/:/cypress_testing/cypress 61 | - ./mochawesome-report:/cypress_testing/mochawesome-report 62 | - /dev/shm:/dev/shm 63 | ``` 64 | 2- use mochawesome as a reporter in cypress.json, just add the following snippet to your cypress.json. 65 | 66 | ```json 67 | { 68 | "reporter": "mochawesome", 69 | "reporterOptions": { 70 | "reportDir": "cypress/report/mochawesome-report", 71 | "overwrite": false, 72 | "html": false, 73 | "json": true 74 | } 75 | } 76 | ``` 77 | 78 | 3- Edit the orchestrator [configuration file](/src/orchestrator.json) with your configuration. Here is the description of each configuration option. 79 | 80 | ``` 81 | - parallelizm: 82 | description: number of container machines per browser 83 | type: Integer 84 | example: 2 85 | 86 | - browser: 87 | description: list of browsers 88 | type: list 89 | example: ["chrome", "firefox"] 90 | 91 | - timeout: 92 | description: timeout of each process of cypress 93 | type: string 94 | example: "20m" 95 | 96 | - environment: 97 | description: enviroment variable to be exported 98 | type: dict 99 | example: {"DOCKER_TAG": "master_283"} 100 | 101 | - preCommands: 102 | description: list of commands to be executed befor the deployment of the cypress containers 103 | type: list 104 | example: ["ls -al", "mkdir -p test"], 105 | 106 | - dockerComposeOptions: 107 | description: docker-compose options to be passed to the docker-compose commands 108 | type: dict 109 | example: {"-p": "project_name"} 110 | 111 | - dockerComposePath: 112 | description: path to the docker compose file. 113 | type: string 114 | example: "/opt/code/github/cypress.docker-compose.yml" 115 | 116 | - specsHomePath: 117 | description: path to the specs dir in the host machine. 118 | type: string 119 | example: "/opt/code/github/cypress/integration/" 120 | 121 | - specsDockerPath: 122 | description: path to the specs dir in the cypress container. 123 | type: string 124 | example: "/cypress/integration" 125 | 126 | - cypressContainerName: 127 | description: the name of cypress service. 128 | type: sting 129 | example: "cypress_service" 130 | 131 | - mochawesomeJSONPath: 132 | description: path to the mochawseom dir in the host machine. 133 | type: string 134 | example: "mochawesome-report/*.json" 135 | 136 | - reportPath: 137 | description: path to save the generated HTML report dir. 138 | type: string 139 | example: "./" 140 | 141 | - specs: 142 | description: array of specific specs to be executed 143 | type: array 144 | example: ["test.js", "test2.js"] 145 | 146 | - analyseReport: 147 | description: boolen value to generate an execution time report. 148 | type: boolen 149 | example: true 150 | 151 | ``` 152 | 153 | ## 🎮 Usage: 154 | 155 | * With your configuration file 156 | ```bash 157 | npx orchestrator --config "/path/to/orchestrator.json" 158 | ``` 159 | 160 | * You can **overwrite** any configuration param on the fly, simply pass the new configuration as a parameter. 161 | ```bash 162 | npx orchestrator --config ./src/orchestrator.json --parallelizm 2 --environment '{"DOCKER_TAG":"master_283"}' --browsers "[chrome, firefox]" --specs "[alerts.js, avatar.js]" 163 | ``` 164 | 165 | ## 📖 Reports: 166 | 167 | The orchestrator generates two reports by default: 168 | - The HTML report under the `mochawesome-report` dir. 169 | - The execution time reports per browser und `ExecutionTimeReport` dir. 170 | 171 | 172 | ## 🎬 To-Do: 173 | * list configuration rather than multiple files for multiple test suites. 174 | * Provide --help option. 175 | 176 | ## License: 177 | The orchestrator is licensed under the MIT license. 178 | -------------------------------------------------------------------------------- /src/analyseReport.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | import { 4 | checkFileIsExisting, 5 | orderBasedOnBrowserDuration, 6 | millisToMinutesAndSeconds, 7 | writeJsonFile, 8 | } from "./helper.js"; 9 | import path from "path"; 10 | import * as lg from "./logger"; 11 | 12 | 13 | const browsers = ["chrome", "firefox"]; 14 | const defaultBrowser = "chrome"; // I will use it in case of there is no browser in the title. 15 | const specs = []; 16 | 17 | function getBrowserFromTitle(title) { 18 | return browsers.find((browser) => title.toLowerCase() === browser); 19 | } 20 | 21 | function intiateSpecData(specName) { 22 | if (!specs.find((spec) => spec.specName === specName)) { 23 | specs.push({ 24 | specName: specName, 25 | data: browsers.map((browser) => { 26 | return { browser: browser, duration: 0 }; 27 | }), 28 | }); 29 | } 30 | } 31 | 32 | function updateSpecData(suites, specName) { 33 | suites.forEach((suite) => { 34 | let browser = getBrowserFromTitle(suite["title"]) || defaultBrowser; 35 | let duration = parseInt(suite["duration"]); 36 | 37 | let spec = specs.find((spec) => spec.specName === specName); 38 | if (spec) 39 | spec.data.find((item) => item.browser === browser).duration += duration; 40 | if (suite.hasOwnProperty("suites")) { 41 | updateSpecData(suite.suites, specName); 42 | } 43 | }); 44 | } 45 | 46 | export function analyseReport(mochaReportPath, executiontimeReportJsonPath) { 47 | if (checkFileIsExisting(mochaReportPath)) { 48 | const executionTimeReportDir = path.dirname(executiontimeReportJsonPath); 49 | const executionTimeReporJson = executiontimeReportJsonPath.split("/").pop(); 50 | const report = require(mochaReportPath); 51 | 52 | report["results"].forEach( result => { 53 | let specFile = result["file"].split("/").pop(); 54 | let suites = result["suites"]; 55 | 56 | intiateSpecData(specFile); 57 | updateSpecData(suites, specFile); 58 | }); 59 | 60 | writeJsonFile(specs, executionTimeReportDir, executionTimeReporJson); 61 | 62 | for (let browser of browsers) { 63 | lg.subStep(`${browser}`); 64 | let data = orderBasedOnBrowserDuration(specs, browser).map((spec) => { 65 | return { 66 | specName: spec.specName, 67 | duration: millisToMinutesAndSeconds( 68 | spec.data.find((item) => item.browser === browser).duration 69 | ), 70 | }; 71 | }); 72 | writeJsonFile(data, executionTimeReportDir, `${executionTimeReporJson.split('.')[0]}-${browser}.json`); 73 | console.table(data); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/checker.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import sh from "shelljs"; 3 | import * as lg from "./logger.js"; 4 | 5 | const requirements = ["docker", "docker-compose", "timeout"]; 6 | 7 | function is(requirement) { 8 | if (sh.exec(`which ${requirement}`, {silent: true}).code) return false; 9 | return true; 10 | } 11 | 12 | function checkRequirements() { 13 | lg.step("Checking the requirements"); 14 | requirements.forEach((requirement) => { 15 | if (!is(requirement)) { 16 | lg.subStep(`${requirement} is not installed ❌`); 17 | throw new Error(`${requirement} is not installed`); 18 | } 19 | lg.subStep(`${requirement} is installed ✔️`); 20 | }); 21 | } 22 | 23 | export { checkRequirements }; 24 | -------------------------------------------------------------------------------- /src/helper.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | import fs from "fs"; 4 | import path from "path"; 5 | 6 | export function checkFileIsExisting(filePath) { 7 | if (fs.existsSync(filePath)) { 8 | return true; 9 | } else { 10 | console.log( 11 | `report does not exist, are you sure there is a json report in ${filePath} path?` 12 | ); 13 | return false; 14 | } 15 | } 16 | 17 | export function parseJsonFile(filePath) { 18 | if (checkFileIsExisting(filePath)) { 19 | return JSON.parse(fs.readFileSync(filePath, "utf8")); 20 | } 21 | } 22 | 23 | export function writeJsonFile(data, outputDir, fileName) { 24 | fs.writeFileSync(path.resolve(outputDir, fileName), JSON.stringify(data)); 25 | } 26 | 27 | export function orderBasedOnBrowserDuration(specs, browser) { 28 | return specs.sort(function (a, b) { 29 | let aDuration = a.data.find((item) => item.browser === browser).duration; 30 | let bDuration = b.data.find((item) => item.browser === browser).duration; 31 | return aDuration - bDuration; 32 | }); 33 | } 34 | 35 | export function millisToMinutesAndSeconds(millis) { 36 | const minutes = Math.floor(millis / 60000); 37 | const seconds = ((millis % 60000) / 1000).toFixed(0); 38 | return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`; 39 | } 40 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | function banner() { 2 | let banner = ` 3 | ██████ ██████ ██████ ██ ██ ███████ ███████ ████████ ██████ █████ ████████ ██████ ██████ 4 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | ██ ██ ██████ ██ ███████ █████ ███████ ██ ██████ ███████ ██ ██ ██ ██████ 6 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 7 | ██████ ██ ██ ██████ ██ ██ ███████ ███████ ██ ██ ██ ██ ██ ██ ██████ ██ ██ 8 | v 2.1.0 @0xIslamTaha 9 | `; 10 | 11 | console.log(banner); 12 | } 13 | 14 | 15 | function step(msg, newLine=false) { 16 | let message; 17 | if (newLine) { 18 | message = `\n[*] ${msg}`; 19 | } else { 20 | message = `[*] ${msg}`; 21 | } 22 | console.log(message); 23 | } 24 | 25 | function subStep(subStep){ 26 | let message = `[-] ${subStep}`; 27 | console.log(message); 28 | } 29 | 30 | export { banner, step, subStep }; 31 | -------------------------------------------------------------------------------- /src/orchestrator.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import sh from "shelljs"; 3 | import marge from "mochawesome-report-generator"; 4 | import { merge } from "mochawesome-merge"; 5 | import fs from "fs"; 6 | import arg from "arg"; 7 | import path from "path"; 8 | import { analyseReport } from "./analyseReport"; 9 | import { 10 | checkFileIsExisting, 11 | parseJsonFile, 12 | orderBasedOnBrowserDuration, 13 | } from "./helper.js"; 14 | import * as lg from "./logger"; 15 | import { checkRequirements } from "./checker"; 16 | 17 | const executionTimeReportDir = "executionTimeReport"; 18 | const executionTimeReportDirPath = path.resolve( 19 | process.cwd(), 20 | executionTimeReportDir 21 | ); 22 | const executiontimeReportJson = "specsExecutionTime.json"; 23 | const executiontimeReportJsonPath = path.join( 24 | executionTimeReportDirPath, 25 | executiontimeReportJson 26 | ); 27 | 28 | function execa(command, flag = true) { 29 | return new Promise((resolve, reject) => 30 | sh.exec(command, function (code, stdout, stderr) { 31 | if (code != 0) { 32 | if ( 33 | flag && 34 | stdout 35 | .concat(stderr) 36 | .toLowerCase() 37 | .includes("cypress failed to make a connection to firefox") 38 | ) { 39 | let oldContName = command.split("--name ")[1].split(" ")[0]; 40 | let newContName = `${oldContName}_${Math.floor( 41 | Math.random() * 100000 42 | )}`; 43 | let cmd = command.replace(oldContName, newContName); 44 | setTimeout(() => execa(cmd, false), 1000); 45 | } else { 46 | return reject(code); 47 | } 48 | } else { 49 | return resolve(code); 50 | } 51 | }) 52 | ); 53 | } 54 | 55 | function parseArgumentsIntoConfig(rawArgs) { 56 | const args = arg( 57 | { 58 | "--config": String, 59 | "-c": "--config", 60 | }, 61 | { 62 | argv: rawArgs.slice(2), 63 | permissive: true, 64 | } 65 | ); 66 | let result = {}; 67 | for (let i = 0; i < args["_"].length; i += 2) { 68 | let key = args["_"][i].replace("--", ""); 69 | let variable = args["_"][i + 1]; 70 | if (variable.includes("{")) { 71 | variable = JSON.parse(variable); 72 | } else if (variable.includes("[")) { 73 | variable = variable 74 | .replace("[", "") 75 | .replace("]", "") 76 | .replace(", ", ",") 77 | .replace(/"|'/g, "") 78 | .split(","); 79 | } 80 | result[key] = variable; 81 | } 82 | return { ...result, ...args }; 83 | } 84 | 85 | function overWriteConfig(args) { 86 | lg.step("Overwrite the config file with the arguments if there is any", true); 87 | const configFile = args["--config"] || path.resolve(__dirname, "orchestrator.json"); 88 | const defaultConfig = JSON.parse(fs.readFileSync(configFile, "utf-8")); 89 | const config = { ...defaultConfig, ...args }; 90 | return config; 91 | } 92 | 93 | function setEnvVars(config) { 94 | lg.step("Export the environment variables"); 95 | Object.keys(config.environment).forEach((key) => { 96 | let value = config.environment[key]; 97 | sh.env[key] = value; 98 | lg.subStep(`${key}=${value}`); 99 | }); 100 | } 101 | 102 | function execPreCommands(config) { 103 | lg.step("Execute the pre commands", true); 104 | config.preCommands.forEach((command) => { 105 | lg.subStep(`~$ ${command}`); 106 | sh.exec(command); 107 | }); 108 | } 109 | 110 | function extractDockerComposeOptions(config) { 111 | let dockerComposeOptions = ""; 112 | Object.keys(config.dockerComposeOptions).forEach((option) => { 113 | dockerComposeOptions = `${dockerComposeOptions} ${option} ${config.dockerComposeOptions[option]}`; 114 | }); 115 | return dockerComposeOptions; 116 | } 117 | 118 | function getListOfSpecs(config, browser) { 119 | let existingSpecs = []; 120 | 121 | if (config.specs.length > 0) { 122 | existingSpecs = [...config.specs]; 123 | } else { 124 | existingSpecs = sh 125 | .ls("-R", config.specsHomePath) 126 | .filter((val) => val.match(/.*ts|js/)); 127 | } 128 | 129 | if (checkFileIsExisting(executiontimeReportJsonPath)) { 130 | let specsExecutionTime = parseJsonFile(executiontimeReportJsonPath); 131 | let browserSpecs = orderBasedOnBrowserDuration( 132 | specsExecutionTime, 133 | browser 134 | ).map((item) => item.specName); 135 | 136 | let specs = browserSpecs.filter((spec) => existingSpecs.includes(spec)); 137 | specs = [ 138 | ...specs, 139 | ...existingSpecs.filter((item) => !specs.includes(item)), 140 | ]; 141 | 142 | return specs; 143 | } else { 144 | return existingSpecs; 145 | } 146 | } 147 | 148 | function removeEmpty(arrays) { 149 | let results = []; 150 | arrays.forEach((array) => { 151 | if (array.length > 0) results.push(array.filter((item) => item !== "")); 152 | }); 153 | return results; 154 | } 155 | 156 | function splitSpecsOverMachines(specs, config) { 157 | let noOfMachines = config.parallelizm * config.browsers.length; 158 | let specsForMachines = []; 159 | 160 | for (let i = 0; i < noOfMachines; i++) { 161 | specsForMachines.push([]); // [ [], [], [] ..] 162 | } 163 | 164 | let _cycles = 0; 165 | while (specs.length > 0) { 166 | for (let i = 0; i < noOfMachines; i++) { 167 | if (specs.length == 0) break; 168 | _cycles % 2 169 | ? specsForMachines[i].push(specs.pop()) 170 | : specsForMachines[i].push(specs.shift()); 171 | } 172 | _cycles++; 173 | } 174 | 175 | return removeEmpty(specsForMachines); 176 | } 177 | 178 | function genearateSpecsCommandsForMachines(config, browser) { 179 | let specsCommandsOverMachines = []; 180 | 181 | let specs = getListOfSpecs(config, browser); 182 | let listOfSpecsOverMachines = splitSpecsOverMachines(specs, config); 183 | 184 | listOfSpecsOverMachines.forEach((listOfspecsPerMachine) => { 185 | let result = ""; 186 | listOfspecsPerMachine.forEach((spec) => { 187 | let specPath = path.join(config.specsDockerPath, spec); 188 | result = `${result},${specPath.replace(/\\/g, "/")}`; 189 | }); 190 | specsCommandsOverMachines.push(result.slice(1)); 191 | }); 192 | 193 | return specsCommandsOverMachines; 194 | } 195 | 196 | function generateSpecsCommandsOverMachinesOrederedByBrowsers(config) { 197 | let specsCommandsOverMachinesOrederedByBrowsers = {}; // {'chrome': [ [] , [] , []], 'firefox': [[], [], []]} 198 | 199 | config.browsers.forEach((browser) => { 200 | specsCommandsOverMachinesOrederedByBrowsers[browser] = 201 | genearateSpecsCommandsForMachines(config, browser); 202 | }); 203 | 204 | return specsCommandsOverMachinesOrederedByBrowsers; 205 | } 206 | 207 | function _constructCypressCommands(config) { 208 | let bashCommands = []; 209 | let specsCommandsOverMachinesOrederedByBrowsers = 210 | generateSpecsCommandsOverMachinesOrederedByBrowsers(config); 211 | let _noOfMachines = 212 | specsCommandsOverMachinesOrederedByBrowsers[config.browsers[0]].length; 213 | 214 | for (let i = 0; i < _noOfMachines; i++) { 215 | let bashCommand = "exit_code=0"; 216 | 217 | let _browsers = i % 2 ? config.browsers : config.browsers.reverse(); 218 | _browsers.forEach((browser) => { 219 | bashCommand = `${bashCommand}; npx cypress run -b ${browser} --headless --spec ${specsCommandsOverMachinesOrederedByBrowsers[browser][i]} || exit_code=$? ; pkill -9 cypress`; 220 | }); 221 | 222 | bashCommand = `${bashCommand} ; exit $exit_code`; 223 | bashCommands.push(bashCommand); 224 | } 225 | return bashCommands; 226 | } 227 | 228 | function upConrainters(config) { 229 | lg.step("Start the cypress containers", true); 230 | let promises = []; 231 | let commands = []; 232 | let [container_name, command] = ["", ""]; 233 | let bashCommands = _constructCypressCommands(config); 234 | let dockerComposeOptions = extractDockerComposeOptions(config); 235 | 236 | bashCommands.forEach((cmd) => { 237 | container_name = `container_${Math.floor( 238 | Math.random() * 100000 239 | )}__${bashCommands.indexOf(cmd)}`; 240 | 241 | command = `timeout --preserve-status ${config.timeout} docker-compose ${dockerComposeOptions} -f ${config.dockerComposePath} run --name ${container_name} ${config.cypressContainerName} bash -c '${cmd}'`; 242 | commands.push(command); 243 | lg.subStep(`~$ ${command}`); 244 | promises.push(execa(command)); 245 | }); 246 | 247 | return promises; 248 | } 249 | 250 | function downContainers(config) { 251 | let dockerComposeOptions = extractDockerComposeOptions(config); 252 | lg.step("Stop the cypress containers", true); 253 | let dockerComposeDown = `docker-compose ${dockerComposeOptions} -f ${config.dockerComposePath} down`; 254 | sh.exec(dockerComposeDown); 255 | } 256 | 257 | function generateReport(config) { 258 | lg.step("Generate the reports", true); 259 | return merge({ files: [config.mochawesomeJSONPath] }) 260 | .then((report) => { 261 | lg.subStep(`HTML report: ${config.reportPath}/mochawesome.html`); 262 | marge.create(report, { 263 | reportDir: config.reportPath, 264 | charts: true, 265 | saveJson: true, 266 | }); 267 | }) 268 | .then(() => { 269 | if (config.analyseReport) { 270 | if (!fs.existsSync(executionTimeReportDirPath)) { 271 | sh.mkdir(executionTimeReportDirPath); 272 | } 273 | lg.subStep( 274 | `Execution time report: ${executionTimeReportDir}/${executiontimeReportJson}` 275 | ); 276 | _analyseReport(config); 277 | } 278 | }); 279 | } 280 | 281 | function _analyseReport(config) { 282 | let mergedMochawesomeJSONPath = ""; 283 | if (config.reportPath.includes(process.cwd())) { 284 | mergedMochawesomeJSONPath = path.resolve( 285 | process.cwd(), 286 | config.reportPath, 287 | "mochawesome.json" 288 | ); 289 | } else { 290 | mergedMochawesomeJSONPath = path.resolve( 291 | config.reportPath, 292 | "mochawesome.json" 293 | ); 294 | } 295 | 296 | analyseReport(mergedMochawesomeJSONPath, executiontimeReportJsonPath); 297 | } 298 | 299 | function afterPromises(config, timer) { 300 | downContainers(config); 301 | generateReport(config).then(() => { 302 | console.timeEnd(timer); 303 | }); 304 | } 305 | 306 | export async function orchestrator(rawArgs) { 307 | lg.banner(); 308 | checkRequirements(); 309 | 310 | let orchestratorTime = "\n[*] Total execution time"; 311 | let config = overWriteConfig(parseArgumentsIntoConfig(rawArgs)); 312 | 313 | console.time(orchestratorTime); 314 | setEnvVars(config); 315 | execPreCommands(config); 316 | 317 | Promise.allSettled(upConrainters(config)).then((promises) => { 318 | afterPromises(config, orchestratorTime); 319 | const failedPromises = promises.filter( 320 | (promise) => promise.status === "rejected" 321 | ); 322 | if (failedPromises.length) { 323 | setTimeout(() => { 324 | lg.step("Exit code: 1"); 325 | sh.exit(1); 326 | }, 5000); 327 | } 328 | }); 329 | } 330 | -------------------------------------------------------------------------------- /src/orchestrator.json: -------------------------------------------------------------------------------- 1 | { 2 | "parallelizm": 2, 3 | "browsers": ["chrome", "firefox"], 4 | "timeout": "20m", 5 | "environment": { 6 | "DOCKER_TAG": "master_283" 7 | }, 8 | "preCommands": [ 9 | "echo 'START ORCHESTRATOR'", 10 | "rm -rf cypress/report/* #Remove the old reports", 11 | "mkdir -p mochawesome-report" 12 | ], 13 | "dockerComposeOptions": { 14 | "-p": "orchestator_public_use_case_project_name" 15 | }, 16 | "dockerComposePath": "/opt/code/github/design-system/cypress.docker-compose.yml", 17 | "specsHomePath": "/opt/code/github/design-system/tests/cypress/storybook/", 18 | "specsDockerPath": "tests/cypress/storybook/", 19 | "cypressContainerName": "cypress_container", 20 | "mochawesomeJSONPath": "/opt/code/github/design-system/tests/report/mochawesome-report/*.json", 21 | "reportPath": "mochawesome-report", 22 | "specs": [], 23 | "analyseReport": true, 24 | "specsExecutionTimePath": "mochawesome-report/specsExecutionTime-chrome.json" 25 | } 26 | --------------------------------------------------------------------------------