merge latest dev
This commit is contained in:
parent
1873ac1836
commit
3b98888c3a
51
.github/workflows/end_to_end_tests.yml
vendored
Normal file
51
.github/workflows/end_to_end_tests.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
|
||||
|
||||
name: "End to end tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
end-to-end-tests:
|
||||
name: "End-to-end testcafe tests"
|
||||
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2.0.0"
|
||||
|
||||
- name: "Setup .env file"
|
||||
run: cp .env.template .env
|
||||
|
||||
- name: "Edit ownership of file for test cases"
|
||||
run: sudo chown 1000:1000 -R .
|
||||
|
||||
- name: "Start environment"
|
||||
run: 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 -m 1 "Compiled successfully"
|
||||
|
||||
- name: "Run tests"
|
||||
run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||
|
||||
- name: Upload failed tests
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: my-artifact
|
||||
path: './tests/screenshots/'
|
||||
|
||||
- name: Display state
|
||||
if: ${{ failure() }}
|
||||
run: docker-compose ps
|
||||
|
||||
- name: Display logs
|
||||
if: ${{ failure() }}
|
||||
run: docker-compose logs
|
@ -14,7 +14,7 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||
export const REDIS_HOST = process.env.REDIS_HOST || undefined;
|
||||
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
||||
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
||||
export const DEBUG_BACK_IGNORE_LOCAL = process.env.DEBUG_BACK_IGNORE_LOCAL ? process.env.DEBUG_BACK_IGNORE_LOCAL == "true" : false;
|
||||
export const STORE_VARIABLES_FOR_LOCAL_MAPS = process.env.STORE_VARIABLES_FOR_LOCAL_MAPS === "true";
|
||||
|
||||
export {
|
||||
MINIMUM_DISTANCE,
|
||||
|
@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
|
||||
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { LocalUrlError } from "../Services/LocalUrlError";
|
||||
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
||||
import { VariableError } from "../Services/VariableError";
|
||||
|
||||
export type ConnectCallback = (user: User, group: Group) => void;
|
||||
export type DisconnectCallback = (user: User, group: Group) => void;
|
||||
@ -336,30 +337,62 @@ export class GameRoom {
|
||||
// First, let's check if "user" is allowed to modify the variable.
|
||||
const variableManager = await this.getVariableManager();
|
||||
|
||||
const readableBy = variableManager.setVariable(name, value, user);
|
||||
try {
|
||||
const readableBy = variableManager.setVariable(name, value, user);
|
||||
|
||||
// If the variable was not changed, let's not dispatch anything.
|
||||
if (readableBy === false) {
|
||||
return;
|
||||
}
|
||||
// If the variable was not changed, let's not dispatch anything.
|
||||
if (readableBy === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: should we batch those every 100ms?
|
||||
const variableMessage = new VariableWithTagMessage();
|
||||
variableMessage.setName(name);
|
||||
variableMessage.setValue(value);
|
||||
if (readableBy) {
|
||||
variableMessage.setReadableby(readableBy);
|
||||
}
|
||||
// TODO: should we batch those every 100ms?
|
||||
const variableMessage = new VariableWithTagMessage();
|
||||
variableMessage.setName(name);
|
||||
variableMessage.setValue(value);
|
||||
if (readableBy) {
|
||||
variableMessage.setReadableby(readableBy);
|
||||
}
|
||||
|
||||
const subMessage = new SubToPusherRoomMessage();
|
||||
subMessage.setVariablemessage(variableMessage);
|
||||
const subMessage = new SubToPusherRoomMessage();
|
||||
subMessage.setVariablemessage(variableMessage);
|
||||
|
||||
const batchMessage = new BatchToPusherRoomMessage();
|
||||
batchMessage.addPayload(subMessage);
|
||||
const batchMessage = new BatchToPusherRoomMessage();
|
||||
batchMessage.addPayload(subMessage);
|
||||
|
||||
// Dispatch the message on the room listeners
|
||||
for (const socket of this.roomListeners) {
|
||||
socket.write(batchMessage);
|
||||
// Dispatch the message on the room listeners
|
||||
for (const socket of this.roomListeners) {
|
||||
socket.write(batchMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof VariableError) {
|
||||
// Ok, we have an error setting a variable. Either the user is trying to hack the map... or the map
|
||||
// is not up to date. So let's try to reload the map from scratch.
|
||||
if (this.variableManagerLastLoad === undefined) {
|
||||
throw e;
|
||||
}
|
||||
const lastLoaded = new Date().getTime() - this.variableManagerLastLoad.getTime();
|
||||
if (lastLoaded < 10000) {
|
||||
console.log(
|
||||
'An error occurred while setting the "' +
|
||||
name +
|
||||
"\" variable. But we tried to reload the map less than 10 seconds ago, so let's fail."
|
||||
);
|
||||
// Do not try to reload if we tried to reload less than 10 seconds ago.
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Reset the variable manager
|
||||
this.variableManagerPromise = undefined;
|
||||
this.mapPromise = undefined;
|
||||
|
||||
console.log(
|
||||
'An error occurred while setting the "' + name + "\" variable. Let's reload the map and try again"
|
||||
);
|
||||
// Try to set the variable again!
|
||||
await this.setVariable(name, value, user);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,9 +482,11 @@ export class GameRoom {
|
||||
}
|
||||
|
||||
private variableManagerPromise: Promise<VariablesManager> | undefined;
|
||||
private variableManagerLastLoad: Date | undefined;
|
||||
|
||||
private getVariableManager(): Promise<VariablesManager> {
|
||||
if (!this.variableManagerPromise) {
|
||||
this.variableManagerLastLoad = new Date();
|
||||
this.variableManagerPromise = this.getMap()
|
||||
.then((map) => {
|
||||
const variablesManager = new VariablesManager(this.roomUrl, map);
|
||||
|
@ -5,13 +5,13 @@ import { promisify } from "util";
|
||||
import { LocalUrlError } from "./LocalUrlError";
|
||||
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
||||
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
||||
import { DEBUG_BACK_IGNORE_LOCAL } from "../Enum/EnvironmentVariable";
|
||||
import { STORE_VARIABLES_FOR_LOCAL_MAPS } from "../Enum/EnvironmentVariable";
|
||||
|
||||
class MapFetcher {
|
||||
async fetchMap(mapUrl: string): Promise<ITiledMap> {
|
||||
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
|
||||
|
||||
if (!DEBUG_BACK_IGNORE_LOCAL && await this.isLocalUrl(mapUrl)) {
|
||||
if ((await this.isLocalUrl(mapUrl)) && !STORE_VARIABLES_FOR_LOCAL_MAPS) {
|
||||
throw new LocalUrlError('URL for map "' + mapUrl + '" targets a local map');
|
||||
}
|
||||
|
||||
|
9
back/src/Services/VariableError.ts
Normal file
9
back/src/Services/VariableError.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Errors related to variable handling.
|
||||
*/
|
||||
export class VariableError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, VariableError.prototype);
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import {
|
||||
import { User } from "_Model/User";
|
||||
import { variablesRepository } from "./Repository/VariablesRepository";
|
||||
import { redisClient } from "./RedisClient";
|
||||
import { VariableError } from "./VariableError";
|
||||
|
||||
interface Variable {
|
||||
defaultValue?: string;
|
||||
@ -174,11 +175,13 @@ export class VariablesManager {
|
||||
if (this.variableObjects) {
|
||||
variableObject = this.variableObjects.get(name);
|
||||
if (variableObject === undefined) {
|
||||
throw new Error('Trying to set a variable "' + name + '" that is not defined as an object in the map.');
|
||||
throw new VariableError(
|
||||
'Trying to set a variable "' + name + '" that is not defined as an object in the map.'
|
||||
);
|
||||
}
|
||||
|
||||
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
|
||||
throw new Error(
|
||||
throw new VariableError(
|
||||
'Trying to set a variable "' +
|
||||
name +
|
||||
'". User "' +
|
||||
|
@ -38,6 +38,7 @@ services:
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||
PUSHER_URL: //pusher.${DOMAIN}
|
||||
ICON_URL: //icon.${DOMAIN}
|
||||
TURN_SERVER: "${TURN_SERVER}"
|
||||
TURN_USER: "${TURN_USER}"
|
||||
TURN_PASSWORD: "${TURN_PASSWORD}"
|
||||
@ -98,3 +99,15 @@ services:
|
||||
- "traefik.http.routers.back-ssl.service=back"
|
||||
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
|
||||
restart: unless-stopped
|
||||
|
||||
icon:
|
||||
image: matthiasluedtke/iconserver:v3.13.0
|
||||
labels:
|
||||
- "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)"
|
||||
- "traefik.http.routers.icon.entryPoints=web,traefik"
|
||||
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)"
|
||||
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.icon-ssl.tls=true"
|
||||
- "traefik.http.routers.icon-ssl.service=icon"
|
||||
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"
|
||||
|
@ -30,6 +30,7 @@ services:
|
||||
UPLOADER_URL: /uploader
|
||||
ADMIN_URL: /admin
|
||||
MAPS_URL: /maps
|
||||
ICON_URL: /icon
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||
@ -61,6 +62,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
# wait for files generated by "messages" container to exists
|
||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
SECRET_KEY: yourSecretKey
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
@ -121,6 +124,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
# wait for files generated by "messages" container to exists
|
||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||
SECRET_KEY: yourSecretKey
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ALLOW_ARTILLERY: "true"
|
||||
@ -181,6 +186,20 @@ services:
|
||||
redis:
|
||||
image: redis:6
|
||||
|
||||
icon:
|
||||
image: matthiasluedtke/iconserver:v3.13.0
|
||||
labels:
|
||||
- "traefik.http.middlewares.strip-icon-prefix.stripprefix.prefixes=/icon"
|
||||
- "traefik.http.routers.icon.rule=PathPrefix(`/icon`)"
|
||||
- "traefik.http.routers.icon.middlewares=strip-icon-prefix@docker"
|
||||
- "traefik.http.routers.icon.entryPoints=web"
|
||||
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.icon-ssl.rule=PathPrefix(`/icon`)"
|
||||
- "traefik.http.routers.icon-ssl.middlewares=strip-icon-prefix@docker"
|
||||
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.icon-ssl.tls=true"
|
||||
- "traefik.http.routers.icon-ssl.service=icon"
|
||||
|
||||
# coturn:
|
||||
# image: coturn/coturn:4.5.2
|
||||
# command:
|
||||
|
12
docker-compose.testcafe.yml
Normal file
12
docker-compose.testcafe.yml
Normal file
@ -0,0 +1,12 @@
|
||||
version: "3"
|
||||
services:
|
||||
testcafe:
|
||||
image: testcafe/testcafe:1.17.1
|
||||
working_dir: /tests
|
||||
environment:
|
||||
BROWSER: "chromium --use-fake-device-for-media-stream"
|
||||
volumes:
|
||||
- ./tests:/tests
|
||||
- ./maps:/maps
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
@ -17,6 +17,12 @@ services:
|
||||
- front
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
- 'play.workadventure.localhost'
|
||||
- 'pusher.workadventure.localhost'
|
||||
- 'maps.workadventure.localhost'
|
||||
|
||||
front:
|
||||
image: thecodingmachine/nodejs:14
|
||||
@ -63,6 +69,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "socket:*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
# wait for files generated by "messages" container to exists
|
||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
SECRET_KEY: yourSecretKey
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
@ -117,6 +125,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
# wait for files generated by "messages" container to exists
|
||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||
SECRET_KEY: yourSecretKey
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ALLOW_ARTILLERY: "true"
|
||||
@ -127,6 +137,7 @@ services:
|
||||
MAX_PER_GROUP: "MAX_PER_GROUP"
|
||||
REDIS_HOST: redis
|
||||
NODE_ENV: development
|
||||
STORE_VARIABLES_FOR_LOCAL_MAPS: "true"
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
|
@ -4,7 +4,7 @@ declare let window: any;
|
||||
|
||||
class AnalyticsClient {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private posthogPromise: Promise<any>;
|
||||
private posthogPromise: Promise<any>|undefined;
|
||||
|
||||
constructor() {
|
||||
if (POSTHOG_API_KEY && POSTHOG_URL) {
|
||||
@ -14,90 +14,78 @@ class AnalyticsClient {
|
||||
window.posthog = posthog;
|
||||
return posthog;
|
||||
});
|
||||
} else {
|
||||
this.posthogPromise = Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
identifyUser(uuid: string, email: string | null) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.identify(uuid, { uuid, email, wa: true });
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
loggedWithSso() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-logged-sso");
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
loggedWithToken() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-logged-token");
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
enteredRoom(roomId: string, roomGroup: string | null) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("$pageView", { roomId, roomGroup });
|
||||
posthog.capture("enteredRoom");
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
openedMenu() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-opened-menu");
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
launchEmote(emote: string) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-emote-launch", { emote });
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
enteredJitsi(roomName: string, roomId: string) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
validationName() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-name-validation");
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
validationWoka(scene: string) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-woka-validation", { scene });
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
|
||||
validationVideo() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
?.then((posthog) => {
|
||||
posthog.capture("wa-video-validation");
|
||||
})
|
||||
.catch();
|
||||
});
|
||||
}
|
||||
}
|
||||
export const analyticsClient = new AnalyticsClient();
|
||||
|
@ -321,8 +321,9 @@ class ConnectionManager {
|
||||
this.localUser = new LocalUser(userUuid, textures, email);
|
||||
localUserStore.saveUser(this.localUser);
|
||||
this.authToken = authToken;
|
||||
gameManager.setPlayerName(username);
|
||||
|
||||
if (username) {
|
||||
gameManager.setPlayerName(username);
|
||||
}
|
||||
//user connected, set connected store for menu at true
|
||||
userIsConnected.set(true);
|
||||
}
|
||||
|
@ -122,10 +122,12 @@ class LocalUserStore {
|
||||
|
||||
setLastRoomUrl(roomUrl: string): void {
|
||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||
caches.open(cacheAPIIndex).then((cache) => {
|
||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||
});
|
||||
if ('caches' in window) {
|
||||
caches.open(cacheAPIIndex).then((cache) => {
|
||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
getLastRoomUrl(): string {
|
||||
return (
|
||||
@ -133,6 +135,9 @@ class LocalUserStore {
|
||||
);
|
||||
}
|
||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||
if (!('caches' in window)) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return caches.open(cacheAPIIndex).then((cache) => {
|
||||
return cache.match(`/${lastRoomUrl}`).then((res) => {
|
||||
return res?.json().then((data) => {
|
||||
|
@ -162,6 +162,7 @@ export class EmbeddedWebsiteManager {
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = absoluteUrl;
|
||||
iframe.tabIndex = -1;
|
||||
iframe.style.width = embeddedWebsiteEvent.position.width + "px";
|
||||
iframe.style.height = embeddedWebsiteEvent.position.height + "px";
|
||||
iframe.style.margin = "0";
|
||||
|
@ -52,13 +52,15 @@ export class StartPositionCalculator {
|
||||
if (!selectedOrDefaultLayer) {
|
||||
selectedOrDefaultLayer = defaultStartLayerName;
|
||||
}
|
||||
|
||||
selectedOrDefaultLayer = selectedOrDefaultLayer.replace('#','');
|
||||
|
||||
let foundLayer: ITiledMapLayer | null = null;
|
||||
for (const layer of this.gameMap.flatLayers) {
|
||||
if (layer.type !== "tilelayer") continue;
|
||||
//we want to prioritize the selectedLayer other the start layer
|
||||
if (
|
||||
(selectedOrDefaultLayer === layer.name ||
|
||||
selectedOrDefaultLayer === `#${layer.name}` ||
|
||||
layer.name.endsWith("/" + selectedOrDefaultLayer)) &&
|
||||
layer.type === "tilelayer" &&
|
||||
(selectedOrDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))
|
||||
|
1
maps/tests/Variables/Cache/.gitignore
vendored
Normal file
1
maps/tests/Variables/Cache/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
variables_tmp.json
|
5
maps/tests/Variables/Cache/script.js
Normal file
5
maps/tests/Variables/Cache/script.js
Normal file
@ -0,0 +1,5 @@
|
||||
WA.onInit().then(async () => {
|
||||
console.log('Trying to set variable "myvar". This should work, even if the cache was busted.');
|
||||
await WA.state.saveVariable('myvar', {'foo': 'bar'});
|
||||
console.log('SUCCESS!');
|
||||
});
|
82
maps/tests/Variables/Cache/variables_cache_1.json
Normal file
82
maps/tests/Variables/Cache/variables_cache_1.json
Normal file
@ -0,0 +1,82 @@
|
||||
{ "compressionlevel":-1,
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":2,
|
||||
"name":"start",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[
|
||||
{
|
||||
"height":67,
|
||||
"id":3,
|
||||
"name":"",
|
||||
"rotation":0,
|
||||
"text":
|
||||
{
|
||||
"fontfamily":"Sans Serif",
|
||||
"pixelsize":11,
|
||||
"text":"Test:\nThis test is to be run automatically using testcafe",
|
||||
"wrap":true
|
||||
},
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":252.4375,
|
||||
"x":2.78125,
|
||||
"y":2.5
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":8,
|
||||
"nextobjectid":11,
|
||||
"orientation":"orthogonal",
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"2021.03.23",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"..\/..\/tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.5,
|
||||
"width":10
|
||||
}
|
126
maps/tests/Variables/Cache/variables_cache_2.json
Normal file
126
maps/tests/Variables/Cache/variables_cache_2.json
Normal file
@ -0,0 +1,126 @@
|
||||
{ "compressionlevel":-1,
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":2,
|
||||
"name":"start",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[
|
||||
{
|
||||
"height":67,
|
||||
"id":3,
|
||||
"name":"",
|
||||
"rotation":0,
|
||||
"text":
|
||||
{
|
||||
"fontfamily":"Sans Serif",
|
||||
"pixelsize":11,
|
||||
"text":"Test:\nThis test is to be run automatically using testcafe\n\n(2nd file)",
|
||||
"wrap":true
|
||||
},
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":252.4375,
|
||||
"x":2.78125,
|
||||
"y":2.5
|
||||
},
|
||||
{
|
||||
"height":0,
|
||||
"id":9,
|
||||
"name":"myvar",
|
||||
"point":true,
|
||||
"properties":[
|
||||
{
|
||||
"name":"default",
|
||||
"type":"string",
|
||||
"value":"{}"
|
||||
},
|
||||
{
|
||||
"name":"jsonSchema",
|
||||
"type":"string",
|
||||
"value":"{}"
|
||||
},
|
||||
{
|
||||
"name":"persist",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
},
|
||||
{
|
||||
"name":"readableBy",
|
||||
"type":"string",
|
||||
"value":""
|
||||
},
|
||||
{
|
||||
"name":"writableBy",
|
||||
"type":"string",
|
||||
"value":""
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"variable",
|
||||
"visible":true,
|
||||
"width":0,
|
||||
"x":88.8149900876127,
|
||||
"y":147.75212636695
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":8,
|
||||
"nextobjectid":11,
|
||||
"orientation":"orthogonal",
|
||||
"properties":[
|
||||
{
|
||||
"name":"script",
|
||||
"type":"string",
|
||||
"value":"script.js"
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"2021.03.23",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"..\/..\/tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.5,
|
||||
"width":10
|
||||
}
|
1
tests/.gitignore
vendored
Normal file
1
tests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/node_modules
|
13
tests/.testcaferc.js
Normal file
13
tests/.testcaferc.js
Normal file
@ -0,0 +1,13 @@
|
||||
const BROWSER = process.env.BROWSER || "chrome --use-fake-ui-for-media-stream --use-fake-device-for-media-stream";
|
||||
|
||||
module.exports = {
|
||||
"browsers": BROWSER,
|
||||
"hostname": "localhost",
|
||||
//"skipJsErrors": true,
|
||||
"src": "tests/",
|
||||
"screenshots": {
|
||||
"path": "screenshots/",
|
||||
"takeOnFails": true,
|
||||
"thumbnails": false,
|
||||
}
|
||||
}
|
18
tests/README.md
Normal file
18
tests/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
End-to-end tests
|
||||
|
||||
This directory contains automated end to end tests.
|
||||
|
||||
To run them locally:
|
||||
|
||||
```console
|
||||
$ npm install
|
||||
$ npm test
|
||||
```
|
||||
|
||||
Alternatively, you can use docker-compose to run the tests:
|
||||
|
||||
```console
|
||||
$ docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||
```
|
||||
|
||||
Note: by default, tests are running in Chrome locally and in Chromium in the Docker image.
|
8915
tests/package-lock.json
generated
Normal file
8915
tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
tests/package.json
Normal file
8
tests/package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"testcafe": "^1.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "testcafe"
|
||||
}
|
||||
}
|
1
tests/screenshots/.gitignore
vendored
Normal file
1
tests/screenshots/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*
|
67
tests/tests/test.ts
Normal file
67
tests/tests/test.ts
Normal file
@ -0,0 +1,67 @@
|
||||
const fs = require('fs')
|
||||
|
||||
fixture `Variables`
|
||||
.page `http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json`;
|
||||
|
||||
test("Test that variables cache in the back don't prevent setting a variable in case the map changes", async t => {
|
||||
// Let's start by visiting a map that DOES not have the variable.
|
||||
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_1.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||
|
||||
await t
|
||||
.typeText('input[name="loginSceneName"]', 'foo')
|
||||
.click('button.loginSceneFormSubmit')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterSceneFormSubmit')
|
||||
.click('button.letsgo');
|
||||
//.takeScreenshot('before_switch.png');
|
||||
|
||||
// Let's REPLACE the map by a map that has a new variable
|
||||
// At this point, the back server contains a cache of the old map (with no variables)
|
||||
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_2.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||
await t.openWindow('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json');
|
||||
|
||||
await t.resizeWindow(960, 800);
|
||||
|
||||
await t
|
||||
.typeText('input[name="loginSceneName"]', 'foo')
|
||||
.click('button.loginSceneFormSubmit')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterSceneFormSubmit')
|
||||
.click('button.letsgo');
|
||||
//.takeScreenshot('after_switch.png');
|
||||
|
||||
const messages = await t.getBrowserConsoleMessages();
|
||||
|
||||
const logs = messages['log'];
|
||||
|
||||
// Let's check we successfully manage to save the variable value.
|
||||
await assertLogMessage(t, 'SUCCESS!');
|
||||
|
||||
t.ctx.passed = true;
|
||||
}).after(async t => {
|
||||
if (!t.ctx.passed) {
|
||||
console.log("Test failed. Browser logs:")
|
||||
console.log(await t.getBrowserConsoleMessages());
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Tries to find a given log message in the logs (for 10 seconds)
|
||||
*/
|
||||
async function assertLogMessage(t, message: string): Promise<void> {
|
||||
let i = 0;
|
||||
let logs: string[]|undefined;
|
||||
do {
|
||||
const messages = await t.getBrowserConsoleMessages();
|
||||
logs = messages['log'];
|
||||
if (logs.find((str) => str === message)) {
|
||||
break;
|
||||
}
|
||||
await t.wait(1000);
|
||||
i++;
|
||||
} while (i < 10);
|
||||
|
||||
await t.expect(logs).contains(message);
|
||||
}
|
Loading…
Reference in New Issue
Block a user