From 8ae83f2d5064fe80dece180e28319a57e309263c Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Mon, 22 Apr 2024 23:07:39 +0200 Subject: [PATCH 01/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20de=20login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/login-form.feature | 6 +++ webapp/e2e/features/register-form.feature | 2 +- webapp/e2e/steps/login-form.steps.js | 57 +++++++++++++++++++++++ webapp/e2e/steps/register-form.steps.js | 4 +- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 webapp/e2e/features/login-form.feature create mode 100644 webapp/e2e/steps/login-form.steps.js diff --git a/webapp/e2e/features/login-form.feature b/webapp/e2e/features/login-form.feature new file mode 100644 index 00000000..19152cc4 --- /dev/null +++ b/webapp/e2e/features/login-form.feature @@ -0,0 +1,6 @@ +Feature: Login a registered user + +Scenario: The user is registered in the site + Given A registered user + When I fill the data in the form and press submit + Then The home screen should be shown \ No newline at end of file diff --git a/webapp/e2e/features/register-form.feature b/webapp/e2e/features/register-form.feature index aad790a5..545cb989 100644 --- a/webapp/e2e/features/register-form.feature +++ b/webapp/e2e/features/register-form.feature @@ -3,4 +3,4 @@ Feature: Registering a new user Scenario: The user is not registered in the site Given An unregistered user When I fill the data in the form and press submit - Then A confirmation message should be shown in the screen \ No newline at end of file + Then The home screen should be shown \ No newline at end of file diff --git a/webapp/e2e/steps/login-form.steps.js b/webapp/e2e/steps/login-form.steps.js new file mode 100644 index 00000000..8f681948 --- /dev/null +++ b/webapp/e2e/steps/login-form.steps.js @@ -0,0 +1,57 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user is registered in the site", ({ given, when, then }) => { + let username; + let password; + + given("A registered user", async () => { + username = "pablo"; + password = "pabloasw"; + await expect(page).toClick("a", { text: "Regístrate" }); + }); + + when("I fill the data in the form and press submit", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector('#login-username'); + await page.type('#login-username', username); + await page.waitForSelector('#register-password'); + await page.type('#register-password', password); + await page.click("button", { text: "Login" }); + }); + + then("The home screen should be shown", async () => { + await page.waitForTimeout(1000); + const url = page.url(); + expect(url).toContain("/home"); + browser.close(); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js index 04c92ecb..6a2435cf 100644 --- a/webapp/e2e/steps/register-form.steps.js +++ b/webapp/e2e/steps/register-form.steps.js @@ -35,7 +35,7 @@ defineFeature(feature, (test) => { when("I fill the data in the form and press submit", async () => { username = "testuser"; - password = "testpassword"; + password = "Testpassword1"; await page.waitForSelector('#register-username'); await page.type('#register-username', username); await page.waitForSelector('#register-password'); @@ -45,7 +45,7 @@ defineFeature(feature, (test) => { await page.click("button", { text: "Registrarse" }); }); - then("A confirmation message should be shown in the screen", async () => { + then("The home screen should be shown", async () => { await page.waitForTimeout(1000); const url = page.url(); expect(url).toContain("/home"); From 08844843dca24c7ce6e91b79d0f08745b25d74a3 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Mon, 22 Apr 2024 23:17:32 +0200 Subject: [PATCH 02/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20de=20perfil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/profile.feature | 6 +++ webapp/e2e/steps/login-form.steps.js | 31 ++++++++------- webapp/e2e/steps/profile.steps.js | 57 ++++++++++++++++++++++++++++ webapp/e2e/test-environment-setup.js | 1 + 4 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 webapp/e2e/features/profile.feature create mode 100644 webapp/e2e/steps/profile.steps.js diff --git a/webapp/e2e/features/profile.feature b/webapp/e2e/features/profile.feature new file mode 100644 index 00000000..17562d96 --- /dev/null +++ b/webapp/e2e/features/profile.feature @@ -0,0 +1,6 @@ +Feature: Seeing logged user's profile + +Scenario: The user is logged in and can view their profile + Given A logged-in user + When I press the Profile link + Then The user's profile shoud be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/login-form.steps.js b/webapp/e2e/steps/login-form.steps.js index 8f681948..69502dc1 100644 --- a/webapp/e2e/steps/login-form.steps.js +++ b/webapp/e2e/steps/login-form.steps.js @@ -27,27 +27,26 @@ defineFeature(feature, (test) => { let username; let password; - given("A registered user", async () => { - username = "pablo"; - password = "pabloasw"; - await expect(page).toClick("a", { text: "Regístrate" }); + given("A logged-in user", async () => { + await page.waitForSelector("#login-username"); + await page.type("#login-username", "testuser"); + await page.waitForSelector("#register-password"); + await page.type("#register-password", "Testpassword1"); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); }); - when("I fill the data in the form and press submit", async () => { - username = "testuser"; - password = "Testpassword1"; - await page.waitForSelector('#login-username'); - await page.type('#login-username', username); - await page.waitForSelector('#register-password'); - await page.type('#register-password', password); - await page.click("button", { text: "Login" }); + when("The user clicks on their profile", async () => { + await page.waitForSelector('[data-testid="profile-menu"]'); + await page.click('[data-testid="profile-menu"]'); + await page.waitForSelector('[data-testid="profile-link"]'); + await page.click('[data-testid="profile-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); }); - then("The home screen should be shown", async () => { - await page.waitForTimeout(1000); + then("The user's profile page should be displayed", async () => { const url = page.url(); - expect(url).toContain("/home"); - browser.close(); + expect(url).toContain("/perfil/testuser"); }); }); diff --git a/webapp/e2e/steps/profile.steps.js b/webapp/e2e/steps/profile.steps.js new file mode 100644 index 00000000..706aba85 --- /dev/null +++ b/webapp/e2e/steps/profile.steps.js @@ -0,0 +1,57 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user is logged in and can view their profile", ({ given, when, then }) => { + let username; + let password; + + given("A registered user", async () => { + username = "pablo"; + password = "pabloasw"; + await expect(page).toClick("a", { text: "Regístrate" }); + }); + + when("I press the Profile link", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector('#login-username'); + await page.type('#login-username', username); + await page.waitForSelector('#register-password'); + await page.type('#register-password', password); + await page.click("button", { text: "Login" }); + }); + + then("The user's profile shoud be shown on screen", async () => { + await page.waitForTimeout(1000); + const url = page.url(); + expect(url).toContain("/home"); + browser.close(); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); \ No newline at end of file diff --git a/webapp/e2e/test-environment-setup.js b/webapp/e2e/test-environment-setup.js index 4120d25c..c17b5fd5 100644 --- a/webapp/e2e/test-environment-setup.js +++ b/webapp/e2e/test-environment-setup.js @@ -5,6 +5,7 @@ let mongoserver; let userservice; let authservice; let gatewayservice; +let statsservice; async function startServer() { console.log('Starting MongoDB memory server...'); From 14ea51241ed8fcb6dd498c0e858bb9a3d98086cd Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Mon, 22 Apr 2024 23:28:46 +0200 Subject: [PATCH 03/26] =?UTF-8?q?Corregidos=20errores=20y=20a=C3=B1adido?= =?UTF-8?q?=20test=20e2e=20de=20Sobre=20nosotros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/aboutus.feature | 6 +++ webapp/e2e/features/profile.feature | 4 +- webapp/e2e/steps/aboutus.steps.js | 56 ++++++++++++++++++++++++++++ webapp/e2e/steps/login-form.steps.js | 30 +++++++-------- webapp/e2e/steps/profile.steps.js | 35 ++++++++--------- 5 files changed, 96 insertions(+), 35 deletions(-) create mode 100644 webapp/e2e/features/aboutus.feature create mode 100644 webapp/e2e/steps/aboutus.steps.js diff --git a/webapp/e2e/features/aboutus.feature b/webapp/e2e/features/aboutus.feature new file mode 100644 index 00000000..090d0a01 --- /dev/null +++ b/webapp/e2e/features/aboutus.feature @@ -0,0 +1,6 @@ +Feature: Seeing logged user's profile + +Scenario: The user can view the about us page + Given A logged-in user + When I click on the About Us link + Then The About Us page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/profile.feature b/webapp/e2e/features/profile.feature index 17562d96..8edc5b87 100644 --- a/webapp/e2e/features/profile.feature +++ b/webapp/e2e/features/profile.feature @@ -1,6 +1,6 @@ Feature: Seeing logged user's profile -Scenario: The user is logged in and can view their profile +Scenario: The user can see his Profile page Given A logged-in user - When I press the Profile link + When I click on the Profile link Then The user's profile shoud be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/aboutus.steps.js b/webapp/e2e/steps/aboutus.steps.js new file mode 100644 index 00000000..69ad46d6 --- /dev/null +++ b/webapp/e2e/steps/aboutus.steps.js @@ -0,0 +1,56 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("User can view the about us page", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username = "testuser"; + password = "Testuser1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the About Us link", async () => { + await page.waitForSelector('[data-testid="about-us-link"]'); + await page.click('[data-testid="about-us-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The About Us page should be shown on screen", async () => { + const url = page.url(); + expect(url).toContain("/sobre"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); \ No newline at end of file diff --git a/webapp/e2e/steps/login-form.steps.js b/webapp/e2e/steps/login-form.steps.js index 69502dc1..7a958605 100644 --- a/webapp/e2e/steps/login-form.steps.js +++ b/webapp/e2e/steps/login-form.steps.js @@ -27,30 +27,28 @@ defineFeature(feature, (test) => { let username; let password; - given("A logged-in user", async () => { - await page.waitForSelector("#login-username"); - await page.type("#login-username", "testuser"); - await page.waitForSelector("#register-password"); - await page.type("#register-password", "Testpassword1"); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + given("A registered user", async () => { + username = "testuser"; + password = "Testpassword1"; }); - when("The user clicks on their profile", async () => { - await page.waitForSelector('[data-testid="profile-menu"]'); - await page.click('[data-testid="profile-menu"]'); - await page.waitForSelector('[data-testid="profile-link"]'); - await page.click('[data-testid="profile-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + when("I fill the data in the form and press submit", async () => { + await page.waitForSelector('#login-username'); + await page.type('#login-username', username); + await page.waitForSelector('#login-password'); + await page.type('#login-password', password); + await page.click("button", { text: "Login" }); }); - then("The user's profile page should be displayed", async () => { + then("The home screen should be shown", async () => { + await page.waitForTimeout(1000); const url = page.url(); - expect(url).toContain("/perfil/testuser"); + expect(url).toContain("/home"); + browser.close(); }); }); afterAll(async () => { browser.close(); }); -}); +}); \ No newline at end of file diff --git a/webapp/e2e/steps/profile.steps.js b/webapp/e2e/steps/profile.steps.js index 706aba85..5966f726 100644 --- a/webapp/e2e/steps/profile.steps.js +++ b/webapp/e2e/steps/profile.steps.js @@ -23,35 +23,36 @@ defineFeature(feature, (test) => { .catch(() => {}); }); - test("The user is logged in and can view their profile", ({ given, when, then }) => { + test("The user can see his Profile page", ({ given, when, then }) => { let username; let password; - given("A registered user", async () => { - username = "pablo"; - password = "pabloasw"; - await expect(page).toClick("a", { text: "Regístrate" }); + given("A logged-in user", async () => { + username="testuser"; + password="Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); }); - when("I press the Profile link", async () => { - username = "testuser"; - password = "Testpassword1"; - await page.waitForSelector('#login-username'); - await page.type('#login-username', username); - await page.waitForSelector('#register-password'); - await page.type('#register-password', password); - await page.click("button", { text: "Login" }); + when("I click on the Profile link", async () => { + await page.waitForSelector('[data-testid="profile-menu"]'); + await page.click('[data-testid="profile-menu"]'); + await page.waitForSelector('[data-testid="profile-link"]'); + await page.click('[data-testid="profile-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); }); then("The user's profile shoud be shown on screen", async () => { - await page.waitForTimeout(1000); const url = page.url(); - expect(url).toContain("/home"); - browser.close(); + expect(url).toContain("/perfil/testuser"); }); }); afterAll(async () => { browser.close(); }); -}); \ No newline at end of file +}); From 4cb91028208880457f0561c414f4fa4f87fdc789 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Mon, 22 Apr 2024 23:37:52 +0200 Subject: [PATCH 04/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20de=20logout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/logout.feature | 6 ++++ webapp/e2e/steps/aboutus.steps.js | 2 +- webapp/e2e/steps/logout.steps.js | 58 ++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 webapp/e2e/features/logout.feature create mode 100644 webapp/e2e/steps/logout.steps.js diff --git a/webapp/e2e/features/logout.feature b/webapp/e2e/features/logout.feature new file mode 100644 index 00000000..59fe36a9 --- /dev/null +++ b/webapp/e2e/features/logout.feature @@ -0,0 +1,6 @@ +Feature: Logging out + +Scenario: The user can logout + Given A logged-in user + When I click on the Logout link + Then The user should be logged out and the Login screen should be shown \ No newline at end of file diff --git a/webapp/e2e/steps/aboutus.steps.js b/webapp/e2e/steps/aboutus.steps.js index 69ad46d6..3d4939f2 100644 --- a/webapp/e2e/steps/aboutus.steps.js +++ b/webapp/e2e/steps/aboutus.steps.js @@ -29,7 +29,7 @@ defineFeature(feature, (test) => { given("A logged-in user", async () => { username = "testuser"; - password = "Testuser1"; + password = "Testpassword1"; await page.waitForSelector("#login-username"); await page.type("#login-username", username); await page.waitForSelector("#login-password"); diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js new file mode 100644 index 00000000..4f1710e0 --- /dev/null +++ b/webapp/e2e/steps/logout.steps.js @@ -0,0 +1,58 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + let username; + let password; + + test("The user can logout", ({ given, when, then }) => { + given("A logged-in user", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#register-password"); + await page.type("#register-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Logout link", async () => { + await page.waitForSelector('[data-testid="profile-menu"]'); + await page.click('[data-testid="profile-menu"]'); + await page.waitForSelector('[data-testid="logout-link"]'); + await page.click('[data-testid="logout-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The user should be logged out", async () => { + const url = page.url(); + expect(url).toContain("/login"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); From 7949ab4f1df4877ab776703d22e6d1798ec67fbf Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Mon, 22 Apr 2024 23:42:44 +0200 Subject: [PATCH 05/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20para=20Config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/aboutus.feature | 2 +- webapp/e2e/features/config.feature | 6 +++ webapp/e2e/steps/aboutus.steps.js | 2 +- webapp/e2e/steps/config.steps.js | 58 +++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 webapp/e2e/features/config.feature create mode 100644 webapp/e2e/steps/config.steps.js diff --git a/webapp/e2e/features/aboutus.feature b/webapp/e2e/features/aboutus.feature index 090d0a01..a5feb075 100644 --- a/webapp/e2e/features/aboutus.feature +++ b/webapp/e2e/features/aboutus.feature @@ -1,6 +1,6 @@ Feature: Seeing logged user's profile -Scenario: The user can view the about us page +Scenario: The user can view the About Us page Given A logged-in user When I click on the About Us link Then The About Us page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/config.feature b/webapp/e2e/features/config.feature new file mode 100644 index 00000000..cabd0110 --- /dev/null +++ b/webapp/e2e/features/config.feature @@ -0,0 +1,6 @@ +Feature: Seeing logged user's profile + +Scenario: The user can view the Configuration page + Given A logged-in user + When I click on the Configuration link + Then The Configuration page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/aboutus.steps.js b/webapp/e2e/steps/aboutus.steps.js index 3d4939f2..ef4ef571 100644 --- a/webapp/e2e/steps/aboutus.steps.js +++ b/webapp/e2e/steps/aboutus.steps.js @@ -23,7 +23,7 @@ defineFeature(feature, (test) => { .catch(() => {}); }); - test("User can view the about us page", ({ given, when, then }) => { + test("The user can view the About Us page", ({ given, when, then }) => { let username; let password; diff --git a/webapp/e2e/steps/config.steps.js b/webapp/e2e/steps/config.steps.js new file mode 100644 index 00000000..cfa2f335 --- /dev/null +++ b/webapp/e2e/steps/config.steps.js @@ -0,0 +1,58 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can view the Configuration page", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Configuration link", async () => { + await page.waitForSelector('[data-testid="profile-menu"]'); + await page.click('[data-testid="profile-menu"]'); + await page.waitForSelector('[data-testid="config-link"]'); + await page.click('[data-testid="config-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The Configuration page should be shown on screen", async () => { + const url = page.url(); + expect(url).toContain("/config"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); \ No newline at end of file From 7d1d5d6c02b9bb573c0c06e298b5c4dbe8289bf7 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Mon, 22 Apr 2024 23:44:20 +0200 Subject: [PATCH 06/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20para=20Histor?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/history.feature | 6 +++ webapp/e2e/steps/history.steps.js | 58 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 webapp/e2e/features/history.feature create mode 100644 webapp/e2e/steps/history.steps.js diff --git a/webapp/e2e/features/history.feature b/webapp/e2e/features/history.feature new file mode 100644 index 00000000..0438ec4b --- /dev/null +++ b/webapp/e2e/features/history.feature @@ -0,0 +1,6 @@ +Feature: Seeing logged user's profile + +Scenario: The user can view the History page + Given A logged-in user + When I click on the History link + Then The History page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/history.steps.js b/webapp/e2e/steps/history.steps.js new file mode 100644 index 00000000..907a2cdb --- /dev/null +++ b/webapp/e2e/steps/history.steps.js @@ -0,0 +1,58 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can view the History page", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the History link", async () => { + await page.waitForSelector('[data-testid="profile-menu"]'); + await page.click('[data-testid="profile-menu"]'); + await page.waitForSelector('[data-testid="history-link"]'); + await page.click('[data-testid="history-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The History page should be shown on screen", async () => { + const url = page.url(); + expect(url).toContain("/history"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); \ No newline at end of file From 7b395cf53f51a6159a4f01172aed865b504dc7eb Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 15:40:45 +0200 Subject: [PATCH 07/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20de=20Stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 315 +----------------------------- webapp/e2e/features/stats.feature | 6 + webapp/e2e/steps/stats.steps.js | 62 ++++++ 3 files changed, 74 insertions(+), 309 deletions(-) create mode 100644 webapp/e2e/features/stats.feature create mode 100644 webapp/e2e/steps/stats.steps.js diff --git a/package-lock.json b/package-lock.json index 6fea74ac..f9f036c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "wiq_es1a-2", + "name": "wiq_es1a", "lockfileVersion": 2, "requires": true, "packages": { @@ -24642,133 +24642,6 @@ } } }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - } - } - }, - "fork-ts-checker-webpack-plugin": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", - "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" - } - } - }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -27325,27 +27198,8 @@ "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "peer": true }, "nanomatch": { "version": "1.2.13", @@ -28129,6 +27983,7 @@ "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "peer": true, "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -31494,166 +31349,8 @@ "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "source-map-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", - "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", - "requires": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" - } - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "requires": { - "escodegen": "^1.8.1" - }, - "dependencies": { - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "peer": true }, "source-map-resolve": { "version": "0.5.3", diff --git a/webapp/e2e/features/stats.feature b/webapp/e2e/features/stats.feature new file mode 100644 index 00000000..275b00f5 --- /dev/null +++ b/webapp/e2e/features/stats.feature @@ -0,0 +1,6 @@ +Feature: Seeing logged user's stats and changing gamemode + +Scenario: The user can see his Stats page and change gamemode + Given A logged-in user + When I click on the Stats link and in Calculator gamemode + Then The user's stats in Calculator gamemode shoud be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/stats.steps.js b/webapp/e2e/steps/stats.steps.js new file mode 100644 index 00000000..4ee196b5 --- /dev/null +++ b/webapp/e2e/steps/stats.steps.js @@ -0,0 +1,62 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can see his Stats page and change gamemode", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username="testuser"; + password="Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Stats link and in Calculator gamemode", async () => { + await page.waitForSelector('[data-testid="stats-link"]'); + await page.click('[data-testid="stats-link"]'); + await page.click('[data-testid="calculator-button"]'); + await page.waitForSelector('button.active'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The user's stats in Calculator gamemode shoud be shown on screen", async () => { + const url = page.url(); + expect(url).toContain("/stats"); + const statsText = await page.evaluate(() => { + return document.querySelector("h2").textContent; + }); + expect(statsText).toContain("Calculadora humana"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); From eb7f30dd04a9f2bc006fe730ae18d95f6e5f47eb Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 15:58:45 +0200 Subject: [PATCH 08/26] =?UTF-8?q?A=C3=B1adidos=20test=20e2e=20de=20ranking?= =?UTF-8?q?=20para=20cambiar=20modo=20de=20juego=20y=20filtrado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/ranking-sorting.feature | 6 ++ webapp/e2e/features/ranking.feature | 6 ++ webapp/e2e/steps/ranking-sorting.steps.js | 63 +++++++++++++++++++++ webapp/e2e/steps/ranking.steps.js | 62 ++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 webapp/e2e/features/ranking-sorting.feature create mode 100644 webapp/e2e/features/ranking.feature create mode 100644 webapp/e2e/steps/ranking-sorting.steps.js create mode 100644 webapp/e2e/steps/ranking.steps.js diff --git a/webapp/e2e/features/ranking-sorting.feature b/webapp/e2e/features/ranking-sorting.feature new file mode 100644 index 00000000..312c77a1 --- /dev/null +++ b/webapp/e2e/features/ranking-sorting.feature @@ -0,0 +1,6 @@ +Feature: Seeing ranking and changing sorting filter + +Scenario: The user can see the Ranking page and change sorting filter + Given A logged-in user + When I click on the Ranking link and in Sort by Total Points + Then The ranking (sorted by Total Points) should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/ranking.feature b/webapp/e2e/features/ranking.feature new file mode 100644 index 00000000..497c9bfc --- /dev/null +++ b/webapp/e2e/features/ranking.feature @@ -0,0 +1,6 @@ +Feature: Seeing ranking and changing gamemode + +Scenario: The user can see the Ranking page and change gamemode + Given A logged-in user + When I click on the Ranking link and in Battery gamemode + Then The ranking of the Battery gamemode should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/ranking-sorting.steps.js b/webapp/e2e/steps/ranking-sorting.steps.js new file mode 100644 index 00000000..3e06098a --- /dev/null +++ b/webapp/e2e/steps/ranking-sorting.steps.js @@ -0,0 +1,63 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can see the Ranking page and change sorting filter", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username="testuser"; + password="Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Ranking link and in Sort by Total Points", async () => { + await page.waitForSelector('[data-testid="ranking-link"]'); + await page.click('[data-testid="ranking-link"]'); + await page.select('[data-testid="combobox"]', "totalPoints"); + await page.waitForSelector('th', { text: "Puntos totales" }); + await page.waitForSelector('td'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The ranking (sorted by Total Points) should be shown on screen", async () => { + const url = page.url(); + expect(url).toContain("/ranking"); + const displayedField = await page.evaluate(() => { + return document.querySelector('th').textContent; + }); + expect(displayedField).toContain("Puntos totales"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); diff --git a/webapp/e2e/steps/ranking.steps.js b/webapp/e2e/steps/ranking.steps.js new file mode 100644 index 00000000..09c130c1 --- /dev/null +++ b/webapp/e2e/steps/ranking.steps.js @@ -0,0 +1,62 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can see the Ranking page and change gamemode", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username="testuser"; + password="Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Ranking link and in Battery gamemode", async () => { + await page.waitForSelector('[data-testid="ranking-link"]'); + await page.click('[data-testid="ranking-link"]'); + await page.click('[data-testid="battery-button"]'); + await page.waitForSelector('button.active'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The ranking of the Battery gamemode should be shown on screen", async () => { + const url = page.url(); + expect(url).toContain("/ranking"); + const statsText = await page.evaluate(() => { + return document.querySelector("h2").textContent; + }); + expect(statsText).toContain("Batería de sabios"); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); From 4afa59f0052b20105f151e7ec9114ad06acb34cc Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 16:37:03 +0200 Subject: [PATCH 09/26] =?UTF-8?q?Test=20e2e=20para=20UsersPage=20(a=C3=B1a?= =?UTF-8?q?dir=20un=20amigo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/users-addfriend.feature | 6 ++ webapp/e2e/steps/users-addfriend.steps.js | 65 +++++++++++++++++++++ webapp/src/pages/Social/UsersPage.js | 6 +- 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 webapp/e2e/features/users-addfriend.feature create mode 100644 webapp/e2e/steps/users-addfriend.steps.js diff --git a/webapp/e2e/features/users-addfriend.feature b/webapp/e2e/features/users-addfriend.feature new file mode 100644 index 00000000..e5524cea --- /dev/null +++ b/webapp/e2e/features/users-addfriend.feature @@ -0,0 +1,6 @@ +Feature: Adding a new friend + +Scenario: The user can add a friend + Given A logged-in user + When I click on the Users link and add a friend + Then The user should disappear from the Users page \ No newline at end of file diff --git a/webapp/e2e/steps/users-addfriend.steps.js b/webapp/e2e/steps/users-addfriend.steps.js new file mode 100644 index 00000000..40464ba2 --- /dev/null +++ b/webapp/e2e/steps/users-addfriend.steps.js @@ -0,0 +1,65 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can add a friend", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username="testuser"; + password="Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Users link and add a friend", async () => { + await page.waitForSelector('[data-testid="users"]'); + await page.click('[data-testid="users"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + then("The user should disappear from the Users page", async () => { + const url = page.url(); + expect(url).toContain("/social/"); + + const numUsersBefore = await page.$$eval('tr', (rows) => rows.length); + + await page.click('[data-testid="add-friend-button-0"]'); + await page.waitForTimeout(1000); + + const numUsersAfter = await page.$$eval('tr', (rows) => rows.length); + + expect(numUsersAfter).toBeLessThan(numUsersBefore); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); diff --git a/webapp/src/pages/Social/UsersPage.js b/webapp/src/pages/Social/UsersPage.js index 9c8b573d..bde2d5c1 100644 --- a/webapp/src/pages/Social/UsersPage.js +++ b/webapp/src/pages/Social/UsersPage.js @@ -30,8 +30,8 @@ const UserList = ({ users, handleAddFriend }) => { - {users.map((user) => ( - + {users.map((user, index) => ( + @@ -42,7 +42,7 @@ const UserList = ({ users, handleAddFriend }) => { {user.isFriend ? ( {t("pages.userspage.friend")} ) : ( - )} From e9b3f06f0c81324febc863d31ab27a5331c3d48c Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 17:13:41 +0200 Subject: [PATCH 10/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20para=20crear?= =?UTF-8?q?=20grupo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/creategroup.feature | 6 +++ webapp/e2e/steps/creategroup.steps.js | 68 +++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 webapp/e2e/features/creategroup.feature create mode 100644 webapp/e2e/steps/creategroup.steps.js diff --git a/webapp/e2e/features/creategroup.feature b/webapp/e2e/features/creategroup.feature new file mode 100644 index 00000000..c1176f54 --- /dev/null +++ b/webapp/e2e/features/creategroup.feature @@ -0,0 +1,6 @@ +Feature: Creating a group + +Scenario: The user can create a group + Given A logged-in user + When I click on the Groups link and create a group + Then The Group should be shown on the My Groups page \ No newline at end of file diff --git a/webapp/e2e/steps/creategroup.steps.js b/webapp/e2e/steps/creategroup.steps.js new file mode 100644 index 00000000..a206a3e4 --- /dev/null +++ b/webapp/e2e/steps/creategroup.steps.js @@ -0,0 +1,68 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); +const feature = loadFeature("./features/register-form.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }); + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("The user can create a group", ({ given, when, then }) => { + let username; + let password; + + given("A logged-in user", async () => { + username="testuser"; + password="Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Groups link and create a group", async () => { + await page.click('[data-testid="groups"]'); + await page.waitForNavigation(); + + await page.waitForSelector('[name="name"]'); + await page.type('[name="name"]', "Test Group"); + await page.click("button", { text: "Crear" }); + await page.waitForTimeout(1000); + }); + + then("The Group should be shown on the My Groups page", async () => { + await page.click('[data-testid="my-groups"]'); + await page.waitForNavigation(); + + const groupExists = await page.evaluate(() => { + const groupName = "Test Group"; + const groups = Array.from(document.querySelectorAll("tbody tr td:first-child")); + return groups.some(td => td.textContent === groupName); + }); + + expect(groupExists).toBe(true); + }); + }); + + afterAll(async () => { + browser.close(); + }); +}); From 6b2b0034490992404950ba74107181d7bab69337 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 17:22:18 +0200 Subject: [PATCH 11/26] Test e2e para responder una pregunta del clasico --- webapp/e2e/features/play-classic.feature | 6 +++ webapp/e2e/steps/aboutus.steps.js | 2 +- webapp/e2e/steps/config.steps.js | 2 +- webapp/e2e/steps/creategroup.steps.js | 2 +- webapp/e2e/steps/history.steps.js | 2 +- webapp/e2e/steps/login-form.steps.js | 2 +- webapp/e2e/steps/logout.steps.js | 2 +- webapp/e2e/steps/play-classic.steps.js | 57 +++++++++++++++++++++++ webapp/e2e/steps/profile.steps.js | 2 +- webapp/e2e/steps/ranking-sorting.steps.js | 2 +- webapp/e2e/steps/ranking.steps.js | 2 +- webapp/e2e/steps/stats.steps.js | 2 +- webapp/e2e/steps/users-addfriend.steps.js | 2 +- 13 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 webapp/e2e/features/play-classic.feature create mode 100644 webapp/e2e/steps/play-classic.steps.js diff --git a/webapp/e2e/features/play-classic.feature b/webapp/e2e/features/play-classic.feature new file mode 100644 index 00000000..a823f926 --- /dev/null +++ b/webapp/e2e/features/play-classic.feature @@ -0,0 +1,6 @@ +Feature: Answering a question + +Scenario: The user can answer a question + Given A logged-in user + When I play on Classic mode and click on an answer + Then The right answer should be colored green \ No newline at end of file diff --git a/webapp/e2e/steps/aboutus.steps.js b/webapp/e2e/steps/aboutus.steps.js index ef4ef571..3591b020 100644 --- a/webapp/e2e/steps/aboutus.steps.js +++ b/webapp/e2e/steps/aboutus.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/aboutus.feature"); let page; let browser; diff --git a/webapp/e2e/steps/config.steps.js b/webapp/e2e/steps/config.steps.js index cfa2f335..a1302056 100644 --- a/webapp/e2e/steps/config.steps.js +++ b/webapp/e2e/steps/config.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/config.feature"); let page; let browser; diff --git a/webapp/e2e/steps/creategroup.steps.js b/webapp/e2e/steps/creategroup.steps.js index a206a3e4..95ca5ce8 100644 --- a/webapp/e2e/steps/creategroup.steps.js +++ b/webapp/e2e/steps/creategroup.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/creategroup.feature"); let page; let browser; diff --git a/webapp/e2e/steps/history.steps.js b/webapp/e2e/steps/history.steps.js index 907a2cdb..7e0c906c 100644 --- a/webapp/e2e/steps/history.steps.js +++ b/webapp/e2e/steps/history.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/history.feature"); let page; let browser; diff --git a/webapp/e2e/steps/login-form.steps.js b/webapp/e2e/steps/login-form.steps.js index 7a958605..28ad112a 100644 --- a/webapp/e2e/steps/login-form.steps.js +++ b/webapp/e2e/steps/login-form.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/login-form.feature"); let page; let browser; diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index 4f1710e0..7e10e8d3 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/logout.feature"); let page; let browser; diff --git a/webapp/e2e/steps/play-classic.steps.js b/webapp/e2e/steps/play-classic.steps.js new file mode 100644 index 00000000..d3ffef19 --- /dev/null +++ b/webapp/e2e/steps/play-classic.steps.js @@ -0,0 +1,57 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); + +const feature = loadFeature("./features/play-classic.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + setDefaultOptions({ timeout: 10000 }); + + await page.goto("http://localhost:3000", { + waitUntil: "networkidle0", + }); + }); + + test("The user can answer a question", ({ given, when, then }) => { + given("A logged-in user", async () => { + + await page.type("#login-username", "testuser"); + await page.type("#login-password", "Testpassword1"); + await page.click("button", { text: "Login" }); + await page.waitForNavigation(); + }); + + when("I play on Classic mode and click on an answer", async () => { + await page.click('[data-testid="classic-mode"]'); + await page.waitForNavigation(); + + await page.waitForSelector('[data-testid="question"]'); + + await page.click('[data-testid="answer-button"]'); + }); + + then("The right answer should be colored green", async () => { + await page.waitForTimeout(3000); + + const correctAnswerColor = await page.evaluate(() => { + const answerButton = document.querySelector('[data-testid="answer-button"]'); + return window.getComputedStyle(answerButton).getPropertyValue("background-color"); + }); + + expect(correctAnswerColor).toMatch("rgb(16, 255, 0)"); + }); + }); + + afterAll(async () => { + await browser.close(); + }); +}); diff --git a/webapp/e2e/steps/profile.steps.js b/webapp/e2e/steps/profile.steps.js index 5966f726..439b72e8 100644 --- a/webapp/e2e/steps/profile.steps.js +++ b/webapp/e2e/steps/profile.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/profile.feature"); let page; let browser; diff --git a/webapp/e2e/steps/ranking-sorting.steps.js b/webapp/e2e/steps/ranking-sorting.steps.js index 3e06098a..aec2c216 100644 --- a/webapp/e2e/steps/ranking-sorting.steps.js +++ b/webapp/e2e/steps/ranking-sorting.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/ranking-sorting.feature"); let page; let browser; diff --git a/webapp/e2e/steps/ranking.steps.js b/webapp/e2e/steps/ranking.steps.js index 09c130c1..d128aa66 100644 --- a/webapp/e2e/steps/ranking.steps.js +++ b/webapp/e2e/steps/ranking.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/ranking.feature"); let page; let browser; diff --git a/webapp/e2e/steps/stats.steps.js b/webapp/e2e/steps/stats.steps.js index 4ee196b5..81b9ec47 100644 --- a/webapp/e2e/steps/stats.steps.js +++ b/webapp/e2e/steps/stats.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/stats.feature"); let page; let browser; diff --git a/webapp/e2e/steps/users-addfriend.steps.js b/webapp/e2e/steps/users-addfriend.steps.js index 40464ba2..ddb5ad96 100644 --- a/webapp/e2e/steps/users-addfriend.steps.js +++ b/webapp/e2e/steps/users-addfriend.steps.js @@ -2,7 +2,7 @@ const puppeteer = require("puppeteer"); const { defineFeature, loadFeature } = require("jest-cucumber"); const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/register-form.feature"); +const feature = loadFeature("./features/users-addfriend.feature"); let page; let browser; From a739b07cc344e0e43edbd9abe8d29abe13feffe0 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 17:44:53 +0200 Subject: [PATCH 12/26] Test e2e para responder una pregunta mal en calculadora --- webapp/e2e/features/play-calculator.feature | 6 ++ webapp/e2e/features/play-classic.feature | 2 +- webapp/e2e/steps/play-calculator.steps.js | 65 +++++++++++++++++++++ webapp/e2e/steps/play-classic.steps.js | 28 +++++---- webapp/src/pages/Calculadora/Calculadora.js | 9 +-- webapp/src/pages/Clasico/Clasico.js | 4 +- 6 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 webapp/e2e/features/play-calculator.feature create mode 100644 webapp/e2e/steps/play-calculator.steps.js diff --git a/webapp/e2e/features/play-calculator.feature b/webapp/e2e/features/play-calculator.feature new file mode 100644 index 00000000..43e47198 --- /dev/null +++ b/webapp/e2e/features/play-calculator.feature @@ -0,0 +1,6 @@ +Feature: Answering a question + +Scenario: The user can answer a question on Human Calculator mode + Given A logged-in user + When I play on Human Calculator mode and answer incorrectly + Then The game ends \ No newline at end of file diff --git a/webapp/e2e/features/play-classic.feature b/webapp/e2e/features/play-classic.feature index a823f926..46d0bfdd 100644 --- a/webapp/e2e/features/play-classic.feature +++ b/webapp/e2e/features/play-classic.feature @@ -1,6 +1,6 @@ Feature: Answering a question -Scenario: The user can answer a question +Scenario: The user can answer a question on Classic mode Given A logged-in user When I play on Classic mode and click on an answer Then The right answer should be colored green \ No newline at end of file diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js new file mode 100644 index 00000000..2f3db9d7 --- /dev/null +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -0,0 +1,65 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); + +const feature = loadFeature("./features/play-calculator.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + setDefaultOptions({ timeout: 10000 }); + + await page.goto("http://localhost:3000", { + waitUntil: "networkidle0", + }); + }); + + test("The user can answer a question on Human Calculator mode", ({ given, when, then }) => { + given("A logged-in user", async () => { + + await page.type("#login-username", "testuser"); + await page.type("#login-password", "Testpassword1"); + await page.click("button", { text: "Login" }); + await page.waitForNavigation(); + }); + + when("I play on Human Calculator mode and answer incorrectly", async () => { + + await page.click('[data-testid="calculator"]'); + await page.waitForNavigation(); + + await page.waitForSelector('[data-testid="operation"]'); + + const operation = await page.evaluate(() => { + return document.querySelector('[data-testid="operation"]').textContent; + }); + + const answer = -1; + + await page.type('[data-testid="answer-input"]', answer.toString()); + + await page.click('[data-testid="submit-button"]'); + }); + + then("The game ends", async () => { + await page.waitForSelector('[data-testid="game-over"]'); + + const gameOverMessage = await page.evaluate(() => { + return document.querySelector('[data-testid="game-over"]').textContent; + }); + + expect(gameOverMessage).toContain("¡Juego terminado!"); + }); + }); + + afterAll(async () => { + await browser.close(); + }); +}); diff --git a/webapp/e2e/steps/play-classic.steps.js b/webapp/e2e/steps/play-classic.steps.js index d3ffef19..9fd3c658 100644 --- a/webapp/e2e/steps/play-classic.steps.js +++ b/webapp/e2e/steps/play-classic.steps.js @@ -21,7 +21,7 @@ defineFeature(feature, (test) => { }); }); - test("The user can answer a question", ({ given, when, then }) => { + test("The user can answer a question on Classic mode", ({ given, when, then }) => { given("A logged-in user", async () => { await page.type("#login-username", "testuser"); @@ -31,7 +31,7 @@ defineFeature(feature, (test) => { }); when("I play on Classic mode and click on an answer", async () => { - await page.click('[data-testid="classic-mode"]'); + await page.click('[data-testid="classic"]'); await page.waitForNavigation(); await page.waitForSelector('[data-testid="question"]'); @@ -40,15 +40,23 @@ defineFeature(feature, (test) => { }); then("The right answer should be colored green", async () => { - await page.waitForTimeout(3000); - - const correctAnswerColor = await page.evaluate(() => { - const answerButton = document.querySelector('[data-testid="answer-button"]'); - return window.getComputedStyle(answerButton).getPropertyValue("background-color"); + await page.waitForTimeout(3000); + + const answerButtons = await page.$$('[data-testid^="answer-button"]'); + let isGreen = false; + + for (const button of answerButtons) { + const buttonColor = await button.evaluate((el) => { + return window.getComputedStyle(el).getPropertyValue("background-color"); + }); + if (buttonColor === "rgb(16, 255, 0)") { + isGreen = true; + break; + } + } + + expect(isGreen).toBe(true); }); - - expect(correctAnswerColor).toMatch("rgb(16, 255, 0)"); - }); }); afterAll(async () => { diff --git a/webapp/src/pages/Calculadora/Calculadora.js b/webapp/src/pages/Calculadora/Calculadora.js index 3aa83b06..d2319d90 100644 --- a/webapp/src/pages/Calculadora/Calculadora.js +++ b/webapp/src/pages/Calculadora/Calculadora.js @@ -175,7 +175,7 @@ const CalculadoraHumana = () => { {t('pages.humancalculator.finished')}

Tu puntuación: {puntuacion}

- @@ -185,8 +185,8 @@ const CalculadoraHumana = () => { ) : ( - - ¿{operation}? + + {operation} = ? { value={valSubmit} onChange={(e) => setValSubmit(e.target.value)} onKeyDown={handleKeyDown} + data-testid="answer-input" /> - diff --git a/webapp/src/pages/Clasico/Clasico.js b/webapp/src/pages/Clasico/Clasico.js index d1da9374..4803c356 100644 --- a/webapp/src/pages/Clasico/Clasico.js +++ b/webapp/src/pages/Clasico/Clasico.js @@ -279,7 +279,7 @@ const JuegoPreguntas = () => { ) : ( - + {t("pages.classic.question")} {indicePregunta + 1}

{preguntaActual.pregunta}

@@ -294,6 +294,7 @@ const JuegoPreguntas = () => { padding={"1rem"} height={"fit-content"} minHeight={"3rem"} + data-testid={`answer-button-${index}`} > {respuesta} @@ -311,6 +312,7 @@ const JuegoPreguntas = () => { disabled={tiempoRestante === 0 || juegoTerminado} colorScheme="teal" m={2} + data-testid="answer-button" > {t("pages.classic.answer")} From 2e15b161547c37f51a1b7698875dfa9f6e9443b5 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 17:49:50 +0200 Subject: [PATCH 13/26] Cambiando a jordi --- webapp/public/jordi.png | Bin 9544 -> 57151 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/webapp/public/jordi.png b/webapp/public/jordi.png index f515be0670f8124f546f24c6532348983c4cacbf..9a83859c1d59394ebffc011a76e84451548ccf09 100644 GIT binary patch literal 57151 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
  • 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�#!~AG) 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 From 40646621a943fe4c176113ea3a34078a7eb64c39 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Tue, 23 Apr 2024 19:30:53 +0200 Subject: [PATCH 14/26] Fixes --- webapp/e2e/steps/logout.steps.js | 4 ++-- webapp/e2e/steps/play-calculator.steps.js | 6 ++++-- webapp/e2e/steps/play-classic.steps.js | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index 7e10e8d3..f47c2ee0 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -32,8 +32,8 @@ defineFeature(feature, (test) => { password = "Testpassword1"; await page.waitForSelector("#login-username"); await page.type("#login-username", username); - await page.waitForSelector("#register-password"); - await page.type("#register-password", password); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); await page.click("button", { text: "Login" }); await page.waitForNavigation({ waitUntil: "networkidle0" }); }); diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index 2f3db9d7..f076de46 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -24,8 +24,10 @@ defineFeature(feature, (test) => { test("The user can answer a question on Human Calculator mode", ({ given, when, then }) => { given("A logged-in user", async () => { - await page.type("#login-username", "testuser"); - await page.type("#login-password", "Testpassword1"); + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); await page.click("button", { text: "Login" }); await page.waitForNavigation(); }); diff --git a/webapp/e2e/steps/play-classic.steps.js b/webapp/e2e/steps/play-classic.steps.js index 9fd3c658..8bd2e372 100644 --- a/webapp/e2e/steps/play-classic.steps.js +++ b/webapp/e2e/steps/play-classic.steps.js @@ -24,8 +24,10 @@ defineFeature(feature, (test) => { test("The user can answer a question on Classic mode", ({ given, when, then }) => { given("A logged-in user", async () => { - await page.type("#login-username", "testuser"); - await page.type("#login-password", "Testpassword1"); + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); await page.click("button", { text: "Login" }); await page.waitForNavigation(); }); From 7b32747602ab0708080db9ccd077ed8ba3854487 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 17:34:55 +0200 Subject: [PATCH 15/26] Fixes --- webapp/e2e/jest.config.js | 2 +- webapp/e2e/steps/play-calculator.steps.js | 6 ++++-- webapp/e2e/steps/play-classic.steps.js | 6 ++++-- webapp/e2e/steps/register-form.steps.js | 2 -- webapp/src/components/Nav/Nav.js | 20 ++++++++++---------- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/webapp/e2e/jest.config.js b/webapp/e2e/jest.config.js index db3be3d9..0d63114d 100644 --- a/webapp/e2e/jest.config.js +++ b/webapp/e2e/jest.config.js @@ -1,5 +1,5 @@ module.exports = { testMatch: ["**/steps/*.js"], - testTimeout: 30000, + testTimeout: 60000, setupFilesAfterEnv: ["expect-puppeteer"] } \ No newline at end of file diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index f076de46..8b269d33 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -20,10 +20,12 @@ defineFeature(feature, (test) => { waitUntil: "networkidle0", }); }); - + let username; + let password; test("The user can answer a question on Human Calculator mode", ({ given, when, then }) => { given("A logged-in user", async () => { - + username = "testuser"; + password = "Testpassword1"; await page.waitForSelector("#login-username"); await page.type("#login-username", username); await page.waitForSelector("#login-password"); diff --git a/webapp/e2e/steps/play-classic.steps.js b/webapp/e2e/steps/play-classic.steps.js index 8bd2e372..d67132b0 100644 --- a/webapp/e2e/steps/play-classic.steps.js +++ b/webapp/e2e/steps/play-classic.steps.js @@ -20,10 +20,12 @@ defineFeature(feature, (test) => { waitUntil: "networkidle0", }); }); - + let username; + let password; test("The user can answer a question on Classic mode", ({ given, when, then }) => { given("A logged-in user", async () => { - + username = "testuser"; + password = "Testpassword1"; await page.waitForSelector("#login-username"); await page.type("#login-username", username); await page.waitForSelector("#login-password"); diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js index 6a2435cf..c251ca93 100644 --- a/webapp/e2e/steps/register-form.steps.js +++ b/webapp/e2e/steps/register-form.steps.js @@ -28,8 +28,6 @@ defineFeature(feature, (test) => { let password; given("An unregistered user", async () => { - username = "pablo"; - password = "pabloasw"; await expect(page).toClick("a", { text: "Regístrate" }); }); diff --git a/webapp/src/components/Nav/Nav.js b/webapp/src/components/Nav/Nav.js index f4a6c1f2..47979c5b 100644 --- a/webapp/src/components/Nav/Nav.js +++ b/webapp/src/components/Nav/Nav.js @@ -283,7 +283,7 @@ const Nav = () => { WIQ - + {t("components.nav.home")} @@ -292,13 +292,13 @@ const Nav = () => { {t("components.nav.gameModes")} - + {t("components.nav.classic")} - + {t("components.nav.wisebattery")} - + {t("components.nav.humancalculator")} @@ -309,20 +309,20 @@ const Nav = () => { {t("components.nav.social")} - + {t("components.nav.users")} - + {t("components.nav.friends")} - + {t("components.nav.groups")} - + {t("components.nav.usergroups")} - {t("components.nav.stats")} - {t("components.nav.ranking")} + {t("components.nav.stats")} + {t("components.nav.ranking")} From ac2e89aec8f6411dd32e08bbfe927c82bb43c052 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 18:31:08 +0200 Subject: [PATCH 16/26] =?UTF-8?q?Fixes=20y=20a=C3=B1adidos=20al=20NavBar?= =?UTF-8?q?=20links=20de=20config=20y=20sobre=20nosotros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/steps/aboutus.steps.js | 56 ---------------------- webapp/e2e/steps/creategroup.steps.js | 11 +++-- webapp/e2e/steps/history.steps.js | 58 ----------------------- webapp/e2e/steps/logout.steps.js | 2 - webapp/e2e/steps/play-calculator.steps.js | 1 - webapp/e2e/steps/profile.steps.js | 58 ----------------------- webapp/e2e/steps/ranking-sorting.steps.js | 5 +- webapp/e2e/steps/ranking.steps.js | 5 +- webapp/e2e/steps/stats.steps.js | 5 +- webapp/e2e/steps/users-addfriend.steps.js | 4 +- webapp/src/components/Nav/Nav.js | 11 +++++ webapp/src/pages/Home/Home.js | 3 ++ webapp/src/pages/Ranking/Ranking.js | 3 ++ 13 files changed, 35 insertions(+), 187 deletions(-) delete mode 100644 webapp/e2e/steps/aboutus.steps.js delete mode 100644 webapp/e2e/steps/history.steps.js delete mode 100644 webapp/e2e/steps/profile.steps.js diff --git a/webapp/e2e/steps/aboutus.steps.js b/webapp/e2e/steps/aboutus.steps.js deleted file mode 100644 index 3591b020..00000000 --- a/webapp/e2e/steps/aboutus.steps.js +++ /dev/null @@ -1,56 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/aboutus.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can view the About Us page", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username = "testuser"; - password = "Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the About Us link", async () => { - await page.waitForSelector('[data-testid="about-us-link"]'); - await page.click('[data-testid="about-us-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The About Us page should be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/sobre"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); \ No newline at end of file diff --git a/webapp/e2e/steps/creategroup.steps.js b/webapp/e2e/steps/creategroup.steps.js index 95ca5ce8..31f57bca 100644 --- a/webapp/e2e/steps/creategroup.steps.js +++ b/webapp/e2e/steps/creategroup.steps.js @@ -39,8 +39,9 @@ defineFeature(feature, (test) => { }); when("I click on the Groups link and create a group", async () => { - await page.click('[data-testid="groups"]'); - await page.waitForNavigation(); + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-grupos-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.waitForSelector('[name="name"]'); await page.type('[name="name"]', "Test Group"); @@ -49,8 +50,10 @@ defineFeature(feature, (test) => { }); then("The Group should be shown on the My Groups page", async () => { - await page.click('[data-testid="my-groups"]'); - await page.waitForNavigation(); + + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-misgrupos-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); const groupExists = await page.evaluate(() => { const groupName = "Test Group"; diff --git a/webapp/e2e/steps/history.steps.js b/webapp/e2e/steps/history.steps.js deleted file mode 100644 index 7e0c906c..00000000 --- a/webapp/e2e/steps/history.steps.js +++ /dev/null @@ -1,58 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/history.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can view the History page", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username = "testuser"; - password = "Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the History link", async () => { - await page.waitForSelector('[data-testid="profile-menu"]'); - await page.click('[data-testid="profile-menu"]'); - await page.waitForSelector('[data-testid="history-link"]'); - await page.click('[data-testid="history-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The History page should be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/history"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); \ No newline at end of file diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index f47c2ee0..a0b94a84 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -39,8 +39,6 @@ defineFeature(feature, (test) => { }); when("I click on the Logout link", async () => { - await page.waitForSelector('[data-testid="profile-menu"]'); - await page.click('[data-testid="profile-menu"]'); await page.waitForSelector('[data-testid="logout-link"]'); await page.click('[data-testid="logout-link"]'); await page.waitForNavigation({ waitUntil: "networkidle0" }); diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index 8b269d33..8be455d9 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -35,7 +35,6 @@ defineFeature(feature, (test) => { }); when("I play on Human Calculator mode and answer incorrectly", async () => { - await page.click('[data-testid="calculator"]'); await page.waitForNavigation(); diff --git a/webapp/e2e/steps/profile.steps.js b/webapp/e2e/steps/profile.steps.js deleted file mode 100644 index 439b72e8..00000000 --- a/webapp/e2e/steps/profile.steps.js +++ /dev/null @@ -1,58 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/profile.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can see his Profile page", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username="testuser"; - password="Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Profile link", async () => { - await page.waitForSelector('[data-testid="profile-menu"]'); - await page.click('[data-testid="profile-menu"]'); - await page.waitForSelector('[data-testid="profile-link"]'); - await page.click('[data-testid="profile-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The user's profile shoud be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/perfil/testuser"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); diff --git a/webapp/e2e/steps/ranking-sorting.steps.js b/webapp/e2e/steps/ranking-sorting.steps.js index aec2c216..64a6228f 100644 --- a/webapp/e2e/steps/ranking-sorting.steps.js +++ b/webapp/e2e/steps/ranking-sorting.steps.js @@ -39,8 +39,9 @@ defineFeature(feature, (test) => { }); when("I click on the Ranking link and in Sort by Total Points", async () => { - await page.waitForSelector('[data-testid="ranking-link"]'); - await page.click('[data-testid="ranking-link"]'); + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-ranking-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.select('[data-testid="combobox"]', "totalPoints"); await page.waitForSelector('th', { text: "Puntos totales" }); await page.waitForSelector('td'); diff --git a/webapp/e2e/steps/ranking.steps.js b/webapp/e2e/steps/ranking.steps.js index d128aa66..d607fcaa 100644 --- a/webapp/e2e/steps/ranking.steps.js +++ b/webapp/e2e/steps/ranking.steps.js @@ -39,8 +39,9 @@ defineFeature(feature, (test) => { }); when("I click on the Ranking link and in Battery gamemode", async () => { - await page.waitForSelector('[data-testid="ranking-link"]'); - await page.click('[data-testid="ranking-link"]'); + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-ranking-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.click('[data-testid="battery-button"]'); await page.waitForSelector('button.active'); await page.waitForNavigation({ waitUntil: "networkidle0" }); diff --git a/webapp/e2e/steps/stats.steps.js b/webapp/e2e/steps/stats.steps.js index 81b9ec47..083d7a87 100644 --- a/webapp/e2e/steps/stats.steps.js +++ b/webapp/e2e/steps/stats.steps.js @@ -39,8 +39,9 @@ defineFeature(feature, (test) => { }); when("I click on the Stats link and in Calculator gamemode", async () => { - await page.waitForSelector('[data-testid="stats-link"]'); - await page.click('[data-testid="stats-link"]'); + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-stats-link"]'); + await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.click('[data-testid="calculator-button"]'); await page.waitForSelector('button.active'); await page.waitForNavigation({ waitUntil: "networkidle0" }); diff --git a/webapp/e2e/steps/users-addfriend.steps.js b/webapp/e2e/steps/users-addfriend.steps.js index ddb5ad96..c9856cf7 100644 --- a/webapp/e2e/steps/users-addfriend.steps.js +++ b/webapp/e2e/steps/users-addfriend.steps.js @@ -39,8 +39,8 @@ defineFeature(feature, (test) => { }); when("I click on the Users link and add a friend", async () => { - await page.waitForSelector('[data-testid="users"]'); - await page.click('[data-testid="users"]'); + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-users-link"]'); await page.waitForNavigation({ waitUntil: "networkidle0" }); }); diff --git a/webapp/src/components/Nav/Nav.js b/webapp/src/components/Nav/Nav.js index 47979c5b..2c199845 100644 --- a/webapp/src/components/Nav/Nav.js +++ b/webapp/src/components/Nav/Nav.js @@ -325,6 +325,17 @@ const Nav = () => { {t("components.nav.ranking")} + + + + + {t("components.nav.config")} + + + {t("components.nav.about")} + + + diff --git a/webapp/src/pages/Home/Home.js b/webapp/src/pages/Home/Home.js index b1721941..a5eff8c1 100644 --- a/webapp/src/pages/Home/Home.js +++ b/webapp/src/pages/Home/Home.js @@ -32,6 +32,7 @@ const Home = () => { title={t('pages.home.classic')} text={t('pages.home.classicDescription')} route="/home/clasico" + data-testid="classic" /> @@ -39,6 +40,7 @@ const Home = () => { title={t('pages.home.wisebattery')} text={t('pages.home.wisebatteryDescription')} route="/home/bateria" + data-testid="battery" /> @@ -46,6 +48,7 @@ const Home = () => { title={t('pages.home.humancalculator')} text={t('pages.home.humancalculatorDescription')} route="/home/calculadora" + data-testid="calculator" /> {error && ( diff --git a/webapp/src/pages/Ranking/Ranking.js b/webapp/src/pages/Ranking/Ranking.js index 750fde6f..8ff15bc7 100644 --- a/webapp/src/pages/Ranking/Ranking.js +++ b/webapp/src/pages/Ranking/Ranking.js @@ -128,18 +128,21 @@ const Ranking = () => { From 77e76dba90baf23f7ca5b5980d272b960f53bfc2 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 18:33:34 +0200 Subject: [PATCH 17/26] Eliminando test e2e innecesarios --- webapp/e2e/features/aboutus.feature | 6 --- webapp/e2e/features/config.feature | 6 --- webapp/e2e/features/history.feature | 6 --- webapp/e2e/features/profile.feature | 6 --- webapp/e2e/steps/config.steps.js | 58 ----------------------------- 5 files changed, 82 deletions(-) delete mode 100644 webapp/e2e/features/aboutus.feature delete mode 100644 webapp/e2e/features/config.feature delete mode 100644 webapp/e2e/features/history.feature delete mode 100644 webapp/e2e/features/profile.feature delete mode 100644 webapp/e2e/steps/config.steps.js diff --git a/webapp/e2e/features/aboutus.feature b/webapp/e2e/features/aboutus.feature deleted file mode 100644 index a5feb075..00000000 --- a/webapp/e2e/features/aboutus.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing logged user's profile - -Scenario: The user can view the About Us page - Given A logged-in user - When I click on the About Us link - Then The About Us page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/config.feature b/webapp/e2e/features/config.feature deleted file mode 100644 index cabd0110..00000000 --- a/webapp/e2e/features/config.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing logged user's profile - -Scenario: The user can view the Configuration page - Given A logged-in user - When I click on the Configuration link - Then The Configuration page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/history.feature b/webapp/e2e/features/history.feature deleted file mode 100644 index 0438ec4b..00000000 --- a/webapp/e2e/features/history.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing logged user's profile - -Scenario: The user can view the History page - Given A logged-in user - When I click on the History link - Then The History page should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/profile.feature b/webapp/e2e/features/profile.feature deleted file mode 100644 index 8edc5b87..00000000 --- a/webapp/e2e/features/profile.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing logged user's profile - -Scenario: The user can see his Profile page - Given A logged-in user - When I click on the Profile link - Then The user's profile shoud be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/config.steps.js b/webapp/e2e/steps/config.steps.js deleted file mode 100644 index a1302056..00000000 --- a/webapp/e2e/steps/config.steps.js +++ /dev/null @@ -1,58 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/config.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can view the Configuration page", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username = "testuser"; - password = "Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Configuration link", async () => { - await page.waitForSelector('[data-testid="profile-menu"]'); - await page.click('[data-testid="profile-menu"]'); - await page.waitForSelector('[data-testid="config-link"]'); - await page.click('[data-testid="config-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The Configuration page should be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/config"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); \ No newline at end of file From 72fdc96f2dac0e95cda8bfc99ec94c6655bdf362 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 19:04:02 +0200 Subject: [PATCH 18/26] More fixes --- webapp/e2e/steps/logout.steps.js | 2 +- webapp/e2e/steps/play-calculator.steps.js | 4 ++-- webapp/e2e/steps/play-classic.steps.js | 4 ++-- webapp/e2e/steps/stats.steps.js | 1 - webapp/e2e/steps/users-addfriend.steps.js | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index a0b94a84..162ce898 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -44,7 +44,7 @@ defineFeature(feature, (test) => { await page.waitForNavigation({ waitUntil: "networkidle0" }); }); - then("The user should be logged out", async () => { + then("The user should be logged out and the Login screen should be shown", async () => { const url = page.url(); expect(url).toContain("/login"); }); diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index 8be455d9..84b872f6 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -31,12 +31,12 @@ defineFeature(feature, (test) => { await page.waitForSelector("#login-password"); await page.type("#login-password", password); await page.click("button", { text: "Login" }); - await page.waitForNavigation(); + await page.waitForNavigation({ waitUntil: "networkidle0" }); }); when("I play on Human Calculator mode and answer incorrectly", async () => { await page.click('[data-testid="calculator"]'); - await page.waitForNavigation(); + await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.waitForSelector('[data-testid="operation"]'); diff --git a/webapp/e2e/steps/play-classic.steps.js b/webapp/e2e/steps/play-classic.steps.js index d67132b0..f46c0d19 100644 --- a/webapp/e2e/steps/play-classic.steps.js +++ b/webapp/e2e/steps/play-classic.steps.js @@ -31,12 +31,12 @@ defineFeature(feature, (test) => { await page.waitForSelector("#login-password"); await page.type("#login-password", password); await page.click("button", { text: "Login" }); - await page.waitForNavigation(); + await page.waitForNavigation({ waitUntil: "networkidle0" }); }); when("I play on Classic mode and click on an answer", async () => { await page.click('[data-testid="classic"]'); - await page.waitForNavigation(); + await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.waitForSelector('[data-testid="question"]'); diff --git a/webapp/e2e/steps/stats.steps.js b/webapp/e2e/steps/stats.steps.js index 083d7a87..123bdb48 100644 --- a/webapp/e2e/steps/stats.steps.js +++ b/webapp/e2e/steps/stats.steps.js @@ -44,7 +44,6 @@ defineFeature(feature, (test) => { await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.click('[data-testid="calculator-button"]'); await page.waitForSelector('button.active'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); }); then("The user's stats in Calculator gamemode shoud be shown on screen", async () => { diff --git a/webapp/e2e/steps/users-addfriend.steps.js b/webapp/e2e/steps/users-addfriend.steps.js index c9856cf7..30864473 100644 --- a/webapp/e2e/steps/users-addfriend.steps.js +++ b/webapp/e2e/steps/users-addfriend.steps.js @@ -40,7 +40,7 @@ defineFeature(feature, (test) => { when("I click on the Users link and add a friend", async () => { await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-users-link"]'); + await page.click('[data-testid="home-usuarios-link"]'); await page.waitForNavigation({ waitUntil: "networkidle0" }); }); From 3a28848874ba007429d52269a386d9ce065b7e87 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 19:08:26 +0200 Subject: [PATCH 19/26] =?UTF-8?q?Eliminando=20m=C3=A1s=20test=20innecesari?= =?UTF-8?q?os?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/steps/ranking-sorting.steps.js | 64 ---------------------- webapp/e2e/steps/ranking.steps.js | 63 ---------------------- webapp/e2e/steps/stats.steps.js | 62 --------------------- webapp/e2e/steps/users-addfriend.steps.js | 65 ----------------------- 4 files changed, 254 deletions(-) delete mode 100644 webapp/e2e/steps/ranking-sorting.steps.js delete mode 100644 webapp/e2e/steps/ranking.steps.js delete mode 100644 webapp/e2e/steps/stats.steps.js delete mode 100644 webapp/e2e/steps/users-addfriend.steps.js diff --git a/webapp/e2e/steps/ranking-sorting.steps.js b/webapp/e2e/steps/ranking-sorting.steps.js deleted file mode 100644 index 64a6228f..00000000 --- a/webapp/e2e/steps/ranking-sorting.steps.js +++ /dev/null @@ -1,64 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/ranking-sorting.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can see the Ranking page and change sorting filter", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username="testuser"; - password="Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Ranking link and in Sort by Total Points", async () => { - await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-ranking-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - await page.select('[data-testid="combobox"]', "totalPoints"); - await page.waitForSelector('th', { text: "Puntos totales" }); - await page.waitForSelector('td'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The ranking (sorted by Total Points) should be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/ranking"); - const displayedField = await page.evaluate(() => { - return document.querySelector('th').textContent; - }); - expect(displayedField).toContain("Puntos totales"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); diff --git a/webapp/e2e/steps/ranking.steps.js b/webapp/e2e/steps/ranking.steps.js deleted file mode 100644 index d607fcaa..00000000 --- a/webapp/e2e/steps/ranking.steps.js +++ /dev/null @@ -1,63 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/ranking.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can see the Ranking page and change gamemode", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username="testuser"; - password="Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Ranking link and in Battery gamemode", async () => { - await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-ranking-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - await page.click('[data-testid="battery-button"]'); - await page.waitForSelector('button.active'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The ranking of the Battery gamemode should be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/ranking"); - const statsText = await page.evaluate(() => { - return document.querySelector("h2").textContent; - }); - expect(statsText).toContain("Batería de sabios"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); diff --git a/webapp/e2e/steps/stats.steps.js b/webapp/e2e/steps/stats.steps.js deleted file mode 100644 index 123bdb48..00000000 --- a/webapp/e2e/steps/stats.steps.js +++ /dev/null @@ -1,62 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/stats.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can see his Stats page and change gamemode", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username="testuser"; - password="Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Stats link and in Calculator gamemode", async () => { - await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-stats-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - await page.click('[data-testid="calculator-button"]'); - await page.waitForSelector('button.active'); - }); - - then("The user's stats in Calculator gamemode shoud be shown on screen", async () => { - const url = page.url(); - expect(url).toContain("/stats"); - const statsText = await page.evaluate(() => { - return document.querySelector("h2").textContent; - }); - expect(statsText).toContain("Calculadora humana"); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); diff --git a/webapp/e2e/steps/users-addfriend.steps.js b/webapp/e2e/steps/users-addfriend.steps.js deleted file mode 100644 index 30864473..00000000 --- a/webapp/e2e/steps/users-addfriend.steps.js +++ /dev/null @@ -1,65 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/users-addfriend.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can add a friend", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username="testuser"; - password="Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Users link and add a friend", async () => { - await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-usuarios-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - then("The user should disappear from the Users page", async () => { - const url = page.url(); - expect(url).toContain("/social/"); - - const numUsersBefore = await page.$$eval('tr', (rows) => rows.length); - - await page.click('[data-testid="add-friend-button-0"]'); - await page.waitForTimeout(1000); - - const numUsersAfter = await page.$$eval('tr', (rows) => rows.length); - - expect(numUsersAfter).toBeLessThan(numUsersBefore); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); From 1650f67a9461d5df3b6639e9d26856bcabbd91ab Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 19:36:57 +0200 Subject: [PATCH 20/26] Ya va el e2e de classic --- webapp/e2e/features/ranking-sorting.feature | 6 -- webapp/e2e/features/ranking.feature | 6 -- webapp/e2e/features/stats.feature | 6 -- webapp/e2e/features/users-addfriend.feature | 6 -- webapp/e2e/steps/creategroup.steps.js | 71 --------------------- webapp/e2e/steps/logout.steps.js | 1 + webapp/e2e/steps/play-calculator.steps.js | 16 +++-- webapp/e2e/steps/play-classic.steps.js | 15 +++-- 8 files changed, 24 insertions(+), 103 deletions(-) delete mode 100644 webapp/e2e/features/ranking-sorting.feature delete mode 100644 webapp/e2e/features/ranking.feature delete mode 100644 webapp/e2e/features/stats.feature delete mode 100644 webapp/e2e/features/users-addfriend.feature delete mode 100644 webapp/e2e/steps/creategroup.steps.js diff --git a/webapp/e2e/features/ranking-sorting.feature b/webapp/e2e/features/ranking-sorting.feature deleted file mode 100644 index 312c77a1..00000000 --- a/webapp/e2e/features/ranking-sorting.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing ranking and changing sorting filter - -Scenario: The user can see the Ranking page and change sorting filter - Given A logged-in user - When I click on the Ranking link and in Sort by Total Points - Then The ranking (sorted by Total Points) should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/ranking.feature b/webapp/e2e/features/ranking.feature deleted file mode 100644 index 497c9bfc..00000000 --- a/webapp/e2e/features/ranking.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing ranking and changing gamemode - -Scenario: The user can see the Ranking page and change gamemode - Given A logged-in user - When I click on the Ranking link and in Battery gamemode - Then The ranking of the Battery gamemode should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/stats.feature b/webapp/e2e/features/stats.feature deleted file mode 100644 index 275b00f5..00000000 --- a/webapp/e2e/features/stats.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Seeing logged user's stats and changing gamemode - -Scenario: The user can see his Stats page and change gamemode - Given A logged-in user - When I click on the Stats link and in Calculator gamemode - Then The user's stats in Calculator gamemode shoud be shown on screen \ No newline at end of file diff --git a/webapp/e2e/features/users-addfriend.feature b/webapp/e2e/features/users-addfriend.feature deleted file mode 100644 index e5524cea..00000000 --- a/webapp/e2e/features/users-addfriend.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Adding a new friend - -Scenario: The user can add a friend - Given A logged-in user - When I click on the Users link and add a friend - Then The user should disappear from the Users page \ No newline at end of file diff --git a/webapp/e2e/steps/creategroup.steps.js b/webapp/e2e/steps/creategroup.steps.js deleted file mode 100644 index 31f57bca..00000000 --- a/webapp/e2e/steps/creategroup.steps.js +++ /dev/null @@ -1,71 +0,0 @@ -const puppeteer = require("puppeteer"); -const { defineFeature, loadFeature } = require("jest-cucumber"); -const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; -const { expect } = require("expect-puppeteer"); -const feature = loadFeature("./features/creategroup.feature"); - -let page; -let browser; - -defineFeature(feature, (test) => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }); - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test("The user can create a group", ({ given, when, then }) => { - let username; - let password; - - given("A logged-in user", async () => { - username="testuser"; - password="Testpassword1"; - await page.waitForSelector("#login-username"); - await page.type("#login-username", username); - await page.waitForSelector("#login-password"); - await page.type("#login-password", password); - await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - when("I click on the Groups link and create a group", async () => { - await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-grupos-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - - await page.waitForSelector('[name="name"]'); - await page.type('[name="name"]', "Test Group"); - await page.click("button", { text: "Crear" }); - await page.waitForTimeout(1000); - }); - - then("The Group should be shown on the My Groups page", async () => { - - await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-misgrupos-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - - const groupExists = await page.evaluate(() => { - const groupName = "Test Group"; - const groups = Array.from(document.querySelectorAll("tbody tr td:first-child")); - return groups.some(td => td.textContent === groupName); - }); - - expect(groupExists).toBe(true); - }); - }); - - afterAll(async () => { - browser.close(); - }); -}); diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index 162ce898..23521cda 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -39,6 +39,7 @@ defineFeature(feature, (test) => { }); when("I click on the Logout link", async () => { + await page.waitForTimeout(1000); await page.waitForSelector('[data-testid="logout-link"]'); await page.click('[data-testid="logout-link"]'); await page.waitForNavigation({ waitUntil: "networkidle0" }); diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index 84b872f6..67c4017e 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -31,13 +31,20 @@ defineFeature(feature, (test) => { await page.waitForSelector("#login-password"); await page.type("#login-password", password); await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + + //await page.waitForNavigation({ waitUntil: "networkidle0" }); }); when("I play on Human Calculator mode and answer incorrectly", async () => { - await page.click('[data-testid="calculator"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + await page.waitForTimeout(1000); + await page.waitForXPath('//button[contains(text(), "Calculadora humana")]'); + const button = await page.$x('//button[contains(text(), "Calculadora humana")]'); + await button[0].click(); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + await page.waitForXPath('//section[contains(@class, "chakra-modal__content")]//button[contains(text(), "Jugar")]'); + const jugarButton = await page.$x('//section[contains(@class, "chakra-modal__content")]//button[contains(text(), "Jugar")]'); + await jugarButton[0].click(); await page.waitForSelector('[data-testid="operation"]'); const operation = await page.evaluate(() => { @@ -55,7 +62,8 @@ defineFeature(feature, (test) => { await page.waitForSelector('[data-testid="game-over"]'); const gameOverMessage = await page.evaluate(() => { - return document.querySelector('[data-testid="game-over"]').textContent; + return document.querySelector('h2:contains("¡Juego terminado!")'); + }); expect(gameOverMessage).toContain("¡Juego terminado!"); diff --git a/webapp/e2e/steps/play-classic.steps.js b/webapp/e2e/steps/play-classic.steps.js index f46c0d19..7439b175 100644 --- a/webapp/e2e/steps/play-classic.steps.js +++ b/webapp/e2e/steps/play-classic.steps.js @@ -31,12 +31,19 @@ defineFeature(feature, (test) => { await page.waitForSelector("#login-password"); await page.type("#login-password", password); await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); }); when("I play on Classic mode and click on an answer", async () => { - await page.click('[data-testid="classic"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + await page.waitForTimeout(1000); + await page.waitForXPath('//button[contains(text(), "Clásico")]'); + const classicButton = await page.$x('//button[contains(text(), "Clásico")]'); + await classicButton[0].click(); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + + await page.waitForXPath('//section[contains(@class, "chakra-modal__content")]//button[contains(text(), "Jugar")]'); + const jugarButton = await page.$x('//section[contains(@class, "chakra-modal__content")]//button[contains(text(), "Jugar")]'); + await jugarButton[0].click(); await page.waitForSelector('[data-testid="question"]'); @@ -53,7 +60,7 @@ defineFeature(feature, (test) => { const buttonColor = await button.evaluate((el) => { return window.getComputedStyle(el).getPropertyValue("background-color"); }); - if (buttonColor === "rgb(16, 255, 0)") { + if (buttonColor.includes("rgb(44, 122, 123)")) { isGreen = true; break; } From c89893c00a0fa44686cdf199130c6940514f09fe Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 19:43:57 +0200 Subject: [PATCH 21/26] Ya va el e2e de calculadora --- webapp/e2e/steps/play-calculator.steps.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index 67c4017e..f21553fa 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -47,10 +47,6 @@ defineFeature(feature, (test) => { await jugarButton[0].click(); await page.waitForSelector('[data-testid="operation"]'); - const operation = await page.evaluate(() => { - return document.querySelector('[data-testid="operation"]').textContent; - }); - const answer = -1; await page.type('[data-testid="answer-input"]', answer.toString()); @@ -59,14 +55,14 @@ defineFeature(feature, (test) => { }); then("The game ends", async () => { - await page.waitForSelector('[data-testid="game-over"]'); + await page.waitForSelector('[data-testid="play-again-button"]'); const gameOverMessage = await page.evaluate(() => { - return document.querySelector('h2:contains("¡Juego terminado!")'); + return document.querySelector('[data-testid="play-again-button"]').textContent; }); - expect(gameOverMessage).toContain("¡Juego terminado!"); + expect(gameOverMessage).toContain("Jugar de nuevo"); }); }); From dfc1e4cd0af2a5e6a8bbe872b18daca0b143f17d Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 20:05:42 +0200 Subject: [PATCH 22/26] =?UTF-8?q?A=C3=B1adido=20test=20e2e=20de=20bateria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/play-battery.feature | 6 ++ webapp/e2e/steps/play-battery.steps.js | 73 ++++++++++++++++++++++++ webapp/src/pages/Bateria/Bateria.js | 2 +- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 webapp/e2e/features/play-battery.feature create mode 100644 webapp/e2e/steps/play-battery.steps.js diff --git a/webapp/e2e/features/play-battery.feature b/webapp/e2e/features/play-battery.feature new file mode 100644 index 00000000..37631072 --- /dev/null +++ b/webapp/e2e/features/play-battery.feature @@ -0,0 +1,6 @@ +Feature: Answering a question + +Scenario: The user can answer a question on Battery mode + Given A logged-in user + When I play on Battery mode and click on an answer + Then The next question should be loaded on screen \ No newline at end of file diff --git a/webapp/e2e/steps/play-battery.steps.js b/webapp/e2e/steps/play-battery.steps.js new file mode 100644 index 00000000..06d14eac --- /dev/null +++ b/webapp/e2e/steps/play-battery.steps.js @@ -0,0 +1,73 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); + +const feature = loadFeature("./features/play-battery.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + setDefaultOptions({ timeout: 10000 }); + + await page.goto("http://localhost:3000", { + waitUntil: "networkidle0", + }); + }); + let username; + let password; + let firstquestion; + test("The user can answer a question on Battery mode", ({ given, when, then }) => { + given("A logged-in user", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I play on Battery mode and click on an answer", async () => { + await page.waitForTimeout(1000); + await page.waitForXPath('//button[contains(text(), "Batería de sabios")]'); + const button = await page.$x('//button[contains(text(), "Batería de sabios")]'); + await button[0].click(); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + + await page.waitForXPath('//section[contains(@class, "chakra-modal__content")]//button[contains(text(), "Jugar")]'); + const jugarButton = await page.$x('//section[contains(@class, "chakra-modal__content")]//button[contains(text(), "Jugar")]'); + await jugarButton[0].click(); + + await page.waitForSelector('[data-testid="question"]'); + + firstquestion = await page.evaluate(element => + element.textContent, await page.$('[data-testid="question"]')); + + await page.click('[data-testid="answer-button"]'); + }); + + then("The next question should be loaded on screen", async () => { + await page.waitForTimeout(3000); + + await page.waitForSelector('[data-testid="question"]'); + + let secondquestion = await page.evaluate(element => + element.textContent, await page.$('[data-testid="question"]')); + + let areDifferent=firstquestion !== secondquestion; + expect(areDifferent).toBe(true); + }); + }); + + afterAll(async () => { + await browser.close(); + }); +}); diff --git a/webapp/src/pages/Bateria/Bateria.js b/webapp/src/pages/Bateria/Bateria.js index 1c1c0303..5e7ab999 100644 --- a/webapp/src/pages/Bateria/Bateria.js +++ b/webapp/src/pages/Bateria/Bateria.js @@ -216,7 +216,7 @@ const JuegoPreguntas = () => { ) : ( - + {t("pages.wisebattery.question")} {indicePregunta + 1}

    {preguntaActual.pregunta}

    From 91c4ddc2bf0f4be4de0a74d2aa5f7d35c7010a41 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 20:35:45 +0200 Subject: [PATCH 23/26] =?UTF-8?q?A=C3=B1adido=20test=20de=20creacion=20de?= =?UTF-8?q?=20grupo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...eategroup.feature => create-group.feature} | 0 webapp/e2e/steps/create-group.steps.js | 68 +++++++++++++++++++ webapp/e2e/steps/logout.steps.js | 6 +- webapp/src/components/Nav/Nav.js | 8 +++ 4 files changed, 80 insertions(+), 2 deletions(-) rename webapp/e2e/features/{creategroup.feature => create-group.feature} (100%) create mode 100644 webapp/e2e/steps/create-group.steps.js diff --git a/webapp/e2e/features/creategroup.feature b/webapp/e2e/features/create-group.feature similarity index 100% rename from webapp/e2e/features/creategroup.feature rename to webapp/e2e/features/create-group.feature diff --git a/webapp/e2e/steps/create-group.steps.js b/webapp/e2e/steps/create-group.steps.js new file mode 100644 index 00000000..93fb1eac --- /dev/null +++ b/webapp/e2e/steps/create-group.steps.js @@ -0,0 +1,68 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); + +const feature = loadFeature("./features/create-group.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + setDefaultOptions({ timeout: 10000 }); + + await page.goto("http://localhost:3000", { + waitUntil: "networkidle0", + }); + }); + let username; + let password; + test("The user can create a group", ({ given, when, then }) => { + given("A logged-in user", async () => { + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I click on the Groups link and create a group", async () => { + await page.waitForTimeout(1000); + + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-grupos-link"]'); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + + await page.waitForSelector('[name="name"]'); + await page.type('[name="name"]', "Test Group"); + await page.click("button", { text: "Crear" }); + await page.waitForTimeout(1000); + }); + + then("The Group should be shown on the My Groups page", async () => { + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-misgrupos-link"]'); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + + const groupExists = await page.evaluate(() => { + const groupName = "Test Group"; + const groups = Array.from(document.querySelectorAll("tbody tr td:first-child")); + return groups.some(td => td.textContent === groupName); + }); + + expect(groupExists).toBe(true); + }); + }); + + afterAll(async () => { + await browser.close(); + }); +}); diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index 23521cda..866ce961 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -40,8 +40,10 @@ defineFeature(feature, (test) => { when("I click on the Logout link", async () => { await page.waitForTimeout(1000); - await page.waitForSelector('[data-testid="logout-link"]'); - await page.click('[data-testid="logout-link"]'); + + await page.click('button[aria-label="Abrir menú"]'); + await page.waitForSelector('[data-testid="home-logout-link"]'); + await page.click('[data-testid="home-logout-link"]'); await page.waitForNavigation({ waitUntil: "networkidle0" }); }); diff --git a/webapp/src/components/Nav/Nav.js b/webapp/src/components/Nav/Nav.js index 2c199845..8009fc19 100644 --- a/webapp/src/components/Nav/Nav.js +++ b/webapp/src/components/Nav/Nav.js @@ -336,6 +336,14 @@ const Nav = () => {
    + + + + + {t("components.nav.disconnect")} + + + From 7a67ba7b761e8e5f83d1b71bd957f4fd9298956a Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 21:02:35 +0200 Subject: [PATCH 24/26] Fixes --- webapp/e2e/steps/create-group.steps.js | 26 ++++++++++++++++++++----- webapp/e2e/steps/logout.steps.js | 4 ++-- webapp/e2e/steps/register-form.steps.js | 2 +- webapp/src/components/Nav/Nav.js | 4 ++-- webapp/src/pages/Bateria/Bateria.js | 1 + webapp/src/pages/Social/Groups.js | 2 +- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/webapp/e2e/steps/create-group.steps.js b/webapp/e2e/steps/create-group.steps.js index 93fb1eac..6a76a6ce 100644 --- a/webapp/e2e/steps/create-group.steps.js +++ b/webapp/e2e/steps/create-group.steps.js @@ -43,15 +43,31 @@ defineFeature(feature, (test) => { await page.waitForSelector('[name="name"]'); await page.type('[name="name"]', "Test Group"); - await page.click("button", { text: "Crear" }); - await page.waitForTimeout(1000); - }); + await page.waitForTimeout(2000); + await page.evaluate(() => { + var botones = document.querySelectorAll('button.chakra-button.css-r7xd4a[data-testid="addgroup-button"]'); + botones.forEach(function(boton) { + if (boton.textContent === "Crear") { + boton.click(); + } + }); + }); + }); then("The Group should be shown on the My Groups page", async () => { + await page.waitForTimeout(1000); await page.click('button[aria-label="Abrir menú"]'); - await page.click('[data-testid="home-misgrupos-link"]'); + await page.waitForTimeout(1000); + await page.evaluate(() => { + var enlaces = document.querySelectorAll('a.chakra-link.css-spn4bz[data-testid="home-misgrupos-link"]'); + enlaces.forEach(function(enlace) { + if (enlace.textContent === "Mis grupos") { + enlace.click(); + } + }); + }); //await page.waitForNavigation({ waitUntil: "networkidle0" }); - + await page.waitForTimeout(2000); const groupExists = await page.evaluate(() => { const groupName = "Test Group"; const groups = Array.from(document.querySelectorAll("tbody tr td:first-child")); diff --git a/webapp/e2e/steps/logout.steps.js b/webapp/e2e/steps/logout.steps.js index 866ce961..651ee829 100644 --- a/webapp/e2e/steps/logout.steps.js +++ b/webapp/e2e/steps/logout.steps.js @@ -35,7 +35,7 @@ defineFeature(feature, (test) => { await page.waitForSelector("#login-password"); await page.type("#login-password", password); await page.click("button", { text: "Login" }); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); }); when("I click on the Logout link", async () => { @@ -44,7 +44,7 @@ defineFeature(feature, (test) => { await page.click('button[aria-label="Abrir menú"]'); await page.waitForSelector('[data-testid="home-logout-link"]'); await page.click('[data-testid="home-logout-link"]'); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); }); then("The user should be logged out and the Login screen should be shown", async () => { diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js index c251ca93..fd603efd 100644 --- a/webapp/e2e/steps/register-form.steps.js +++ b/webapp/e2e/steps/register-form.steps.js @@ -32,7 +32,7 @@ defineFeature(feature, (test) => { }); when("I fill the data in the form and press submit", async () => { - username = "testuser"; + username = "papapa"; password = "Testpassword1"; await page.waitForSelector('#register-username'); await page.type('#register-username', username); diff --git a/webapp/src/components/Nav/Nav.js b/webapp/src/components/Nav/Nav.js index 8009fc19..4606ff8a 100644 --- a/webapp/src/components/Nav/Nav.js +++ b/webapp/src/components/Nav/Nav.js @@ -318,7 +318,7 @@ const Nav = () => { {t("components.nav.groups")} - + {t("components.nav.usergroups")} {t("components.nav.stats")} @@ -329,7 +329,7 @@ const Nav = () => { - {t("components.nav.config")} + {t("components.nav.options")} {t("components.nav.about")} diff --git a/webapp/src/pages/Bateria/Bateria.js b/webapp/src/pages/Bateria/Bateria.js index 5e7ab999..e9c868a3 100644 --- a/webapp/src/pages/Bateria/Bateria.js +++ b/webapp/src/pages/Bateria/Bateria.js @@ -230,6 +230,7 @@ const JuegoPreguntas = () => { padding={"1rem"} height={"fit-content"} minHeight={"3rem"} + data-testid={`answer-button-${index}`} > {respuesta} diff --git a/webapp/src/pages/Social/Groups.js b/webapp/src/pages/Social/Groups.js index ebad1366..d0e3b193 100644 --- a/webapp/src/pages/Social/Groups.js +++ b/webapp/src/pages/Social/Groups.js @@ -117,7 +117,7 @@ const Groups = () => { onChange={(e) => setName(e.target.value)} /> - From 46efcab3010daa1fe17da74d929c6952b4d68511 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 22:54:29 +0200 Subject: [PATCH 25/26] Ya funciona el e2e de crear grupo --- webapp/e2e/features/create-group.feature | 2 +- webapp/e2e/steps/create-group.steps.js | 38 +++++++---------------- webapp/e2e/steps/play-battery.steps.js | 2 +- webapp/e2e/steps/play-calculator.steps.js | 2 +- 4 files changed, 14 insertions(+), 30 deletions(-) diff --git a/webapp/e2e/features/create-group.feature b/webapp/e2e/features/create-group.feature index c1176f54..068e973b 100644 --- a/webapp/e2e/features/create-group.feature +++ b/webapp/e2e/features/create-group.feature @@ -3,4 +3,4 @@ Feature: Creating a group Scenario: The user can create a group Given A logged-in user When I click on the Groups link and create a group - Then The Group should be shown on the My Groups page \ No newline at end of file + Then The confirmation message should be shown on screen \ No newline at end of file diff --git a/webapp/e2e/steps/create-group.steps.js b/webapp/e2e/steps/create-group.steps.js index 6a76a6ce..58671132 100644 --- a/webapp/e2e/steps/create-group.steps.js +++ b/webapp/e2e/steps/create-group.steps.js @@ -42,39 +42,23 @@ defineFeature(feature, (test) => { //await page.waitForNavigation({ waitUntil: "networkidle0" }); await page.waitForSelector('[name="name"]'); - await page.type('[name="name"]', "Test Group"); + await page.type('[name="name"]', "Testgroup"); await page.waitForTimeout(2000); - await page.evaluate(() => { - var botones = document.querySelectorAll('button.chakra-button.css-r7xd4a[data-testid="addgroup-button"]'); - botones.forEach(function(boton) { - if (boton.textContent === "Crear") { - boton.click(); - } - }); - }); + await page.click('button[data-testid="addgroup-button"]'); }); - then("The Group should be shown on the My Groups page", async () => { - await page.waitForTimeout(1000); - await page.click('button[aria-label="Abrir menú"]'); + then("The confirmation message should be shown on screen", async () => { await page.waitForTimeout(1000); - await page.evaluate(() => { - var enlaces = document.querySelectorAll('a.chakra-link.css-spn4bz[data-testid="home-misgrupos-link"]'); - enlaces.forEach(function(enlace) { - if (enlace.textContent === "Mis grupos") { - enlace.click(); - } - }); - }); - //await page.waitForNavigation({ waitUntil: "networkidle0" }); - await page.waitForTimeout(2000); - const groupExists = await page.evaluate(() => { - const groupName = "Test Group"; - const groups = Array.from(document.querySelectorAll("tbody tr td:first-child")); - return groups.some(td => td.textContent === groupName); + await page.waitForSelector('div[role="alert"]'); + + // Obtén el texto del elemento que contiene el mensaje + const alertText = await page.evaluate(() => { + const alertElement = document.querySelector('div[role="alert"]'); + return alertElement.innerText.trim(); }); + const rightMessage=alertText === "Group created successfully"; + expect(rightMessage).toBe(true); - expect(groupExists).toBe(true); }); }); diff --git a/webapp/e2e/steps/play-battery.steps.js b/webapp/e2e/steps/play-battery.steps.js index 06d14eac..cdd50d57 100644 --- a/webapp/e2e/steps/play-battery.steps.js +++ b/webapp/e2e/steps/play-battery.steps.js @@ -51,7 +51,7 @@ defineFeature(feature, (test) => { firstquestion = await page.evaluate(element => element.textContent, await page.$('[data-testid="question"]')); - await page.click('[data-testid="answer-button"]'); + await page.click('[data-testid="answer-button-0"]'); }); then("The next question should be loaded on screen", async () => { diff --git a/webapp/e2e/steps/play-calculator.steps.js b/webapp/e2e/steps/play-calculator.steps.js index f21553fa..73ae60d1 100644 --- a/webapp/e2e/steps/play-calculator.steps.js +++ b/webapp/e2e/steps/play-calculator.steps.js @@ -47,7 +47,7 @@ defineFeature(feature, (test) => { await jugarButton[0].click(); await page.waitForSelector('[data-testid="operation"]'); - const answer = -1; + const answer = -999; await page.type('[data-testid="answer-input"]', answer.toString()); From 728e28211b5f86d9d6db3ccfca07745e7c5e7718 Mon Sep 17 00:00:00 2001 From: iyanfdezz Date: Wed, 24 Apr 2024 23:10:52 +0200 Subject: [PATCH 26/26] =?UTF-8?q?Test=20e2e=20de=20a=C3=B1adir=20amigo=20f?= =?UTF-8?q?unciona?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/e2e/features/add-friend.feature | 6 ++ webapp/e2e/steps/add-friend.steps.js | 90 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 webapp/e2e/features/add-friend.feature create mode 100644 webapp/e2e/steps/add-friend.steps.js diff --git a/webapp/e2e/features/add-friend.feature b/webapp/e2e/features/add-friend.feature new file mode 100644 index 00000000..01cfd11b --- /dev/null +++ b/webapp/e2e/features/add-friend.feature @@ -0,0 +1,6 @@ +Feature: Adding a friend + +Scenario: The user can add a friend + Given A logged-in user and another user + When I add the user as a friend + Then The user should disappear from the Users page \ No newline at end of file diff --git a/webapp/e2e/steps/add-friend.steps.js b/webapp/e2e/steps/add-friend.steps.js new file mode 100644 index 00000000..1d8418bf --- /dev/null +++ b/webapp/e2e/steps/add-friend.steps.js @@ -0,0 +1,90 @@ +const puppeteer = require("puppeteer"); +const { defineFeature, loadFeature } = require("jest-cucumber"); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const { expect } = require("expect-puppeteer"); + +const feature = loadFeature("./features/add-friend.feature"); + +let page; +let browser; + +defineFeature(feature, (test) => { + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + setDefaultOptions({ timeout: 10000 }); + + await page.goto("http://localhost:3000", { + waitUntil: "networkidle0", + }); + }); + let username; + let password; + test("The user can add a friend", ({ given, when, then }) => { + given("A logged-in user and another user", async () => { + await expect(page).toClick("a", { text: "Regístrate" }); + username="friend"; + password="Friend123"; + await page.waitForSelector('#register-username'); + await page.type('#register-username', username); + await page.waitForSelector('#register-password'); + await page.type('#register-password', password); + await page.waitForSelector('#register-pass2'); + await page.type('#register-pass2', password); + await page.click("button", { text: "Registrarse" }); + + await page.waitForTimeout(1000); + + await page.click('button[aria-label="Abrir menú"]'); + await page.waitForSelector('[data-testid="home-logout-link"]'); + await page.click('[data-testid="home-logout-link"]'); + + username = "testuser"; + password = "Testpassword1"; + await page.waitForSelector("#login-username"); + await page.type("#login-username", username); + await page.waitForSelector("#login-password"); + await page.type("#login-password", password); + await page.click("button", { text: "Login" }); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + when("I add the user as a friend", async () => { + await page.waitForTimeout(1000); + + await page.click('button[aria-label="Abrir menú"]'); + await page.click('[data-testid="home-usuarios-link"]'); + //await page.waitForNavigation({ waitUntil: "networkidle0" }); + await page.waitForTimeout(1000); + + await page.waitForSelector('[data-testid^="add-friend-button-"]'); + await page.click('[data-testid^="add-friend-button-"]'); + + }); + + then("The user should disappear from the Users page", async () => { + await page.waitForTimeout(1000); + + const userRowsBefore = await page.$$('[data-testid^="user-row-"]'); + const userCountBefore = userRowsBefore.length; + + await page.waitForSelector('[data-testid^="add-friend-button-"]'); + const addFriendButtons = await page.$$('[data-testid^="add-friend-button-"]'); + await addFriendButtons[0].click(); + + await page.waitForTimeout(1000); + + const userRowsAfter = await page.$$('[data-testid^="user-row-"]'); + const userCountAfter = userRowsAfter.length; + + expect(userCountAfter).toBe(userCountBefore - 1); + + }); + }); + + afterAll(async () => { + await browser.close(); + }); +}); \ No newline at end of file