diff --git a/.env b/.env
index 0d204929..0658ba2d 100644
--- a/.env
+++ b/.env
@@ -1,11 +1,11 @@
VITE_INITIAL_COMPILER_URL=https://compiler.sensebox.de
VITE_BOARD=sensebox-mcu
-VITE_BLOCKLY_API=https://api.blockly.sensebox.de
+VITE_BLOCKLY_API=https://api.testing.sensebox.de
GENERATE_SOURCEMAP=false
# in days
VITE_SHARE_LINK_EXPIRES=30
-VITE_PROJECTS_ALLOWED_AUTHORS="mario.pesch@uni-muenster.de, p_scha35@uni-muenster.de, e.c-schneider@reedu.de, verena.witte@yahoo.de"
+VITE_PROJECTS_ALLOWED_AUTHORS="mario.pesch@uni-muenster.de,p_scha35@uni-muenster.de,e.c-schneider@reedu.de,verena.witte@yahoo.de"
diff --git a/cypress/e2e/blockly.cy.js b/cypress/e2e/blockly.cy.js
index 3c9a0ac2..236d14c2 100644
--- a/cypress/e2e/blockly.cy.js
+++ b/cypress/e2e/blockly.cy.js
@@ -89,6 +89,58 @@ describe("Blockly Editor Page Tests", () => {
.and("contain.text", "Sprache");
});
+ it("[Blockly] changes to tablet mode and compiles code for esp32", () => {
+ cy.intercept({
+ method: "POST",
+ pathname: "/compile",
+ }).as("compile");
+
+ cy.visit("/settings");
+ cy.get("#ota-selector").click();
+ cy.contains("li", "Activated").click();
+ cy.visit("/");
+ cy.contains("button", "Close").click();
+ cy.get('img[alt="Sensebox ESP"]').click();
+ cy.get('button[aria-label="Compile code"]').click();
+
+ // check if the request was made
+ cy.wait("@compile", {
+ responseTimeout: 30000,
+ requestTimeout: 30000,
+ }).then((interception) => {
+ expect(interception.response.statusCode).to.eq(200);
+ expect(interception.response.body).to.have.property("data");
+ expect(interception.response.body.data).to.have.property("id");
+ expect(interception.response.body.data.id).to.be.a("string");
+ });
+ });
+
+ it("[Blockly] changes to tablet mode and compiles code for MCU", () => {
+ cy.intercept({
+ method: "POST",
+ pathname: "/compile",
+ }).as("compile");
+
+ cy.visit("/settings");
+ cy.get("#ota-selector").click();
+ cy.contains("li", "Activated").click();
+ cy.visit("/");
+ cy.contains("button", "Close").click();
+ cy.get('img[alt="Sensebox MCU"]').click();
+ cy.get('button[aria-label="Compile code"]').click();
+
+ // check if the request was made
+ cy.wait("@compile", {
+ responseTimeout: 30000,
+ requestTimeout: 30000,
+ }).then((interception) => {
+ expect(interception.response.statusCode).to.eq(200);
+ expect(interception.response.body).to.have.property("data");
+ expect(interception.response.body.data).to.have.property("id");
+ expect(interception.response.body.data.id).to.be.a("string");
+ });
+ });
+
it("[Blockly] compiles code", () => {
// intercept the request to the compiler
@@ -98,6 +150,7 @@ describe("Blockly Editor Page Tests", () => {
}).as("compile");
cy.visit("/");
+
cy.get('img[alt="Sensebox ESP"]').click();
cy.get('button[aria-label="Compile code"]').click();
diff --git a/cypress/e2e/tutorial-flow.cy.js b/cypress/e2e/tutorial-flow.cy.js
new file mode 100644
index 00000000..3c01c749
--- /dev/null
+++ b/cypress/e2e/tutorial-flow.cy.js
@@ -0,0 +1,168 @@
+describe("End-to-End Tutorial Flow", () => {
+ const timestamp = Date.now();
+ const email = `testuser+${timestamp}@example.com`;
+ const password = "SecurePass123!";
+ const initialTitle = "Test123";
+ const updatedTitle = "Test123 - Updated";
+ const BACKEND_URL = "https://api.testing.sensebox.de";
+ let userToken;
+ let createdTutorialId = "";
+
+ it("1. registers a new user", () => {
+ cy.visit("/user/register");
+
+ cy.get('input[name="email"]').type(email);
+ cy.get('input[name="password"]').type(password);
+ cy.get('input[name="confirmPassword"]').type(password);
+
+ cy.get('button[type="submit"]').click();
+
+ cy.url().should("include", "/user/register/success");
+
+ // ✅ Prüfe deutschen Erfolgshinweis
+ cy.contains("Konto erfolgreich erstellt!").should("be.visible");
+ });
+
+ it("2. logs in with the registered user", () => {
+ cy.visit("/user/login");
+
+ cy.get('input[name="email"]').type(email);
+ cy.get('input[name="password"]').type(password);
+ cy.get('button[type="submit"]').click();
+
+ // Prüfe, dass wir nicht mehr auf der Login-Seite sind
+ cy.url().should("not.include", "/login");
+ });
+
+ it("3. navigates to /tutorial/builder", () => {
+ cy.visit("/user/login");
+
+ cy.get('input[name="email"]').type(email);
+ cy.get('input[name="password"]').type(password);
+ cy.get('button[type="submit"]').click();
+
+ // Prüfe, dass wir nicht mehr auf der Login-Seite sind
+ cy.url().should("not.include", "/login");
+
+ cy.visit("/tutorial/builder");
+
+ cy.url().should("include", "/tutorial/builder");
+ });
+
+ it("4. creates a new tutorial via UI", () => {
+ cy.visit("/user/login");
+
+ cy.get('input[name="email"]').type(email);
+ cy.get('input[name="password"]').type(password);
+ cy.get('button[type="submit"]').click();
+
+ // Prüfe, dass wir nicht mehr auf der Login-Seite sind
+ cy.url().should("not.include", "/login");
+ cy.visit("/tutorial/builder");
+
+ // Eingaben vornehmen
+ cy.get("#tutorial-title").type(initialTitle);
+ cy.get("#tutorial-subtitle").type("Test123");
+ cy.get("#accordion_builder_advanced").click();
+
+ cy.get('button[value="3"]').click();
+
+ // ❗ Klicke auf den Speichern-Button
+ cy.contains("button", "Tutorial speichern").click();
+
+ // Warte auf Erfolgsmeldung
+ cy.contains("Tutorial erfolgreich gespeichert!").should("be.visible");
+
+ cy.get("button").contains("Zum Tutorial").click();
+
+ cy.url().then((url) => {
+ const match = url.match(/\/tutorial\/([a-f0-9]{24})/);
+ if (match) {
+ createdTutorialId = match[1];
+ } else {
+ throw new Error("Tutorial-ID nicht in URL gefunden");
+ }
+ });
+ });
+
+ it("5. views the created tutorial", () => {
+ cy.visit(`/tutorial/${createdTutorialId}`);
+ cy.url().should("include", `/tutorial/${createdTutorialId}`);
+ cy.contains(initialTitle).should("be.visible");
+ });
+
+ it("6. edits the tutorial title and saves", () => {
+ cy.visit("/user/login");
+ cy.get('input[name="email"]').type(email);
+ cy.get('input[name="password"]').type(password);
+ cy.get('button[type="submit"]').click();
+
+ // Prüfe, dass wir nicht mehr auf der Login-Seite sind
+ cy.url().should("not.include", "/login");
+ cy.visit("/tutorial");
+ cy.get('img[alt="Sensebox ESP"]').click();
+ cy.get(`#edit${createdTutorialId}`).click();
+ cy.get("#tutorial-title").type(updatedTitle);
+ cy.get("#accordion_builder_advanced").click();
+
+ cy.get('button[value="1"]').click();
+
+ cy.get(`#edit_hardware`).click();
+ cy.get('img[alt="MCU-S2"]').click();
+ cy.get('img[alt="Display"]').click();
+ cy.get('img[alt="GPS"]').click();
+ cy.get("button").contains("Fertig").click();
+ cy.contains("button", "Tutorial speichern").click();
+
+ // Warte auf Erfolgsmeldung
+ cy.contains("Tutorial erfolgreich gespeichert!").should("be.visible");
+
+ cy.get("button").contains("Zum Tutorial").click();
+
+ // Optional: Prüfe auch im UI
+ cy.contains(updatedTitle).should("be.visible");
+
+ cy.contains("GPS").should("be.visible");
+ cy.contains("Display").should("be.visible");
+ cy.contains("MCU-S2").should("be.visible");
+ });
+
+ it("7. deletes the test tutorial", () => {
+ cy.visit("/user/login");
+ cy.get('input[name="email"]').type(email);
+ cy.get('input[name="password"]').type(password);
+ cy.get('button[type="submit"]').click();
+
+ // Prüfe, dass wir nicht mehr auf der Login-Seite sind
+ cy.url().should("not.include", "/login");
+ cy.visit("/tutorial");
+ cy.get('img[alt="Sensebox ESP"]').click();
+ cy.get(`#delete${createdTutorialId}`).click();
+ cy.get("#confirmDelete").click();
+ // confirm delete button is gone
+ cy.get("body").find(`#delete${createdTutorialId}`).should("not.exist");
+ });
+
+ it("8. deletes the test user account", () => {
+ cy.request("POST", `${BACKEND_URL}/user/login`, {
+ email,
+ password,
+ }).then((loginRes) => {
+ const token = loginRes.body.token;
+
+ // Nutzer löschen
+ cy.request({
+ method: "DELETE",
+ url: `${BACKEND_URL}/user/me`,
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }).then((deleteRes) => {
+ expect(deleteRes.status).to.eq(200);
+ expect(deleteRes.body.message).to.eq(
+ "User account successfully deleted.",
+ );
+ });
+ });
+ });
+});
diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js
index 3eaffffa..bea78b60 100644
--- a/cypress/support/e2e.js
+++ b/cypress/support/e2e.js
@@ -14,4 +14,19 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
-import './commands'
\ No newline at end of file
+import "./commands";
+// cypress/support/e2e.js
+Cypress.on("uncaught:exception", (err, runnable) => {
+ // Prüfe auf bekannte Monaco-Fehler
+ if (
+ err.message.includes("LoadError") ||
+ err.message.includes("loader.js") ||
+ err.message.includes("monaco-editor") ||
+ err.message.includes("[object Event]") // Dein spezifischer Fehler
+ ) {
+ // Verhindere, dass Cypress den Test abbricht
+ return false;
+ }
+ // Bei allen anderen Fehlern: Test abbrechen
+ return true;
+});
diff --git a/index.html b/index.html
index a8eb4aaf..143aa917 100644
--- a/index.html
+++ b/index.html
@@ -6,6 +6,10 @@
+