Merge pull request #1579 from thecodingmachine/end-to-end-tests
Adding End to end tests
This commit is contained in:
commit
f3e498439c
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,6 +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_HOST = process.env.REDIS_HOST || undefined;
|
||||||
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
||||||
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
||||||
|
export const STORE_VARIABLES_FOR_LOCAL_MAPS = process.env.STORE_VARIABLES_FOR_LOCAL_MAPS === "true";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
|
@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
|
|||||||
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { LocalUrlError } from "../Services/LocalUrlError";
|
import { LocalUrlError } from "../Services/LocalUrlError";
|
||||||
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
||||||
|
import { VariableError } from "../Services/VariableError";
|
||||||
|
|
||||||
export type ConnectCallback = (user: User, group: Group) => void;
|
export type ConnectCallback = (user: User, group: Group) => void;
|
||||||
export type DisconnectCallback = (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.
|
// First, let's check if "user" is allowed to modify the variable.
|
||||||
const variableManager = await this.getVariableManager();
|
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 the variable was not changed, let's not dispatch anything.
|
||||||
if (readableBy === false) {
|
if (readableBy === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should we batch those every 100ms?
|
// TODO: should we batch those every 100ms?
|
||||||
const variableMessage = new VariableWithTagMessage();
|
const variableMessage = new VariableWithTagMessage();
|
||||||
variableMessage.setName(name);
|
variableMessage.setName(name);
|
||||||
variableMessage.setValue(value);
|
variableMessage.setValue(value);
|
||||||
if (readableBy) {
|
if (readableBy) {
|
||||||
variableMessage.setReadableby(readableBy);
|
variableMessage.setReadableby(readableBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
const subMessage = new SubToPusherRoomMessage();
|
const subMessage = new SubToPusherRoomMessage();
|
||||||
subMessage.setVariablemessage(variableMessage);
|
subMessage.setVariablemessage(variableMessage);
|
||||||
|
|
||||||
const batchMessage = new BatchToPusherRoomMessage();
|
const batchMessage = new BatchToPusherRoomMessage();
|
||||||
batchMessage.addPayload(subMessage);
|
batchMessage.addPayload(subMessage);
|
||||||
|
|
||||||
// Dispatch the message on the room listeners
|
// Dispatch the message on the room listeners
|
||||||
for (const socket of this.roomListeners) {
|
for (const socket of this.roomListeners) {
|
||||||
socket.write(batchMessage);
|
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 variableManagerPromise: Promise<VariablesManager> | undefined;
|
||||||
|
private variableManagerLastLoad: Date | undefined;
|
||||||
|
|
||||||
private getVariableManager(): Promise<VariablesManager> {
|
private getVariableManager(): Promise<VariablesManager> {
|
||||||
if (!this.variableManagerPromise) {
|
if (!this.variableManagerPromise) {
|
||||||
|
this.variableManagerLastLoad = new Date();
|
||||||
this.variableManagerPromise = this.getMap()
|
this.variableManagerPromise = this.getMap()
|
||||||
.then((map) => {
|
.then((map) => {
|
||||||
const variablesManager = new VariablesManager(this.roomUrl, map);
|
const variablesManager = new VariablesManager(this.roomUrl, map);
|
||||||
|
@ -5,12 +5,13 @@ import { promisify } from "util";
|
|||||||
import { LocalUrlError } from "./LocalUrlError";
|
import { LocalUrlError } from "./LocalUrlError";
|
||||||
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
||||||
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
||||||
|
import { STORE_VARIABLES_FOR_LOCAL_MAPS } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
class MapFetcher {
|
class MapFetcher {
|
||||||
async fetchMap(mapUrl: string): Promise<ITiledMap> {
|
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)
|
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
|
||||||
|
|
||||||
if (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');
|
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 { User } from "_Model/User";
|
||||||
import { variablesRepository } from "./Repository/VariablesRepository";
|
import { variablesRepository } from "./Repository/VariablesRepository";
|
||||||
import { redisClient } from "./RedisClient";
|
import { redisClient } from "./RedisClient";
|
||||||
|
import { VariableError } from "./VariableError";
|
||||||
|
|
||||||
interface Variable {
|
interface Variable {
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
@ -174,11 +175,13 @@ export class VariablesManager {
|
|||||||
if (this.variableObjects) {
|
if (this.variableObjects) {
|
||||||
variableObject = this.variableObjects.get(name);
|
variableObject = this.variableObjects.get(name);
|
||||||
if (variableObject === undefined) {
|
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)) {
|
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
|
||||||
throw new Error(
|
throw new VariableError(
|
||||||
'Trying to set a variable "' +
|
'Trying to set a variable "' +
|
||||||
name +
|
name +
|
||||||
'". User "' +
|
'". User "' +
|
||||||
|
@ -61,6 +61,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
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_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
@ -121,6 +123,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
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_KEY: yourSecretKey
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
ALLOW_ARTILLERY: "true"
|
ALLOW_ARTILLERY: "true"
|
||||||
|
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
|
- front
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- 'play.workadventure.localhost'
|
||||||
|
- 'pusher.workadventure.localhost'
|
||||||
|
- 'maps.workadventure.localhost'
|
||||||
|
|
||||||
front:
|
front:
|
||||||
image: thecodingmachine/nodejs:14
|
image: thecodingmachine/nodejs:14
|
||||||
@ -63,6 +69,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "socket:*"
|
DEBUG: "socket:*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
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_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
@ -117,6 +125,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
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_KEY: yourSecretKey
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
ALLOW_ARTILLERY: "true"
|
ALLOW_ARTILLERY: "true"
|
||||||
@ -127,6 +137,7 @@ services:
|
|||||||
MAX_PER_GROUP: "MAX_PER_GROUP"
|
MAX_PER_GROUP: "MAX_PER_GROUP"
|
||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
|
STORE_VARIABLES_FOR_LOCAL_MAPS: "true"
|
||||||
volumes:
|
volumes:
|
||||||
- ./back:/usr/src/app
|
- ./back:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
|
@ -4,7 +4,7 @@ declare let window: any;
|
|||||||
|
|
||||||
class AnalyticsClient {
|
class AnalyticsClient {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private posthogPromise: Promise<any>;
|
private posthogPromise: Promise<any>|undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (POSTHOG_API_KEY && POSTHOG_URL) {
|
if (POSTHOG_API_KEY && POSTHOG_URL) {
|
||||||
@ -14,90 +14,78 @@ class AnalyticsClient {
|
|||||||
window.posthog = posthog;
|
window.posthog = posthog;
|
||||||
return posthog;
|
return posthog;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.posthogPromise = Promise.reject();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identifyUser(uuid: string, email: string | null) {
|
identifyUser(uuid: string, email: string | null) {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.identify(uuid, { uuid, email, wa: true });
|
posthog.identify(uuid, { uuid, email, wa: true });
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithSso() {
|
loggedWithSso() {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-logged-sso");
|
posthog.capture("wa-logged-sso");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithToken() {
|
loggedWithToken() {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-logged-token");
|
posthog.capture("wa-logged-token");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredRoom(roomId: string, roomGroup: string | null) {
|
enteredRoom(roomId: string, roomGroup: string | null) {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("$pageView", { roomId, roomGroup });
|
posthog.capture("$pageView", { roomId, roomGroup });
|
||||||
posthog.capture("enteredRoom");
|
posthog.capture("enteredRoom");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openedMenu() {
|
openedMenu() {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-opened-menu");
|
posthog.capture("wa-opened-menu");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
launchEmote(emote: string) {
|
launchEmote(emote: string) {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-emote-launch", { emote });
|
posthog.capture("wa-emote-launch", { emote });
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredJitsi(roomName: string, roomId: string) {
|
enteredJitsi(roomName: string, roomId: string) {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validationName() {
|
validationName() {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-name-validation");
|
posthog.capture("wa-name-validation");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validationWoka(scene: string) {
|
validationWoka(scene: string) {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-woka-validation", { scene });
|
posthog.capture("wa-woka-validation", { scene });
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validationVideo() {
|
validationVideo() {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-video-validation");
|
posthog.capture("wa-video-validation");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const analyticsClient = new AnalyticsClient();
|
export const analyticsClient = new AnalyticsClient();
|
||||||
|
@ -122,10 +122,12 @@ class LocalUserStore {
|
|||||||
|
|
||||||
setLastRoomUrl(roomUrl: string): void {
|
setLastRoomUrl(roomUrl: string): void {
|
||||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||||
caches.open(cacheAPIIndex).then((cache) => {
|
if ('caches' in window) {
|
||||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
caches.open(cacheAPIIndex).then((cache) => {
|
||||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||||
});
|
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getLastRoomUrl(): string {
|
getLastRoomUrl(): string {
|
||||||
return (
|
return (
|
||||||
@ -133,6 +135,9 @@ class LocalUserStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||||
|
if (!('caches' in window)) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
return caches.open(cacheAPIIndex).then((cache) => {
|
return caches.open(cacheAPIIndex).then((cache) => {
|
||||||
return cache.match(`/${lastRoomUrl}`).then((res) => {
|
return cache.match(`/${lastRoomUrl}`).then((res) => {
|
||||||
return res?.json().then((data) => {
|
return res?.json().then((data) => {
|
||||||
|
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