diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index a7b3ecfb..d6370f10 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -20,6 +20,15 @@ jobs: - name: "Checkout" uses: "actions/checkout@v2.0.0" + - name: "Setup NodeJS" + uses: actions/setup-node@v1 + with: + node-version: '14.x' + + - name: "Install dependencies" + run: npm install + working-directory: "tests" + - name: "Setup .env file" run: cp .env.template .env @@ -27,10 +36,10 @@ jobs: run: sudo chown 1000:1000 -R . - name: "Start environment" - run: docker-compose up -d + run: LIVE_RELOAD=0 docker-compose up -d - name: "Wait for environment to build (and downloading testcafe image)" - run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully" + run: (docker-compose -f docker-compose.testcafe.yml build &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully" # - name: "temp debug: display logs" # run: docker-compose logs @@ -42,7 +51,7 @@ jobs: # run: docker-compose logs -f pusher | grep -q "WorkAdventure starting on port" - name: "Run tests" - run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe + run: PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe - name: Upload failed tests if: ${{ failure() }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8bbbc93e..e5d406cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,9 +59,43 @@ $ docker-compose exec back yarn run pretty WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test. -Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine). +Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine), or an end-to-end test (we use Testcafe). If you are providing a new feature, you should setup a test map in the `maps/tests` directory. The test map should contain -some description text describing how to test the feature. Finally, you should modify the `maps/tests/index.html` file -to add a reference to your newly created test map. +some description text describing how to test the feature. +* if the features is meant to be manually tested, you should modify the `maps/tests/index.html` file to add a reference + to your newly created test map +* if the features can be automatically tested, please provide a testcafe test + +#### Running testcafe tests + +End-to-end tests are available in the "/tests" directory. + +To run these tests locally: + +```console +$ LIVE_RELOAD=0 docker-compose up -d +$ cd tests +$ npm install +$ npm run test +``` + +Note: If your tests fail on a Javascript error in "sockjs", this is due to the +Webpack live reload. The Webpack live reload feature is conflicting with testcafe. This is why we recommend starting +WorkAdventure with the `LIVE_RELOAD=0` environment variable. + +End-to-end tests can take a while to run. To run only one test, use: + +```console +$ npm run test -- tests/[name of the test file].ts +``` + +You can also run the tests inside a container (but you will not have visual feedbacks on your test, so we recommend using +the local tests). + +```console +$ LIVE_RELOAD=0 docker-compose up -d +# Wait 2-3 minutes for the environment to start, then: +$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up +``` diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index 88287753..8fbf82e4 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -12,43 +12,52 @@ export class DebugController { getDump() { this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { - const query = parse(req.getQuery()); + (async () => { + const query = parse(req.getQuery()); - if (query.token !== ADMIN_API_TOKEN) { - return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); - } + if (query.token !== ADMIN_API_TOKEN) { + return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); + } - return res - .writeStatus("200 OK") - .writeHeader("Content-Type", "application/json") - .end( - stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => { - if (key === "listeners") { - return "Listeners"; - } - if (key === "socket") { - return "Socket"; - } - if (key === "batchedMessages") { - return "BatchedMessages"; - } - if (value instanceof Map) { - const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any - for (const [mapKey, mapValue] of value.entries()) { - obj[mapKey] = mapValue; + return res + .writeStatus("200 OK") + .writeHeader("Content-Type", "application/json") + .end( + stringify( + await Promise.all(socketManager.getWorlds().values()), + (key: unknown, value: unknown) => { + if (key === "listeners") { + return "Listeners"; + } + if (key === "socket") { + return "Socket"; + } + if (key === "batchedMessages") { + return "BatchedMessages"; + } + if (value instanceof Map) { + const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any + for (const [mapKey, mapValue] of value.entries()) { + obj[mapKey] = mapValue; + } + return obj; + } else if (value instanceof Set) { + const obj: Array = []; + for (const [setKey, setValue] of value.entries()) { + obj.push(setValue); + } + return obj; + } else { + return value; + } } - return obj; - } else if (value instanceof Set) { - const obj: Array = []; - for (const [setKey, setValue] of value.entries()) { - obj.push(setValue); - } - return obj; - } else { - return value; - } - }) - ); + ) + ); + })().catch((e) => { + console.error(e); + res.writeStatus("500"); + res.end("An error occurred"); + }); }); } } diff --git a/docker-compose.testcafe.yml b/docker-compose.testcafe.yml index 774477a1..e61db21d 100644 --- a/docker-compose.testcafe.yml +++ b/docker-compose.testcafe.yml @@ -1,12 +1,19 @@ -version: "3" +version: "3.5" services: testcafe: - image: testcafe/testcafe:1.17.1 - working_dir: /tests + build: tests/ + working_dir: /project/tests + command: + - --dev + # Run as root to have the right to access /var/run/docker.sock + user: root environment: BROWSER: "chromium --use-fake-device-for-media-stream" + PROJECT_DIR: ${PROJECT_DIR} + ADMIN_API_TOKEN: ${ADMIN_API_TOKEN} volumes: - - ./tests:/tests + - ./:/project - ./maps:/maps + - /var/run/docker.sock:/var/run/docker.sock # security_opt: # - seccomp:unconfined diff --git a/docker-compose.yaml b/docker-compose.yaml index e5e6489c..17e04f7c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,20 +1,21 @@ -version: "3" +version: "3.5" services: reverse-proxy: - image: traefik:v2.0 + image: traefik:v2.5 command: - --api.insecure=true - --providers.docker - --entryPoints.web.address=:80 - --entryPoints.websecure.address=:443 + - "--providers.docker.exposedbydefault=false" ports: - "80:80" - "443:443" # The Web UI (enabled by --api.insecure=true) - "8080:8080" - depends_on: - - back - - front + #depends_on: + # - back + # - front volumes: - /var/run/docker.sock:/var/run/docker.sock networks: @@ -51,10 +52,12 @@ services: MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER" + LIVE_RELOAD: "$LIVE_RELOAD:-true" command: yarn run start volumes: - ./front:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)" - "traefik.http.routers.front.entryPoints=web" - "traefik.http.services.front.loadbalancer.server.port=8080" @@ -87,6 +90,7 @@ services: volumes: - ./pusher:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.pusher.rule=Host(`pusher.workadventure.localhost`)" - "traefik.http.routers.pusher.entryPoints=web" - "traefik.http.services.pusher.loadbalancer.server.port=8080" @@ -111,6 +115,7 @@ services: volumes: - ./maps:/var/www/html labels: + - "traefik.enable=true" - "traefik.http.routers.maps.rule=Host(`maps.workadventure.localhost`)" - "traefik.http.routers.maps.entryPoints=web,traefik" - "traefik.http.services.maps.loadbalancer.server.port=80" @@ -142,6 +147,7 @@ services: volumes: - ./back:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.back.rule=Host(`api.workadventure.localhost`)" - "traefik.http.routers.back.entryPoints=web" - "traefik.http.services.back.loadbalancer.server.port=8080" @@ -160,6 +166,7 @@ services: volumes: - ./uploader:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.uploader.rule=Host(`uploader.workadventure.localhost`)" - "traefik.http.routers.uploader.entryPoints=web" - "traefik.http.services.uploader.loadbalancer.server.port=8080" @@ -187,6 +194,7 @@ services: redisinsight: image: redislabs/redisinsight:latest labels: + - "traefik.enable=true" - "traefik.http.routers.redisinsight.rule=Host(`redis.workadventure.localhost`)" - "traefik.http.routers.redisinsight.entryPoints=web" - "traefik.http.services.redisinsight.loadbalancer.server.port=8001" @@ -198,6 +206,7 @@ services: icon: image: matthiasluedtke/iconserver:v3.13.0 labels: + - "traefik.enable=true" - "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)" - "traefik.http.routers.icon.entryPoints=web" - "traefik.http.services.icon.loadbalancer.server.port=8080" diff --git a/front/package.json b/front/package.json index 55ef493e..6342a247 100644 --- a/front/package.json +++ b/front/package.json @@ -56,6 +56,7 @@ "queue-typescript": "^1.0.1", "quill": "1.3.6", "quill-delta-to-html": "^0.12.0", + "retry-axios": "^2.6.0", "rxjs": "^6.6.3", "simple-peer": "^9.11.0", "socket.io-client": "^2.3.0", diff --git a/front/src/Components/UI/ErrorDialog.svelte b/front/src/Components/UI/ErrorDialog.svelte index 0a0dc7a8..4e03edb4 100644 --- a/front/src/Components/UI/ErrorDialog.svelte +++ b/front/src/Components/UI/ErrorDialog.svelte @@ -1,8 +1,8 @@ @@ -11,12 +11,14 @@

Error

{#each $errorStore as error} -

{error}

+

{error.message}

{/each}
-
- -
+ {#if $hasClosableMessagesInErrorStore} +
+ +
+ {/if}