From 2cf9344e3b03e7c046c88afc056e2b8df45589b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:32:31 +0200 Subject: [PATCH 01/47] fix: bug solved in test case --- .../login_features/negative_blank_email_login.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/login_features/negative_blank_email_login.feature b/webapp/e2e/features/login_features/negative_blank_email_login.feature index 83795c21..2239ea5f 100644 --- a/webapp/e2e/features/login_features/negative_blank_email_login.feature +++ b/webapp/e2e/features/login_features/negative_blank_email_login.feature @@ -1,9 +1,9 @@ Feature: Preventing wrong login accesses -Scenario: A registered user wants to log in using his credentials but leaving the password in blank +Scenario: A registered user wants to log in using his credentials but leaving the email in blank Given A registered user in the root screen When User presses the log in button And User enters in the log in screen - And User fills the form with his proper email but leaves the password in blank + And User fills the form with his proper password but leaves the email in blank And User presses the log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From 306524f98718afeaede1b3dd93b9afff45daba80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:32:46 +0200 Subject: [PATCH 02/47] fix: bug solved in test case --- .../login_features/negative_blank_password_login.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/login_features/negative_blank_password_login.feature b/webapp/e2e/features/login_features/negative_blank_password_login.feature index 2239ea5f..83795c21 100644 --- a/webapp/e2e/features/login_features/negative_blank_password_login.feature +++ b/webapp/e2e/features/login_features/negative_blank_password_login.feature @@ -1,9 +1,9 @@ Feature: Preventing wrong login accesses -Scenario: A registered user wants to log in using his credentials but leaving the email in blank +Scenario: A registered user wants to log in using his credentials but leaving the password in blank Given A registered user in the root screen When User presses the log in button And User enters in the log in screen - And User fills the form with his proper password but leaves the email in blank + And User fills the form with his proper email but leaves the password in blank And User presses the log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From d70c9034cd5003087ece3bb0c61546c41379e252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:33:35 +0200 Subject: [PATCH 03/47] fix: removed impossible condition to test (full leaderboard) --- .../negative_non_logged_user_seeing_stats_stories.feature | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/e2e/features/seeing_stats_features/negative_non_logged_user_seeing_stats_stories.feature b/webapp/e2e/features/seeing_stats_features/negative_non_logged_user_seeing_stats_stories.feature index b464cd64..e92203a0 100644 --- a/webapp/e2e/features/seeing_stats_features/negative_non_logged_user_seeing_stats_stories.feature +++ b/webapp/e2e/features/seeing_stats_features/negative_non_logged_user_seeing_stats_stories.feature @@ -1,7 +1,6 @@ Feature: Seeing the leader board Scenario: A non-logged user wants to see its stats - Given A non-logged user - And A full leader board (many other users with many other games) + Given A non-logged user in main menu When The user accesses to the leader board via URL Then The user is redirected to the log in screen \ No newline at end of file From b90164f4239399ee8faf6dca6887133812205673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:35:14 +0200 Subject: [PATCH 04/47] fix: refactoring for better case test implementation --- .../negative_register_blank_password.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/register_form_features/negative_register_blank_password.feature b/webapp/e2e/features/register_form_features/negative_register_blank_password.feature index 443a4818..b30c5776 100644 --- a/webapp/e2e/features/register_form_features/negative_register_blank_password.feature +++ b/webapp/e2e/features/register_form_features/negative_register_blank_password.feature @@ -1,6 +1,7 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site and tries to create an account + Scenario: The user is not registered in the root directory of the website and tries to create an account Given An unregistered user - When I fill the data in the form leaving the password in blank and press submit + When The user fills its data in the form leaving the password field in blank + And Press Log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From f32ecb89b0f55ca999f724a953ac9d61f4db858f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:35:22 +0200 Subject: [PATCH 05/47] fix: refactoring for better case test implementation --- .../negative_register_blank_repeated_password.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/register_form_features/negative_register_blank_repeated_password.feature b/webapp/e2e/features/register_form_features/negative_register_blank_repeated_password.feature index 951abeb6..b30c5776 100644 --- a/webapp/e2e/features/register_form_features/negative_register_blank_repeated_password.feature +++ b/webapp/e2e/features/register_form_features/negative_register_blank_repeated_password.feature @@ -1,6 +1,7 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site and tries to create an account + Scenario: The user is not registered in the root directory of the website and tries to create an account Given An unregistered user - When I fill the data in the form leaving the repeat password field in blank and press submit + When The user fills its data in the form leaving the password field in blank + And Press Log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From d314f17d979181116276c0df4d47d45ecfbb693e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:35:37 +0200 Subject: [PATCH 06/47] fix: refactoring for better case test implementation --- .../negative_register_blank_username.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/register_form_features/negative_register_blank_username.feature b/webapp/e2e/features/register_form_features/negative_register_blank_username.feature index f77368c3..3ad7e59f 100644 --- a/webapp/e2e/features/register_form_features/negative_register_blank_username.feature +++ b/webapp/e2e/features/register_form_features/negative_register_blank_username.feature @@ -1,6 +1,7 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site and tries to create an account + Scenario: The user is not registered in the root directory of the website and tries to create an account Given An unregistered user - When I fill the data in the form leaving the username field in blank and press submit + When The user fills its data in the form leaving the username field in blank + And Press log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From f3b381f2064c2f6c45d2c41b6586ff70c9a896bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:35:47 +0200 Subject: [PATCH 07/47] fix: refactoring for better case test implementation --- .../negative_register_email_already_in_use.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/register_form_features/negative_register_email_already_in_use.feature b/webapp/e2e/features/register_form_features/negative_register_email_already_in_use.feature index f1b19991..ebb84c7c 100644 --- a/webapp/e2e/features/register_form_features/negative_register_email_already_in_use.feature +++ b/webapp/e2e/features/register_form_features/negative_register_email_already_in_use.feature @@ -1,6 +1,7 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site and tries to create an account + Scenario: The user is not registered in the root directory of the website Given An unregistered user - When I fill the data in the form using an already used email and press submit + When the user fills the data in the form using an already used email + And click the sign in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From a0d6dda79baf5ad2a6937c314b4f817d9eb96574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:35:53 +0200 Subject: [PATCH 08/47] fix: refactoring for better case test implementation --- .../negative_register_username_already_in_use.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/register_form_features/negative_register_username_already_in_use.feature b/webapp/e2e/features/register_form_features/negative_register_username_already_in_use.feature index 776727c4..c7f0e875 100644 --- a/webapp/e2e/features/register_form_features/negative_register_username_already_in_use.feature +++ b/webapp/e2e/features/register_form_features/negative_register_username_already_in_use.feature @@ -1,6 +1,7 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site and tries to create an account + Scenario: The user is not registered in the root directory of the website and tries to create an account Given An unregistered user - When I fill the data in the form using an already used username and press submit + When The user fills the data in the form using an already used username + And Press Log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From 7f0db7a821dd1d25fd5090801a3d51c01ffa15a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:36:07 +0200 Subject: [PATCH 09/47] fix: refactoring for better case test implementation --- .../negative_register_wrong_email_format.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/register_form_features/negative_register_wrong_email_format.feature b/webapp/e2e/features/register_form_features/negative_register_wrong_email_format.feature index 6b80e175..264f6ef1 100644 --- a/webapp/e2e/features/register_form_features/negative_register_wrong_email_format.feature +++ b/webapp/e2e/features/register_form_features/negative_register_wrong_email_format.feature @@ -1,6 +1,7 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site and tries to create an account + Scenario: The user is not registered in the root directory of the website and tries to create an account Given An unregistered user - When I fill the data in the form with a wrong email format and press submit + When The user fills its data in the form placing a wrong email format + And Press Log in button Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From 84859191cf57e4654b65860a6c459a5d7b0d7cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:37:14 +0200 Subject: [PATCH 10/47] fix: added more testing steps for a better functionality checking --- .../logout_features/positive_logged_user_logout.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/logout_features/positive_logged_user_logout.feature b/webapp/e2e/features/logout_features/positive_logged_user_logout.feature index 61ed0986..2e2bc2bc 100644 --- a/webapp/e2e/features/logout_features/positive_logged_user_logout.feature +++ b/webapp/e2e/features/logout_features/positive_logged_user_logout.feature @@ -2,5 +2,6 @@ Feature: Logging out an account Scenario: A logged user wants to log out the webpage Given A logged user in main menu - When User presses the log out button - Then The login screen shows on the user device + When User presses the button for deploying the lateral menu + And User presses the log out button + Then The login screen shows on the user device and the user is no longer logged in From 17379b29737759f5ea3b246dc85a8b22ad70e015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:39:39 +0200 Subject: [PATCH 11/47] fix: refactoring for better test case implementation --- .../register_form_features/positive_register_form.feature | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/webapp/e2e/features/register_form_features/positive_register_form.feature b/webapp/e2e/features/register_form_features/positive_register_form.feature index 6917adf4..f79f064a 100644 --- a/webapp/e2e/features/register_form_features/positive_register_form.feature +++ b/webapp/e2e/features/register_form_features/positive_register_form.feature @@ -1,6 +1,7 @@ Feature: Registering a new user -Scenario: The user is not registered in the site +Scenario: The user is not registered in the root directory of the website Given An unregistered user - When I fill the data in the form and press submit - Then The main menu screen is shown and the new user is created \ No newline at end of file + When I fill the data in the form + And click the sign in button + Then The main menu screen is shown \ No newline at end of file From 774ddb7c65bf9ba4761bec0e66253116b946a91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:40:38 +0200 Subject: [PATCH 12/47] fix: removed test condition which is not possible to test (full leaderboard) --- .../seeing_stats_features/positive_seeing-stats.feature | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webapp/e2e/features/seeing_stats_features/positive_seeing-stats.feature b/webapp/e2e/features/seeing_stats_features/positive_seeing-stats.feature index 464b6bd9..d7b06353 100644 --- a/webapp/e2e/features/seeing_stats_features/positive_seeing-stats.feature +++ b/webapp/e2e/features/seeing_stats_features/positive_seeing-stats.feature @@ -2,7 +2,6 @@ Feature: Seeing the leader board Scenario: A logged user with many games wants to see its stats Given A logged user in the main menu with many games - And A full leader board (many other users with many other games) When The user presses the button for deploying the lateral menu - And the user presses the button for seeing stats - Then it successfully displays both, the leader board and the logged user statistics \ No newline at end of file + And The user presses the button for seeing stats + Then It successfully displays both, the leader board and the logged user statistics \ No newline at end of file From 2634bd11d7ec51cc409ec2efb52909deda574f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:41:37 +0200 Subject: [PATCH 13/47] fix: typos solved / descriptions with some more detail given --- .../positive_non_logged_user_seeing_about_screen.feature | 4 ++-- .../negative_non_logged_user_playing_game.feature | 4 ++-- .../negative_register_blank_email.feature | 2 +- .../negative_non_logged_user_seeing_rules.feature | 4 ++-- .../seeing_rules_features/positive_seeing_rules.feature | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/webapp/e2e/features/about_features/positive_non_logged_user_seeing_about_screen.feature b/webapp/e2e/features/about_features/positive_non_logged_user_seeing_about_screen.feature index 4d049f39..7185b089 100644 --- a/webapp/e2e/features/about_features/positive_non_logged_user_seeing_about_screen.feature +++ b/webapp/e2e/features/about_features/positive_non_logged_user_seeing_about_screen.feature @@ -3,5 +3,5 @@ Feature: Seeing the about screen of the webpage Scenario: A non-logged user wants to see the about screen of the webpage Given A non-logged user in the main menu When The user presses the button for deploying the lateral menu - And the user presses the button for seeing the about secction (i) - Then The screen shows redirects the user to the about screen \ No newline at end of file + And the user presses the button for seeing the about section (i) + Then The user is presented to the about screen \ No newline at end of file diff --git a/webapp/e2e/features/playing_game_features/negative_non_logged_user_playing_game.feature b/webapp/e2e/features/playing_game_features/negative_non_logged_user_playing_game.feature index 4e4d90e8..373a1d52 100644 --- a/webapp/e2e/features/playing_game_features/negative_non_logged_user_playing_game.feature +++ b/webapp/e2e/features/playing_game_features/negative_non_logged_user_playing_game.feature @@ -1,7 +1,7 @@ Feature: Preventing starting a new game - Scenario: A logged user wants to play a new game - Given A non-logged user in the main menu + Scenario: A non-logged user wants to play a new game + Given A non-logged user in the root directory When Entering the endpoint via URL Then No new game is created and the user is redirected to the log in screen diff --git a/webapp/e2e/features/register_form_features/negative_register_blank_email.feature b/webapp/e2e/features/register_form_features/negative_register_blank_email.feature index ceaf28dc..93fb2db3 100644 --- a/webapp/e2e/features/register_form_features/negative_register_blank_email.feature +++ b/webapp/e2e/features/register_form_features/negative_register_blank_email.feature @@ -1,6 +1,6 @@ Feature: Preventing wrong user creation - Scenario: The user is not registered in the site + Scenario: The user is not registered in the site and tries to create an account Given An unregistered user When I fill the data in the form leaving the email in blank and press submit Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file diff --git a/webapp/e2e/features/seeing_rules_features/negative_non_logged_user_seeing_rules.feature b/webapp/e2e/features/seeing_rules_features/negative_non_logged_user_seeing_rules.feature index 8de07602..7f8c65d3 100644 --- a/webapp/e2e/features/seeing_rules_features/negative_non_logged_user_seeing_rules.feature +++ b/webapp/e2e/features/seeing_rules_features/negative_non_logged_user_seeing_rules.feature @@ -1,6 +1,6 @@ Feature: Preventing seeing the rules of the game Scenario: A non-logged user wants to see the rules for the game - Given A non-logged user - When The user accesses to the rules via URL + Given A non-logged user in main menu + When User accesses de /rules endpoint via URL Then The user is redirected to the log in screen \ No newline at end of file diff --git a/webapp/e2e/features/seeing_rules_features/positive_seeing_rules.feature b/webapp/e2e/features/seeing_rules_features/positive_seeing_rules.feature index 4c7142eb..0fb53312 100644 --- a/webapp/e2e/features/seeing_rules_features/positive_seeing_rules.feature +++ b/webapp/e2e/features/seeing_rules_features/positive_seeing_rules.feature @@ -3,5 +3,5 @@ Feature: Seeing the rules of the game Scenario: A logged user wants to see the rules for the game Given A logged user in the main menu When The user presses the button for deploying the lateral menu - And the user presses the button for seeing the rules + And The user presses the button for seeing the rules Then The screen shows redirects the user to the rules' screen \ No newline at end of file From a637d9f2300936fba27c0ae85ff9fd3f3aa5f30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:48:52 +0200 Subject: [PATCH 14/47] fix: file renaming as they did not followed naming convention --- ...gative_incorrect_credentials_login.feature | 9 ++++ .../positive_playing_full_game.feature | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 webapp/e2e/features/login_features/negative_incorrect_credentials_login.feature create mode 100644 webapp/e2e/features/playing_game_features/positive_playing_full_game.feature diff --git a/webapp/e2e/features/login_features/negative_incorrect_credentials_login.feature b/webapp/e2e/features/login_features/negative_incorrect_credentials_login.feature new file mode 100644 index 00000000..3e579c5a --- /dev/null +++ b/webapp/e2e/features/login_features/negative_incorrect_credentials_login.feature @@ -0,0 +1,9 @@ +Feature: Preventing wrong login accesses + + Scenario: A registered user wants to log in using his credentials but they do not match any registered user + Given A registered user in the root screen + When User presses the log in button + And User enters in the log in screen + And User fills the form with credentials that do not match + And User presses the log in button + Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file diff --git a/webapp/e2e/features/playing_game_features/positive_playing_full_game.feature b/webapp/e2e/features/playing_game_features/positive_playing_full_game.feature new file mode 100644 index 00000000..03129b89 --- /dev/null +++ b/webapp/e2e/features/playing_game_features/positive_playing_full_game.feature @@ -0,0 +1,54 @@ +Feature: Starting an entire game + +Scenario: A non-logged user wants to play an entire game (Kiwi Quest gamemode) + Given A non-logged user in the main menu + When Clicking the button to start a new game (Kiwi Quest gamemode) + + And Waiting for the question to load + And Checking user is on round 1/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 2/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 3/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 4/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 5/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 6/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 7/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 8/9 + And Clicking on a random answer + And Sending the answer + + And Waiting for the question to load + And Checking user is on round 9/9 + And Clicking on a random answer + And Sending the answer + + + Then The user statistics are shown + From 3c91afd3baa6ba7b2fdacf7185c9cf1b68d181d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:50:23 +0200 Subject: [PATCH 15/47] feat: added e2e test case for logged users in main menu whenever it tries to access the about screen --- ...e_logged_user_seeing_about_screen.steps.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 webapp/e2e/steps/about_positive_logged_user_seeing_about_screen.steps.js diff --git a/webapp/e2e/steps/about_positive_logged_user_seeing_about_screen.steps.js b/webapp/e2e/steps/about_positive_logged_user_seeing_about_screen.steps.js new file mode 100644 index 00000000..311d0908 --- /dev/null +++ b/webapp/e2e/steps/about_positive_logged_user_seeing_about_screen.steps.js @@ -0,0 +1,62 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/about_features/positive_logged_user_seeing_about_screen.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("A logged user wants to see the about screen of the webpage", ({given,when,and,then}) => { + + let username; + let password; + + given("A logged user in the main menu", async () => { + username = "test@email.com" + password = "password" + + await expect(page).toClick("button[data-testid='Login'"); + await expect(page).toFill("#user", username); + await expect(page).toFill("#password", password); + await expect(page).toClick("button[data-testid='Login'"); + }); + + when("The user presses the button for deploying the lateral menu", async () => { + await expect(page).toClick("#lateralMenuButton"); + }); + + and("the user presses the button for seeing the about section (i)", async () => { + await expect(page).toClick("#aboutButton"); + }); + + then("The user is presented to the about screen", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "About" || header === "Sobre nosotros"; + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From c9fa50c145c8a5f8327e0377e6a71bf56e8ffc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:50:40 +0200 Subject: [PATCH 16/47] feat: added e2e test case for non-logged users in main menu whenever it tries to access the about screen --- ...n_logged_user_seeing_about_screen.steps.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 webapp/e2e/steps/about_positive_non_logged_user_seeing_about_screen.steps.js diff --git a/webapp/e2e/steps/about_positive_non_logged_user_seeing_about_screen.steps.js b/webapp/e2e/steps/about_positive_non_logged_user_seeing_about_screen.steps.js new file mode 100644 index 00000000..9072d93f --- /dev/null +++ b/webapp/e2e/steps/about_positive_non_logged_user_seeing_about_screen.steps.js @@ -0,0 +1,53 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/about_features/positive_non_logged_user_seeing_about_screen.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("A non-logged user wants to see the about screen of the webpage", ({given,when,and,then}) => { + + given("A non-logged user in the main menu", async () => { + + }); + + when("The user presses the button for deploying the lateral menu", async () => { + await expect(page).toClick("#lateralMenuButton"); + }); + + and("the user presses the button for seeing the about section (i)", async () => { + await expect(page).toClick("#aboutButton"); + }); + + then("The user is presented to the about screen", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "About" || header === "Sobre nosotros"; + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From a415ea8f320a915877b8931315f834313ffbbbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:51:06 +0200 Subject: [PATCH 17/47] feat: added e2e test case for non-logged users in root directory tying to log in properly --- webapp/e2e/steps/login_positive.steps.js | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 webapp/e2e/steps/login_positive.steps.js diff --git a/webapp/e2e/steps/login_positive.steps.js b/webapp/e2e/steps/login_positive.steps.js new file mode 100644 index 00000000..7a9a4678 --- /dev/null +++ b/webapp/e2e/steps/login_positive.steps.js @@ -0,0 +1,78 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/login_features/positive_login.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("A registered user wants to log in using his correct credentials", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + given('A registered user in the root screen', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + and('User enters in the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + and('User fills the form with his credentials properly', async() => { + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + }); + + and('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + then('The main menu screen shows on the user device', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Bienvenid@ " + username || header === "Welcome " + username; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 851d99577bc48da10eddf8c961bf96b6b98c457e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:52:32 +0200 Subject: [PATCH 18/47] feat: added e2e test case for non-logged users in root directory tying to log in with a wrong email --- ...egister_negative_bad_email_format.steps.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 webapp/e2e/steps/login_register_negative_bad_email_format.steps.js diff --git a/webapp/e2e/steps/login_register_negative_bad_email_format.steps.js b/webapp/e2e/steps/login_register_negative_bad_email_format.steps.js new file mode 100644 index 00000000..7df25a3b --- /dev/null +++ b/webapp/e2e/steps/login_register_negative_bad_email_format.steps.js @@ -0,0 +1,79 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/login_features/negative_bad_format_email_login.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("A registered user wants to log in using his credentials but with an invalid email", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "email.com" // Bad formatted email + let password = "Password" + + given('A registered user in the root screen', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + and('User enters in the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + and('User fills the form with his proper password but writes a wrong formatted email', async() => { + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + }); + + and('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + then('Log in screen shows an informative error message and does not allow the user to log in', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "El formato del correo electrónico no es correcto" + || header === "Invalid email format"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From e633f46feb6076b9d36fe69b353117b8fee1eb63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:52:44 +0200 Subject: [PATCH 19/47] feat: added e2e test case for non-logged users in root directory tying to log in with a blank email --- ..._negative_blank_email_credentials.steps.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 webapp/e2e/steps/login_register_negative_blank_email_credentials.steps.js diff --git a/webapp/e2e/steps/login_register_negative_blank_email_credentials.steps.js b/webapp/e2e/steps/login_register_negative_blank_email_credentials.steps.js new file mode 100644 index 00000000..077730cf --- /dev/null +++ b/webapp/e2e/steps/login_register_negative_blank_email_credentials.steps.js @@ -0,0 +1,79 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/login_features/negative_blank_email_login.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("A registered user wants to log in using his credentials but leaving the email in blank", ({given,when,and,then}) => { + let username = "pepe" + let user = "" + let password = "Password" + + given('A registered user in the root screen', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + and('User enters in the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + and('User fills the form with his proper password but leaves the email in blank', async() => { + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + }); + + and('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + then('Log in screen shows an informative error message and does not allow the user to log in', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "El formato del correo electrónico no es correcto" + || header === "Invalid email format"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 12086b7f994911a25f83e3a3476763f7ca8f2604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:53:00 +0200 Subject: [PATCH 20/47] feat: added e2e test case for non-logged users in root directory tying to log in with a blank password --- ...gative_blank_password_credentials.steps.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 webapp/e2e/steps/login_register_negative_blank_password_credentials.steps.js diff --git a/webapp/e2e/steps/login_register_negative_blank_password_credentials.steps.js b/webapp/e2e/steps/login_register_negative_blank_password_credentials.steps.js new file mode 100644 index 00000000..ea59247b --- /dev/null +++ b/webapp/e2e/steps/login_register_negative_blank_password_credentials.steps.js @@ -0,0 +1,79 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/login_features/negative_blank_password_login.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("A registered user wants to log in using his credentials but leaving the password in blank", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "@email.com" + let password = "" // Blank password + + given('A registered user in the root screen', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + and('User enters in the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + and('User fills the form with his proper email but leaves the password in blank', async() => { + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + }); + + and('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + then('Log in screen shows an informative error message and does not allow the user to log in', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "El formato del correo electrónico no es correcto" // Wrong message shown from frontend + || header === "Invalid email format"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 739a5644bbfe9f8a4260e4ddf5a3267b3ca81651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:53:46 +0200 Subject: [PATCH 21/47] feat: added e2e test case for non-logged users in root directory tying to log in with an email and password that do not match --- ...er_negative_incorrect_credentials.steps.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 webapp/e2e/steps/login_register_negative_incorrect_credentials.steps.js diff --git a/webapp/e2e/steps/login_register_negative_incorrect_credentials.steps.js b/webapp/e2e/steps/login_register_negative_incorrect_credentials.steps.js new file mode 100644 index 00000000..3f1cddfc --- /dev/null +++ b/webapp/e2e/steps/login_register_negative_incorrect_credentials.steps.js @@ -0,0 +1,79 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/login_features/negative_incorrect_credentials_login.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("A registered user wants to log in using his credentials but they do not match any registered user", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "notPepesPassword" + + given('A registered user in the root screen', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + and('User enters in the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + and('User fills the form with credentials that do not match', async() => { + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + }); + + and('User presses the log in button', async() => { + await expect(page).toClick("button[data-testid='Login'"); + }); + + then('Log in screen shows an informative error message and does not allow the user to log in', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "Correo electrónico o contraseña no válidos, verifique que sean correctos" + || header === "Invalid email or password, check for them to be correct"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From a31c71d6d30304ecefa22a141435c9d4d6f807dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:54:13 +0200 Subject: [PATCH 22/47] feat: added e2e test case for logged users in main page trying to log out --- .../logout_positive_logged_user.steps.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 webapp/e2e/steps/logout_positive_logged_user.steps.js diff --git a/webapp/e2e/steps/logout_positive_logged_user.steps.js b/webapp/e2e/steps/logout_positive_logged_user.steps.js new file mode 100644 index 00000000..f953de75 --- /dev/null +++ b/webapp/e2e/steps/logout_positive_logged_user.steps.js @@ -0,0 +1,79 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/logout_features/positive_logged_user_logout.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("A logged user wants to log out the webpage", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A logged user in main menu', async () => { + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + + await expect(page).toClick("button[data-testid='Login'"); + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + await expect(page).toClick("button[data-testid='Login'"); + + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let newHeader = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let newValue = newHeader === "Bienvenid@ " + username || header === "Welcome " + username; + expect(newValue).toBeTruthy(); + }); + + when('User presses the button for deploying the lateral menu', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + await expect(page).toClick("#lateralMenuButton"); + + }); + + and('User presses the log out button', async() => { + await expect(page).toClick("button[data-testid='LogOut']"); + + }); + + then('The login screen shows on the user device and the user is no longer logged in', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 6e0da95e92b37f165dc19b142c3d7cbc24e3abf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:54:29 +0200 Subject: [PATCH 23/47] feat: added e2e test case for non-logged users in main page trying to log out --- .../logout_positive_non_logged_user.steps.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 webapp/e2e/steps/logout_positive_non_logged_user.steps.js diff --git a/webapp/e2e/steps/logout_positive_non_logged_user.steps.js b/webapp/e2e/steps/logout_positive_non_logged_user.steps.js new file mode 100644 index 00000000..508d3c73 --- /dev/null +++ b/webapp/e2e/steps/logout_positive_non_logged_user.steps.js @@ -0,0 +1,65 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/logout_features/positive_non_logged_user_logout.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("A non-logged user wants to log out the webpage", ({given,when,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A non-logged user in main menu', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User accesses de /logout endpoint via URL', async() => { + await page + .goto("http://localhost:3000/logout", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + then('The login screen shows on the user device', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From cd8cc6f066d7a5a8242c557c6b4d264b6c6613fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 16:55:08 +0200 Subject: [PATCH 24/47] feat: added e2e test case for logged users fully playing a whole game properly --- .../steps/playing_full_game_positive.steps.js | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 webapp/e2e/steps/playing_full_game_positive.steps.js diff --git a/webapp/e2e/steps/playing_full_game_positive.steps.js b/webapp/e2e/steps/playing_full_game_positive.steps.js new file mode 100644 index 00000000..98567029 --- /dev/null +++ b/webapp/e2e/steps/playing_full_game_positive.steps.js @@ -0,0 +1,347 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/playing_game_features/positive_playing_full_game.feature'); +let page; +let browser; +let NUMBER_OF_ANSWERS = 4; +let TEST_TIMEOUT = 300 * 1000; // 5minutes +const crypto = require('crypto'); + + +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("A non-logged user wants to play an entire game (Kiwi Quest gamemode)", ({given,when,and,then}) => { + // Trying to avoid repeated users: + // Generate random bytes + let randomBytes = crypto.randomBytes(8); // 8 bytes = 64 bits + // Convert bytes to hex + let hexString = randomBytes.toString('hex'); + // Take the first 16 characters + let randomHash = hexString.substring(0, 8); + + + + let username = "test" + randomHash + let user = username + "@pepe.com" + let password = "pepe" + randomHash + + given('A non-logged user in the main menu', async () => { + // Registering a new user for simplicity of testing purposes + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", password); + await expect(page).toClick("button[data-testid='Sign up'"); + + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + // Checking user is in main screen + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Bienvenid@ " + username || header === "Welcome " + username; + + expect(value).toBeTruthy(); + + }); + + when('Clicking the button to start a new game (Kiwi Quest gamemode)', async() => { + await expect(page).toClick("button[class='chakra-button css-4ctvp9'"); + await expect(page).toClick("button[data-testid='Play']"); + }); + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + + }); + + and('Checking user is on round 1/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 1; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 2/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 2; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 3/9', async() => { + + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 3; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 4/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 4; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 5/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 5; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 6/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 6; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 7/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 7; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 8/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 8; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + + + + + and('Waiting for the question to load', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + + }); + + and('Checking user is on round 9/9', async() => { + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let round = 9; + let value = header === "Ronda " + round + " de 9" || header === "Round " + round + " of 9"; + expect(value).toBeTruthy(); + + }); + + and('Clicking on a random answer', async() => { + let chosen = Math.floor(Math.random() * NUMBER_OF_ANSWERS) + 1; // choses a int in between [1,4] (index of answers range) + await expect(page).toClick("button[data-testid='Option" + chosen + "']"); + + }); + + and('Sending the answer', async() => { + await expect(page).toClick("button[data-testid='Next']"); + + }); + + then('The user statistics are shown', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting till page is loaded + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Resultados" || header === "Results"; + + expect(value).toBeTruthy(); + }); + + }, TEST_TIMEOUT); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 5739bfc01949aa01e2fd106d9c3542fb5bf77c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:11:25 +0200 Subject: [PATCH 25/47] feat: added e2e test case for non-logged users tying to play a game via url --- ...ing_game_negative_non_logged_user.steps.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 webapp/e2e/steps/playing_game_negative_non_logged_user.steps.js diff --git a/webapp/e2e/steps/playing_game_negative_non_logged_user.steps.js b/webapp/e2e/steps/playing_game_negative_non_logged_user.steps.js new file mode 100644 index 00000000..a644b5a7 --- /dev/null +++ b/webapp/e2e/steps/playing_game_negative_non_logged_user.steps.js @@ -0,0 +1,62 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/playing_game_features/negative_non_logged_user_playing_game.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("A non-logged user wants to play a new game", ({given,when,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A non-logged user in the root directory', async () => { + + }); + + when('Entering the endpoint via URL', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + await page + .goto(gameURL, { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + + + then('No new game is created and the user is redirected to the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 8a83ea5df809947161d03ec3bb959696f341a7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:12:10 +0200 Subject: [PATCH 26/47] feat: added e2e test case for non-logged users registering --- .../e2e/steps/register_positive_form.steps.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 webapp/e2e/steps/register_positive_form.steps.js diff --git a/webapp/e2e/steps/register_positive_form.steps.js b/webapp/e2e/steps/register_positive_form.steps.js new file mode 100644 index 00000000..ccb366e9 --- /dev/null +++ b/webapp/e2e/steps/register_positive_form.steps.js @@ -0,0 +1,75 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/positive_register_form.feature'); +const crypto = require('crypto'); +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 not registered in the root directory of the website", ({given,when,and,then}) => { + // Trying to avoid repeated users: + // Generate random bytes + let randomBytes = crypto.randomBytes(8); // 8 bytes = 64 bits + // Convert bytes to hex + let hexString = randomBytes.toString('hex'); + // Take the first 16 characters + let randomHash = hexString.substring(0, 20); + + + + let username = "test" + randomHash + let user = username + "@email.com" + let password = "password" + + + given("An unregistered user", async () => { + + }); + + when("I fill the data in the form", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", password); + + }); + + and("click the sign in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("The main menu screen is shown", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "Bienvenid@ " + username || header === "Welcome " + username; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 46153b0704d0af35102ab5799919915e386468ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:14:02 +0200 Subject: [PATCH 27/47] feat: added e2e test case for non-logged users tying to register with a wrongly formatted email --- ...ister_negative_wrong_email_format.steps.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 webapp/e2e/steps/register_negative_wrong_email_format.steps.js diff --git a/webapp/e2e/steps/register_negative_wrong_email_format.steps.js b/webapp/e2e/steps/register_negative_wrong_email_format.steps.js new file mode 100644 index 00000000..18ad8805 --- /dev/null +++ b/webapp/e2e/steps/register_negative_wrong_email_format.steps.js @@ -0,0 +1,75 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/negative_register_wrong_email_format.feature'); +const crypto = require('crypto'); +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 not registered in the root directory of the website and tries to create an account", ({given,when,and,then}) => { + // Trying to avoid repeated users: + // Generate random bytes + let randomBytes = crypto.randomBytes(8); // 8 bytes = 64 bits + // Convert bytes to hex + let hexString = randomBytes.toString('hex'); + // Take the first 16 characters + let randomHash = hexString.substring(0, 20); + + + + let username = "test" + randomHash + let user = username + "email.com" // Bad format email + let password = "password" + + given("An unregistered user", async () => { + + }); + + when("The user fills its data in the form placing a wrong email format", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", password); + + }); + + and("Press Log in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("Log in screen shows an informative error message and does not allow the user to log in", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "Formato de correo electrónico no válido o credenciales (nombre de usuario o correo electrónico) ya en uso" + || header === "Invalid email format or credentials (username or email) already in use"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 356f03548bad5d1e6e6999fd8c925d913f95213e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:14:36 +0200 Subject: [PATCH 28/47] feat: added e2e test case for non-logged users tying to register with a username already used --- ...tive_username_already_in_use_form.steps.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 webapp/e2e/steps/register_negative_username_already_in_use_form.steps.js diff --git a/webapp/e2e/steps/register_negative_username_already_in_use_form.steps.js b/webapp/e2e/steps/register_negative_username_already_in_use_form.steps.js new file mode 100644 index 00000000..68e4971d --- /dev/null +++ b/webapp/e2e/steps/register_negative_username_already_in_use_form.steps.js @@ -0,0 +1,65 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/negative_register_username_already_in_use.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 not registered in the root directory of the website and tries to create an account", ({given,when,and,then}) => { + let username = "test" + let user = username + "@emailNonRepeated.com" + let password = "password" + + given("An unregistered user", async () => { + + }); + + when("The user fills the data in the form using an already used username", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", password); + + }); + + and("Press Log in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("Log in screen shows an informative error message and does not allow the user to log in", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "Formato de correo electrónico no válido o credenciales (nombre de usuario o correo electrónico) ya en uso" + || header === "Invalid email format or credentials (username or email) already in use"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From d6da0b1b31380b2b5820a936325779c1587b144a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:14:50 +0200 Subject: [PATCH 29/47] feat: added e2e test case for non-logged users tying to register with an email already used --- ...egative_email_already_in_use_form.steps.js | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 webapp/e2e/steps/register_negative_email_already_in_use_form.steps.js diff --git a/webapp/e2e/steps/register_negative_email_already_in_use_form.steps.js b/webapp/e2e/steps/register_negative_email_already_in_use_form.steps.js new file mode 100644 index 00000000..8cd00a7b --- /dev/null +++ b/webapp/e2e/steps/register_negative_email_already_in_use_form.steps.js @@ -0,0 +1,72 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/negative_register_email_already_in_use.feature'); +const crypto = require('crypto'); +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 not registered in the root directory of the website", ({given,when,and,then}) => { + // Trying to avoid repeated users: + // Generate random bytes + let randomBytes = crypto.randomBytes(8); // 8 bytes = 64 bits + // Convert bytes to hex + let hexString = randomBytes.toString('hex'); + // Take the first 16 characters + let randomHash = hexString.substring(0, 20); + let username = "test" + let user = "test" + "@email.com" + let password = "password" + + given("An unregistered user", async () => { + + }); + + when("the user fills the data in the form using an already used email", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", password); + + }); + + and("click the sign in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("Log in screen shows an informative error message and does not allow the user to log in", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "Formato de correo electrónico no válido o credenciales (nombre de usuario o correo electrónico) ya en uso" + || header === "Invalid email format or credentials (username or email) already in use"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From a8f9c1b3387e4bbab8089e4a957a1fd33513bca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:15:14 +0200 Subject: [PATCH 30/47] feat: added e2e test case for non-logged users tying to register with a blank password --- .../register_negative_blank_password.steps.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 webapp/e2e/steps/register_negative_blank_password.steps.js diff --git a/webapp/e2e/steps/register_negative_blank_password.steps.js b/webapp/e2e/steps/register_negative_blank_password.steps.js new file mode 100644 index 00000000..5977074a --- /dev/null +++ b/webapp/e2e/steps/register_negative_blank_password.steps.js @@ -0,0 +1,75 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/negative_register_blank_password.feature'); +const crypto = require('crypto'); +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 not registered in the root directory of the website and tries to create an account", ({given,when,and,then}) => { + // Trying to avoid repeated users: + // Generate random bytes + let randomBytes = crypto.randomBytes(8); // 8 bytes = 64 bits + // Convert bytes to hex + let hexString = randomBytes.toString('hex'); + // Take the first 16 characters + let randomHash = hexString.substring(0, 20); + + + + let username = "test" + randomHash + let user = username + "@email.com" + let password = "password" + + given("An unregistered user", async () => { + + }); + + when("The user fills its data in the form leaving the password field in blank", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", ""); // Blank password + await expect(page).toFill("input[id='field-:r5:']", password); + + }); + + and("Press Log in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("Log in screen shows an informative error message and does not allow the user to log in", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "Formato de correo electrónico no válido o credenciales (nombre de usuario o correo electrónico) ya en uso" + || header === "Invalid email format or credentials (username or email) already in use"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From fd70f9c80cf801d1a0d19ff68f8d4ab3784d1ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:15:24 +0200 Subject: [PATCH 31/47] feat: added e2e test case for non-logged users tying to register with a blank repeated password --- ..._negative_blank_repeated_password.steps.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 webapp/e2e/steps/register_negative_blank_repeated_password.steps.js diff --git a/webapp/e2e/steps/register_negative_blank_repeated_password.steps.js b/webapp/e2e/steps/register_negative_blank_repeated_password.steps.js new file mode 100644 index 00000000..96349b77 --- /dev/null +++ b/webapp/e2e/steps/register_negative_blank_repeated_password.steps.js @@ -0,0 +1,75 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/negative_register_blank_repeated_password.feature'); +const crypto = require('crypto'); +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 not registered in the root directory of the website and tries to create an account", ({given,when,and,then}) => { + // Trying to avoid repeated users: + // Generate random bytes + let randomBytes = crypto.randomBytes(8); // 8 bytes = 64 bits + // Convert bytes to hex + let hexString = randomBytes.toString('hex'); + // Take the first 16 characters + let randomHash = hexString.substring(0, 20); + + + + let username = "test" + randomHash + let user = username + "@email.com" + let password = "password" + + given("An unregistered user", async () => { + + }); + + when("The user fills its data in the form leaving the password field in blank", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", ""); // Blank password + + }); + + and("Press Log in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("Log in screen shows an informative error message and does not allow the user to log in", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "La confirmación de contraseña no puede estar vacía" + || header === "The confirm password label can not be empty"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From c20ee32952710197616942ae954b411d1e30cd7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:15:39 +0200 Subject: [PATCH 32/47] feat: added e2e test case for non-logged users tying to register with a blank username --- .../register_negative_blank_username.steps.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 webapp/e2e/steps/register_negative_blank_username.steps.js diff --git a/webapp/e2e/steps/register_negative_blank_username.steps.js b/webapp/e2e/steps/register_negative_blank_username.steps.js new file mode 100644 index 00000000..fa8d7e29 --- /dev/null +++ b/webapp/e2e/steps/register_negative_blank_username.steps.js @@ -0,0 +1,65 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/register_form_features/negative_register_blank_username.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 not registered in the root directory of the website and tries to create an account", ({given,when,and,then}) => { + let username = "" // Blank username + let user = "test@email.com" + let password = "password" + + given("An unregistered user", async () => { + + }); + + when("The user fills its data in the form leaving the username field in blank", async () => { + await expect(page).toClick("span[class='chakra-link css-1bicqx'"); + await expect(page).toFill("input[id='user'", user); + await expect(page).toFill("input[id='username'", username); + await expect(page).toFill("#password", password); + await expect(page).toFill("input[id='field-:r5:']", password); + + }); + + and("Press Log in button", async () => { + await expect(page).toClick("button[data-testid='Sign up'"); + + }); + + then("Log in screen shows an informative error message and does not allow the user to log in", async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("div[class='chakra-alert__desc css-zzks76'", (element) => { + return element.innerHTML + }) + let value = header === "Formato de correo electrónico no válido o credenciales (nombre de usuario o correo electrónico) ya en uso" + || header === "Invalid email format or credentials (username or email) already in use"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From eab01e36866cdf838057265e99109e1e5f0501af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:16:10 +0200 Subject: [PATCH 33/47] feat: added e2e test case for non-logged users tying to see the rules of the game --- ...ng_rules_negative_non_logged_user.steps.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 webapp/e2e/steps/seeing_rules_negative_non_logged_user.steps.js diff --git a/webapp/e2e/steps/seeing_rules_negative_non_logged_user.steps.js b/webapp/e2e/steps/seeing_rules_negative_non_logged_user.steps.js new file mode 100644 index 00000000..71dcbc2c --- /dev/null +++ b/webapp/e2e/steps/seeing_rules_negative_non_logged_user.steps.js @@ -0,0 +1,64 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/seeing_rules_features/negative_non_logged_user_seeing_rules.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("A non-logged user wants to see the rules for the game", ({given,when,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A non-logged user in main menu', async () => { + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('User accesses de /rules endpoint via URL', async() => { + await page + .goto("http://localhost:3000/dashboard/rules", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + then('The user is redirected to the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From f85dc9f1d8bb7fad49311fb6b608fd584af0ad40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:16:26 +0200 Subject: [PATCH 34/47] feat: added e2e test case for logged users tying to see the rules of the game --- .../e2e/steps/seeing_rules_positive.steps.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 webapp/e2e/steps/seeing_rules_positive.steps.js diff --git a/webapp/e2e/steps/seeing_rules_positive.steps.js b/webapp/e2e/steps/seeing_rules_positive.steps.js new file mode 100644 index 00000000..05bd5159 --- /dev/null +++ b/webapp/e2e/steps/seeing_rules_positive.steps.js @@ -0,0 +1,79 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/seeing_rules_features/positive_seeing_rules.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("A logged user wants to see the rules for the game", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A logged user in the main menu', async () => { + // Entering login screen from root directory + await expect(page).toClick("button[data-testid='Login'"); + + // Filling the credentials + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + + // Clicking to send the login request + await expect(page).toClick("button[data-testid='Login'"); + + // Checking user is in main screen + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Bienvenid@ " + username || header === "Welcome " + username; + + expect(value).toBeTruthy(); + + }); + + when('The user presses the button for deploying the lateral menu', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + await expect(page).toClick("#lateralMenuButton"); + + }); + + and('The user presses the button for seeing the rules', async() => { + await expect(page).toClick("button[data-testid='rules']"); + + }); + + then("The screen shows redirects the user to the rules' screen", async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Rules" || "Reglas"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From fe0576281e18e2f4a24b9c6fcf2872be3fef19bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:16:49 +0200 Subject: [PATCH 35/47] feat: added e2e test case for non-logged users trying to see the leaderboard --- ...ng_stats_negative_non_logged_user.steps.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 webapp/e2e/steps/seeing_stats_negative_non_logged_user.steps.js diff --git a/webapp/e2e/steps/seeing_stats_negative_non_logged_user.steps.js b/webapp/e2e/steps/seeing_stats_negative_non_logged_user.steps.js new file mode 100644 index 00000000..1a97a1cc --- /dev/null +++ b/webapp/e2e/steps/seeing_stats_negative_non_logged_user.steps.js @@ -0,0 +1,65 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/seeing_stats_features/negative_non_logged_user_seeing_stats_stories.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("A non-logged user wants to see its stats", ({given,when,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A non-logged user in main menu', async () => { + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + let header = await page.$eval("button[data-testid='Login']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + + when('The user accesses to the leader board via URL', async() => { + await page + .goto("http://localhost:3000/dashboard/statistics", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + then('The user is redirected to the log in screen', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Login" || "Iniciar sesión"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From 45a6a8d4a39adf47f4bbdb0b1f183368ef544537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:17:39 +0200 Subject: [PATCH 36/47] feat: added e2e test case for logged user seeing the leaderboard --- .../e2e/steps/seeing_stats_positive.steps.js | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 webapp/e2e/steps/seeing_stats_positive.steps.js diff --git a/webapp/e2e/steps/seeing_stats_positive.steps.js b/webapp/e2e/steps/seeing_stats_positive.steps.js new file mode 100644 index 00000000..7db3e127 --- /dev/null +++ b/webapp/e2e/steps/seeing_stats_positive.steps.js @@ -0,0 +1,80 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/seeing_stats_features/positive_seeing-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("A logged user with many games wants to see its stats", ({given,when,and,then}) => { + let username = "pepe" + let user = username + "@pepe.com" + let password = "pepe" + + let gameURL = "http://localhost:3000/dashboard/game"; + + given('A logged user in the main menu with many games', async () => { + // Entering login screen from root directory + await expect(page).toClick("button[data-testid='Login'"); + + // Filling the credentials + await expect(page).toFill("#user", user); + await expect(page).toFill("#password", password); + + // Clicking to send the login request + await expect(page).toClick("button[data-testid='Login'"); + await new Promise(resolve => setTimeout(resolve, 6000)); // Waiting for page to fully load + + // Checking user is in main screen + let header = await page.$eval("h2[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Bienvenid@ " + username || header === "Welcome " + username; + + expect(value).toBeTruthy(); + + }); + + when('The user presses the button for deploying the lateral menu', async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + await expect(page).toClick("#lateralMenuButton"); + + }); + + and('The user presses the button for seeing stats', async() => { + await expect(page).toClick("button[data-testid='statistics']"); + + }); + + then("It successfully displays both, the leader board and the logged user statistics", async() => { + await new Promise(resolve => setTimeout(resolve, 6000)); + let header = await page.$eval("h1[class='chakra-heading css-79qjat']", (element) => { + return element.innerHTML + }) + let value = header === "Estadísiticas" || "Statistics"; + + expect(value).toBeTruthy(); + }); + }, 600000); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file From de2816c1e0a00286cbdb1eb3e05c31d59438fb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 17:18:16 +0200 Subject: [PATCH 37/47] fix: removed unused test cases because they are meaningless --- .../negative_incorrect_credentials.feature | 9 --------- .../playing_game_features/positive_playing_game.feature | 7 ------- 2 files changed, 16 deletions(-) delete mode 100644 webapp/e2e/features/login_features/negative_incorrect_credentials.feature delete mode 100644 webapp/e2e/features/playing_game_features/positive_playing_game.feature diff --git a/webapp/e2e/features/login_features/negative_incorrect_credentials.feature b/webapp/e2e/features/login_features/negative_incorrect_credentials.feature deleted file mode 100644 index 662052c5..00000000 --- a/webapp/e2e/features/login_features/negative_incorrect_credentials.feature +++ /dev/null @@ -1,9 +0,0 @@ -Feature: Preventing wrong login accesses - - Scenario: A registered user wants to log in using his credentials but leaving the email in blank - Given A registered user in the root screen - When User presses the log in button - And User enters in the log in screen - And User fills the form with his proper password but leaves the email in blank - And User presses the log in button - Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file diff --git a/webapp/e2e/features/playing_game_features/positive_playing_game.feature b/webapp/e2e/features/playing_game_features/positive_playing_game.feature deleted file mode 100644 index 1f4ae9ee..00000000 --- a/webapp/e2e/features/playing_game_features/positive_playing_game.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Starting a new game - -Scenario: A logged user wants to play a new game - Given A logged user in the main menu - When Clicking the button to start a new game - Then A new game is created and the first question appears on the screen - From af313d1b2e55fc2ef4d10109f5c31a741943513c Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 29 Apr 2024 17:21:47 +0200 Subject: [PATCH 38/47] feat: added architectural decisions --- docs/src/09_architecture_decisions.adoc | 117 ++++++++++++++++++++---- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/docs/src/09_architecture_decisions.adoc b/docs/src/09_architecture_decisions.adoc index 421aff80..e2faa1dd 100644 --- a/docs/src/09_architecture_decisions.adoc +++ b/docs/src/09_architecture_decisions.adoc @@ -6,27 +6,110 @@ ifndef::imagesdir[:imagesdir: ../images] During the application development process, decisions had to be made as issues emerged. The most interesting design decisions are reflected in this architectural records: -[options="header",cols="1,4"] -|=== -|Decision|Explanation -|React -|Offers a powerful combination of performance, flexibility, and developer experience, making it a popular choice for building modern web applications. One of the members of the group has already worked with it in the past. It allows us to build a good user interface for the application. +=== Main webapp technology +*Date Recorded:* 01/02/2024 -|SpringBoot -|This choice is based on the extensive experience accumulated by our team in developing with Java, as well as the familiarity and comfort offered by the tools and practices associated with the Spring Boot ecosystem. +*Scope:* frontend, webapp -|PostgreSQL -|We have chosen to use the PostgreSQL database instead of MongoDB due to the relational nature of PostgreSQL, which offers a robust and coherent structure for storing and relating data. We made this decision to ensure data integrity, perform complex queries, and maintain consistency in our storage operations. +*Description:* +Offers a powerful combination of performance, flexibility, and developer experience, making it a popular choice for building modern web applications. One of the members of the group has already worked with it in the past. It allows us to build a good user interface for the application. -|React Libraries -|To enhance the efficiency and effectiveness of our development process, we've taken proactive steps to incorporate specific libraries into our project. These carefully chosen libraries, meticulously outlined in detail within issue #16. +*Decision taken:* +The frontend team has agreed to use *React* or a React framework, as all members agree they are not up to the task of maintaining a personalised stylesheet. + +=== React technology to be used +*Date Recorded:* 01/02/2024 + +*Scope:* frontend, webapp + +*Description:* +The frontend team has been discussing whether to use React or a React framework like Next.js as it is the currently recommended option. + +*Decision taken:* +The frontend team has agreed to use a *pure React-based approach* due to most members not having any kind of prior experience with React and those that do have it are do not feel confident enough in their skills. + +=== Main backend technology + +*Date Recorded:* 01/02/2024 + +*Scope:* backend, API + +*Description:* +The backend team wants to deprecate the given Node.js microservices system in favour of a monolithic Springboot-based approach. This is mainly due to only one member having prior experience in Node.js development. No members support keeping the current approach, and when asked if SpringBoot would allow a possible microservices constraint, a member of the backend team has stated that a proper-developed SpringBoot application should be easy to divide between different microservices. + +*Decision taken:* +The backend team has agreed to use *SpringBoot* and *JPA* for the backend. + +=== Replacing MongoDB with a relational DBMS + +*Date Recorded:* 01/02/2024 + +*Scope:* database + +*Description:* +A member of the backend has proposed the replacement of MongoDB as DBMS, with PostgreSQL replacing it. A member of the frontend team has supported it, remarking modern DBMS have built-in JSON support if needed. Other members of the backend team have stated their agreement, as many of them do not have prior experience with MongoDB and prefer relational databases. + +*Decision taken:* +The team has agreed to drop MongoDB and to use *PostgreSQL*. + +=== Libraries for React +*Date Recorded:* 09/02/2024 + +*Scope:* frontend, webapp + +*Description:* + +To enhance the efficiency and effectiveness of our development process, we've taken proactive steps to incorporate specific libraries into our project. These carefully chosen libraries, meticulously outlined in detail within issue #16. + +*Decision taken:* +The team has agreed to use Chakra UI as the main library for the frontend. + + +=== HTTP/2 and HTTPS +*Date Recorded:* 23/02/2024 + +*Scope:* security + +*Description:* +To improve security we have decided to make HTTP/2 and HTTPS one of the main requirements in our project as can be seen in issue #51. + +*Decision taken:* +The team has agreed to use *HTTP/2* and *HTTPS*. + +=== Architecture of the question generator process +*Date Recorded:* 03/03/2024 + +*Scope:* backend, question generator + +*Description:* +The question generator must be a part of our application, but different to the webapp or the API, it doesnt need to be running on a loop. It just needs to be run once everytime we want to load or update questions. Implementing it in the API module would result on more coupling but not duplicating code for the model. On the other hand, implementing it in a new module would make the application cleaner and easier to change. + +*Decision taken:* +The team has decided to implement the question generator as a *separate module* of our application. + +=== Connect to the database with the question generator +*Date Recorded:* 10/03/2024 + +*Scope:* backend, question generator, database + +*Description:* +The backend team has been discussing whether to use JPA or JDBC to connect to the database with the question generator. When researching JPA it was found that although it is easier to use since the models already exist in our API, the models cannot be used from a different package, which makes us duplicate code as we do not have time to research another way of implementing it. On the other hand, JDBC would be more complex to use and does not have the same level of flexibility as JPA. + + +*Decision taken:* +The team has agreed to use *JPA* to connect to the database with the question generator. Although this will generate duplication of code in the models, it should be manageable due to the small size of the project. This will be recorded as technical debt and new approach should be looked into if the project grows in size. + +=== Workflow of the game logic +*Date Recorded:* 19/03/2024 + +*Scope:* backend, game logic + +*Description:* +The game logic must be implemented in the backend, since implementing it in the frontend would make it very dependent on performance and prone to allowing cheating and exploits. The questions will be served to the frontend each round but then validated in the backend, to prevent cheating. Points will also be automatically calculated in the backend. + +*Decision taken:* +The team has agreed to implement the game logic in the backend to make it more robust and prevent cheating. -|HTTPS -|To improve security we have decided to make HTTPS as one main requirements in out project as can be seen in issue #51. -|Architecture of the Question Generator -|It has been decided to implement the QG in an independent module so it is only run once and it is not generating questions at real time, this way, we are not dependent on wikidata for having our application available. -|=== -If needed, a more descriptive record can be seen link:https://github.com/Arquisoft/wiq_en2b/wiki[here]. From b7b7f136b2d92417fdb2f89ffc5b335ca94632b3 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 29 Apr 2024 17:28:19 +0200 Subject: [PATCH 39/47] feat: fixed enter --- docs/src/09_architecture_decisions.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/09_architecture_decisions.adoc b/docs/src/09_architecture_decisions.adoc index e2faa1dd..83314426 100644 --- a/docs/src/09_architecture_decisions.adoc +++ b/docs/src/09_architecture_decisions.adoc @@ -58,7 +58,6 @@ The team has agreed to drop MongoDB and to use *PostgreSQL*. *Scope:* frontend, webapp *Description:* - To enhance the efficiency and effectiveness of our development process, we've taken proactive steps to incorporate specific libraries into our project. These carefully chosen libraries, meticulously outlined in detail within issue #16. *Decision taken:* From 73e27cf1c3ba43f8cdd1c6d15eb8be91c70bd42b Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 29 Apr 2024 18:47:44 +0200 Subject: [PATCH 40/47] feat: 404 page --- webapp/public/img/kiwi.png | Bin 0 -> 1168 bytes webapp/public/locales/en/translation.json | 4 +++ webapp/public/locales/es/translation.json | 4 +++ webapp/src/components/Router.jsx | 2 ++ webapp/src/pages/NotFound.jsx | 36 ++++++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 webapp/public/img/kiwi.png create mode 100644 webapp/src/pages/NotFound.jsx diff --git a/webapp/public/img/kiwi.png b/webapp/public/img/kiwi.png new file mode 100644 index 0000000000000000000000000000000000000000..dbdac5df7e8813230905cd87379c401821bbb95c GIT binary patch literal 1168 zcmV;B1aJF^P)4z!6#ks1C6KjngF`VXfkHaeWQ_+81rJ%gXNku^*DfJb$S(+VOeu8Fk|A3LXANE& zD1?*@F~vV1VA8^MFwd2BlI}g7foG0d*#XQ8?M64H_m}r) z&xUsf?M63M#R#fI3#}9YHj-6#pVQIB+-IZR=%(a?r4R%5l1@tG0pND@DC!#`2456X zzn64U6IqD^RB+wULM!c~&uXjZKK)M)@D?C6c@t=Sm8|NtJP}l~wjwANK)0+eLMQ>a zY2*??&2<|@0uXr$P9=LgdYn6Xia}2u4silo0G+%VKnQS2ZzQWTZPn}h+)-E^KEB&d z$3L@8%OJ1zFa6EEfV&c`aRg9Nwg_BASqjHQh{F2RTn8ZgFjSfV zu~vdl&2{W{_Svo$uLP?Gnss`WqB1D}_@%+C!7p#}K*=jX_3lFBO2@a6=cd6D_xU;i z?;u@pJ>#1PDv<*EBQWlRmptopTu?}W*q`ED;}^+$6~=E|q=@-+*!AR>o9EvzzF55Dq~G??t6C3SW4LD7gQBgrG!Z!qXUYjf(!E?5AfYkiFH6M=pvY0QF#H1x-riQ zqAy41g%~Ng+u2XqHNb@B#_O5a&>+&W>W|F{xfi1iN%|fea)&8kALqm@ye{NAz!X@! z(M|j4*CaukoD+N2Ibf%NWP7PfsE>Y4O&^_fndE_pcL9~S>0GSqO0gVsaFc5!JGgvs zu(gw0e~z!u{q_l}g;0l!l&xQAAvoj-BtkF&2)0?5T*oz*4F_90>C5By*7JW3eq@dv zU!T{4*8qJxdeN~s?R4_Po5}0d;c$A)CXU#Grl9n_xYF}#JJWUYoX|@;>6>pKrvG0Z z4)N*hTL8e|VT`N8A-0d|0J2U9;s8!z7*|{Psr1Z6L|n=uB}YTn-J_S zpo^eyOQ-ibNjgu*P5rpoKB}X+wvsunIzWfm0eRJ){x0v&YDKENw2n~TpX}Xb&bb`m z6`<`$2s&&6*j(TXH4W;Um~p}2VT|F; @@ -27,5 +28,6 @@ export default createRoutesFromElements( } /> } /> + } /> ) diff --git a/webapp/src/pages/NotFound.jsx b/webapp/src/pages/NotFound.jsx new file mode 100644 index 00000000..1f80048e --- /dev/null +++ b/webapp/src/pages/NotFound.jsx @@ -0,0 +1,36 @@ +import React, { useState } from "react"; +import { useTranslation } from 'react-i18next'; +import { Center, Heading, Stack, Box, Text, Image, Container, SimpleGrid} from '@chakra-ui/react'; +import { InfoIcon } from '@chakra-ui/icons'; + +import LateralMenu from '../components/menu/LateralMenu'; +import MenuButton from '../components/menu/MenuButton'; +import GoBack from "components/GoBack"; +import "../styles/animations.css"; +export default function About() { + const { t, i18n } = useTranslation(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const changeLanguage = (selectedLanguage) => { + i18n.changeLanguage(selectedLanguage); + }; + + return ( +
+ setIsMenuOpen(true)} /> + setIsMenuOpen(false)} changeLanguage={changeLanguage} isDashboard={false}/> + + + + {t('page404.title')} + + + 404Kiwi + {t("page404.text")} + + + +
+ ); +} \ No newline at end of file From 4bbdec3b5738dfbe3d91561c62c24a17015a68ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 22:04:17 +0200 Subject: [PATCH 41/47] chore: refactoring quality requirements --- docs/src/10_quality_requirements.adoc | 41 ++++++++++----------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/docs/src/10_quality_requirements.adoc b/docs/src/10_quality_requirements.adoc index b9cb0680..8330d1c4 100644 --- a/docs/src/10_quality_requirements.adoc +++ b/docs/src/10_quality_requirements.adoc @@ -20,11 +20,9 @@ To obtain a measurable system response to stimulus corresponding to the various | Reliability | The system shall reliably generate accurate and diverse questions from Wikidata. User registrations, logins, and game data storage shall be handled without errors. | High, Medium | Availability | The system shall be available 99% of the time when a user attempts to access it. | High, High | Performance efficiency | The system shall deliver optimal performance, ensuring responsive interactions for users. It shall efficiently generate questions from Wikidata and handle real-time gameplay with up to 1000 concurrent users. | High, High -| Usability | The system shall provide a user-friendly interface, allowing users to register, log in, and play the game with a learning time of less than 4 hours. | High, Medium +| Usability | The system shall provide a user-friendly interface, allowing users to register, log in, and play the game with a learning time of less than 15 minutes. Other functionalities that are not that obvious, such us rules or about pages, are allowed for 2 hours. | High, Medium | Security | User data shall be securely handled. Robust authentication mechanisms shall be in place for user registration and login. API access points for user information and generated questions shall be secured with proper authorization. | Medium, High | Compatibility | The system shall be compatible with various web browsers and devices, providing a seamless experience for users regardless of their choice of platform. It shall be well-optimized for different screen sizes and functionalities. | High, Medium -| Testability | The unit tests shall have at least 80% coverage. | High, Medium -| Monitoring | The system shall have monitoring in place to track the performance and availability of the system. | High, Medium |=== ==== Change Scenarios [options="header",cols="1,3,1"] @@ -32,41 +30,32 @@ To obtain a measurable system response to stimulus corresponding to the various |Quality attribute|Scenario|Priority | Modifiability | The system shall be designed and implemented in a way that allows for easy maintenance and updates. | High, Medium | Maintainability | The code of the system shall be well-documented, and modular, allowing for efficient troubleshooting and modifications. | High, Medium +| Testability | The unit tests shall have at least 80% coverage. | High, Medium +| Monitoring | The system shall have monitoring in place to track the performance and availability of the system. | High, Medium |=== ==== Implementation +===== Security +The system is secured using Spring Security. The user data is stored in a database and the passwords are hashed using BCrypt. The API access points are secured with proper authorization. HTTPS is used to encrypt the data in transit. -===== Performance efficiency -The tests were done with a 2 core and 4 GB of memory system. -This system's efficiency has been measured with Gatling. For the script that we used, a user already created, logged in and played a full game. After that, the user clicked to look the statistics. -The scripts were run a total of 4 times. One with 1 user, other with 100 users, another one with 1000 users and finally one with 10000 users. -The results of this scripts show that response times are reasonable up until 1000 users. Having 10000 users playing a game at the same time make a lot of failures. -Here are the results. - -**1 user:** - -image::Gatling_1_user.png[align="center", title="Gatling results with 1 user"] - -**100 users:** - -image::Gatling_100_users.png[align="center", title="Gatling results with 100 user"] +The system is also protected against SQL injection via using JPA repositories and prepared statements. -**1000 users:** +The system is also designed in such a way that prevents cheating, by limiting the options available for the user and doing all validation in the backend, such as checking if the answer is correct, preventing request forgery. -image::Gatling_1000_users.png[align="center", title="Gatling results with 1000 user"] +===== Testability +Many different tests were done to this project as they were considered as a critical requirement for the development. -**10000 users:** +* **Unit tests:** The strongest constraint, as an 80% code coverage is mandatory to ensure code works properly. -image::Gatling_10000_users.png[align="center", title="Gatling results with 10000 user"] +* **Load tests:** They show how the system will behave under expected workloads. -===== Security -The system is secured using Spring Security. The user data is stored in a database and the passwords are hashed using BCrypt. The API access points are secured with proper authorization. HTTPS is used to encrypt the data in transit. +* **Stress tests:** This way we can the behaviour of the system under unexpected loads, such as DDoS attacks. Although we are not protected against them yet, we could do it by using CloudFare as discussed issue #266. -The system is also protected against SQL injection via using JPA repositories and prepared statements. +* **Acceptance (e2e) tests:** The idea behind them is to ensure the correct functionality of the whole system so real user interactions will work as expected. -The system is also designed in such a way that prevents cheating, by limiting the options available for the user and doing all validation in the backend, such as checking if the answer is correct, preventing request forgery. +* **Usability tests:** They aim for the idea of knowing how easy the product is to learn and to use. -===== Testability +More information about testing can be seen in Section 12. ===== Monitoring The system is monitored using Spring Boot Actuator and Prometheus. The monitoring data is visualized using Grafana. From b6ecf775435f48d369ab7c45ecc7df452b8eea67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 22:04:45 +0200 Subject: [PATCH 42/47] chore: renaming documentation files for new testing file --- docs/index.adoc | 10 +++++++--- docs/src/{12_glossary.adoc => 13_glossary.adoc} | 0 docs/src/{13_annex.adoc => 14_annex.adoc} | 0 3 files changed, 7 insertions(+), 3 deletions(-) rename docs/src/{12_glossary.adoc => 13_glossary.adoc} (100%) rename docs/src/{13_annex.adoc => 14_annex.adoc} (100%) diff --git a/docs/index.adoc b/docs/index.adoc index 2905e55c..c2adcc83 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -78,9 +78,13 @@ include::src/10_quality_requirements.adoc[] include::src/11_technical_risks.adoc[] <<<< -// 12. Glossary -include::src/12_glossary.adoc[] +// 12. Testing +include::src/12_test.adoc[] + +<<<< +// 13. Glossary +include::src/13_glossary.adoc[] <<<< // 13. Annex -include::src/13_annex.adoc[] +include::src/14_annex.adoc[] diff --git a/docs/src/12_glossary.adoc b/docs/src/13_glossary.adoc similarity index 100% rename from docs/src/12_glossary.adoc rename to docs/src/13_glossary.adoc diff --git a/docs/src/13_annex.adoc b/docs/src/14_annex.adoc similarity index 100% rename from docs/src/13_annex.adoc rename to docs/src/14_annex.adoc From ec70bb6bd94007e3da238b120adb310cf4f7137d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Mon, 29 Apr 2024 22:05:20 +0200 Subject: [PATCH 43/47] feat: document test section file --- docs/src/12_test.adoc | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/src/12_test.adoc diff --git a/docs/src/12_test.adoc b/docs/src/12_test.adoc new file mode 100644 index 00000000..cb5cb55d --- /dev/null +++ b/docs/src/12_test.adoc @@ -0,0 +1,50 @@ +ifndef::imagesdir[:imagesdir: ../images] + +== Testing +=== Performance efficiency +The tests were done with a 2 core and 4 GB of memory system. However, real used VM has 2GB and 1 vCPU in order to keep costs low. +If required, a more powerful machine could be easily used instead of the current one. +The difference between machines is not such significant as real VM only loads API and the docker image for the DB, while the testing machine must also take care of loading the front of the website. + +This system's efficiency has been measured with Gatling. For the script that we used, a user already created, logged in and played a full game. After that, the user clicked to look the statistics. + +The scripts were run a total of 4 times. For load testing, one with 1 user, other with 100 users and another one with 1000 users. Finally one with 10000 for stress testing. +Regarding load testing, the results of these scripts show that response times are reasonable up until 1000 users, although roughly 25% of the requests fail. +Here are the results. + +**1 user:** + +image::Gatling_1_user.png[align="center", title="Gatling results with 1 user"] + +**100 users:** + +image::Gatling_100_users.png[align="center", title="Gatling results with 100 user"] + +**1000 users:** + +image::Gatling_1000_users.png[align="center", title="Gatling results with 1000 user"] + +**10000 users:** + +Having 10000 users playing a game at the same time make a lot of failures. However, the server does not get down so after all those users leave, the system could still run normally. + +image::Gatling_10000_users.png[align="center", title="Gatling results with 10000 user"] + +=== Functional Suitability +To ensure the users are able to do what they are supposed to do and nothing else, both unit and acceptance testing were done. + +Acceptance testing tries to see if the user is able to use the application properly without crashing. However, as security is one of the most important requirements, unit testing from a 'back-end' point of view, so that as few unexpected inputs or bugs may happen in any part of the application (front-end included). + +On top of it, strong e2e testing was built to reensure the integrity of the software, as negative and positive test cases were introduced. Via e2e, we can also know what the user is capable of by means of normal application usage and how the system reacts to it. + +Unit testing was done via Jupyter for API while Jest and React Testing Library for React components in front-end views. +Gherkin was used for acceptance testing and user story building. +Finally, e2e tests were done in JS using Cucumber and Puppeteer. + +=== Usability tests +Although they were not as much as desired, 4 usability tests were done as a way to know how easy our product is. The people tested all use internet daily, although they do not share the same level of expertise. + +Most people had no problem to complete the basic task (registering and playing a full game). However, one wanted to log in before signing up (probably that was just a dumb mistake). +Regarding some more complex task, such us seeing the rules, some of the users had problems as they had not noticed the button for deploying the lateral menu. + +Overall, usability tests were good enough, although we would like to have some more people with a wider range of age \ No newline at end of file From 12e76b9e98d67f42e370b21f673d6ffbbf183f50 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 29 Apr 2024 22:13:55 +0200 Subject: [PATCH 44/47] feat: added jose emilio --- webapp/public/img/kiwi-2.gif | Bin 0 -> 4742 bytes webapp/public/img/kiwi-3.gif | Bin 0 -> 4719 bytes webapp/public/img/kiwi.gif | Bin 0 -> 5729 bytes webapp/src/components/menu/LateralMenu.jsx | 1 + webapp/src/pages/NotFound.jsx | 4 ++-- 5 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 webapp/public/img/kiwi-2.gif create mode 100644 webapp/public/img/kiwi-3.gif create mode 100644 webapp/public/img/kiwi.gif diff --git a/webapp/public/img/kiwi-2.gif b/webapp/public/img/kiwi-2.gif new file mode 100644 index 0000000000000000000000000000000000000000..19590de312a545cd4210e8cd90dbafb6c8fc1ba2 GIT binary patch literal 4742 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 BGCcqQ literal 0 HcmV?d00001 diff --git a/webapp/public/img/kiwi.gif b/webapp/public/img/kiwi.gif new file mode 100644 index 0000000000000000000000000000000000000000..fbd3f0fe304f639107b9d50b41d92207d81bf102 GIT binary patch literal 5729 zcmcJTcTf}Bp2q_uASftO14_H1R0#@FMU-Ads&par8hXbNdQ0d%l+b$%$8mwVst&d%GNeX~37otZOd&YAP)XTIg9qNXe<0hI^H1N;C0?mw<-De3De zJk?fua34ZJ2Dp5>08-OjoFu{PKLtrSll@*os9}iBIIAZq^#_5w(gGy zD-XIY_+oyOhSnII=r(mlOq9C^mFU+>%|WU+V78sHZEE+?UM?p{ci7-6$y76PeCbrv z&|2%RPlz)-`aZ8+v{_sbmw!T3Qi@wrXm&g&3LTrxo|)35nGy(g!ip=hAUXfZ3sK ze!i&TBpT|UqGwWg$^_Z_VDh*sgVEpeuYXs3lM8-UIF|iZaUL1NP);2q8`Mpjf|Viw zDv>aJY3~6mgi&ZS@;!RGCL>x}-SmnV_KTl;a&D5=cC1SByhR$D)mqa%1bf6eM z^rAgofL+lH6HC(Hs8&mbsS1Kn1NJS%MhTG#UuB=Iw5@!nLvQfU@8`eotxfdw{83N; z-w2ZVrw~+G_p*Zq$2D=%Ky8tkvJJq$wcIX0-qGr|5E@G3D*(?4Tv(e_zToN|fV$d` zI78U037rgJZDGpPOu0fsSJ=$3??LR**vu~c!Cj~`JTg>__jOEsj6IKkKypGDmz%pQ zDlL#LGa(wC%bJSBq)E6Hl$Gb@m%4%)4J%$lLlI9K-*_ryR$H~a#C8^USM^v?7O_-j zwYK?EPS*|$4vCLU&-Bj@#w;$4brU8wUlJFWD+>*_=XXeq8-*^DVn?6W$4~aR&xzkU zbVhKSobmx1%WBgns<<8+>d^*MU^#g=f?OiK(z6c$@Mb-k6Amrb9*YXU!C*yZ6}6?z z$F0qSoEIvL4Q6)O9eabb9?GMi+5~p!?Yt<0pmwivA^3G~Nv6?}xdW9)JakoVfxqD^ zXDf`oj#SlVDdrnYN}KtrnOzrYr5E!hEliq5utbC~lu}mHXoO72cFl(5QjvX*KSCp>BsYabp|JQcEQSONd)gTW_qd z#z7Ibz&C2vc4kXwPBb0H;j1YCU-w1CZNW^mY=qnB8~PvjVc(nfb~%I51G1e553UjM zadUJ(J5Be-HyG+q;OY!C{}7x@efi&Y=kmk$=OneAT{5P)!#u)#SN%uaBm(f6F^d*fUw(-N@|a^q%-Yl7Pt0!%wGX*_5guV+ zG_iL1iT~a?gssDJa$PC8@Il&-7?F4mJ+2?dPyK9>NoWl%{~)UCmJ<&d0%>e1#C9f& zW#R~m0{WFYWkb1g*SHV@@0o>5Rw*p;yZTxt;sBCVu|i@ER1~>2JWH|IEazTfj>REX zuzot;>7!6L^G*aUa$ciV7>Z-RcFSn&96-DtjXgtsW(i;Kze0D%66ps?!bj0-@F62S z$U!t>A_r#pdWVtXLVebxfQL@+bANBCTh0jEoMdwl=|%-MS+8J~zH^K&k?$|Q^IAH+ zl_TO#KXH9oF6pApa<@_K4@&Ofri)o$i8x+!eM!GhyQCBq`GQ{_&%bCO({7Ocy>I=U z>imD;sieM}S(ipEvg@sL`|0yQD%$4>AiSVId?}LhG1)Ye3mr<>)@xVuQK^36H38!X zO|Lj5NB85JhUc=zXs!EpPuZkvQ9WNe-I?C?RC)w@1w%a@!@^~q{GDIMSo7G2`6edC zr38kiYh@;+h8P2jGPoR(0XddM#V)1!iIs&weNYf*8K$B!5Y$yymX7s&+tq#VWm?Q| zSpNWP8xA#!A15$Rwr7{Wp9M`sFBFiPS5{f((T18E6kGj(?VIzTqh2{5%<4>j^4VM3 zKVE(=KUmLM#>E6A=Q7gHV-;fV+%j!T%zwnox?z(gIbo$>O8<3D$L3sgiqh+La+(;J ze@k89p_zK$I=$Uk0PF!_EQ^6MErID{dNNhBLt>#KNy51}wfi?r*9`IsUcA zjJAnoNx62x1GZAI^6i%}JJ~*|4WvLQ^cz~e-XyvthgfI}?M%u3%0Zjkvb*m4)b7T0 zamlYcyGvGnT5FQeF8Ec|V*Zh1{h!(4jmT8V&%`8DFg70vyr8(il?%-&6!?fR(3P;) z1ghHfQ``ze_UCh{l`iKpF!`{rz|4(k^haltyJjJ^XtCn8?xSm>1tTFg-5UT5| z><|QZlMMCNw+oAMydx9m6PcjK=i(=mZXpGW%5eY|IV2%buT%7kid8}4rCAk~RX|fv zt6~{C4PMvU#$MJG(fPWk7mV~u==U4!8OGt?&FD`I@=v3^%H8JX8FArD)=_H{r0JCc zlRdSMb2oPn;Q0pms4&t1+REyPJTH77-Evl~pQaB{dB&G( zI8}Jl2>A6e$F8Ezol53gGAZmhow(6VbZ)bE(??DTCMU`={Q=SP&OCX;^B-Ww9ql}& zAP%LRw5e%ta&kpi>9GD8Apk=4t-5#hOf_qcQPLEY(y&1zB8GKpQ^@41Rmi*kzp>Ly z*urEUtn^UI@66`-9ua9N>G)m+6A!8I5$5u8Nbbfz*%9g%%xWOjBwf6!gjoK?j^K7D z39>bu@?Y6;RdTUe$htnST-*L9J6G3suI8S(FZ;+)L03)pqZhJVIj_KPwL9!#`s}l6 zO}sp?^&E${)!{NHCvFOsD)MXR1?P9PwEp?*T;}CiYCEQqnme6**cM)w7vUdj@6ax` zEL+ALe-5rM^i%`VS!$M!L|;`-mnS<@2aZ*|5R|xfTcncFGX-sq##*{a<-GQKtHy3A3VpJ2HOed0 zL6|4#l>9QF4hY-)7TId|2HVM&TxvJyhJSajsMck|Y!nYB6!-KR&b()NJ2$_exwOpq zVJ(uRu`#ptp&-=c9L4;oMXWW=#BeV9{+2ZfQ>)behelJslHmX3`!P ziUHhVYxde%eTNb{xdVTvqOg%FYbwWmyi=mt>u)@H!r(ZTs~IHbg=>q|v)9Uh_*xyf zsJC;sh{+Ooqv5@SatX8cXNQ~XfUru+w;hpnasp-y#w75TxJ$3%yXJS8++!>H-Bu7PPXf{~ zs@^pVP}msA*72f*aDCaFEaV>)M|B8AR-z645`qD~JHyfTGarIYYt66Knt#uzm1s=f z==ZTuU6py{M#4)G>jb-=?MAa|p+5URkG#-so1WqW+hq9bFtv z$pQb^{!X%o>fsDxrES?x<%BkvCF=%6M!G<&$WlK1W!f_*CYp5R_CeeFD<1+!Ka{o` zpS_tPxOgwY@$Ky!K_)%l%>%t1!r=aQWlZ4qk(RtRk%`DC>3H{;6k8tMi16H$9CR?! z*AiHkotB(bVpdkJ1%h%F78TcM*S`Q^3bQhijUXH++BqfP4>!WrJe1dJJ2DC$f7?Rn zn4V$j`YM&(04(+!lM0J=` zT!U3APzQS!5!&ibD#ECr(U=SIuJ68f-Z(JnK^&Vv+%XL#iohqpI_$g~B`%Wwma3&B z%!12rTslL$6$ijmGxBAb&3~aBM6-A?3qcV!BSlJv89{-+>(h7}ww4k*++C*9=x6F^ z{e>3BSj~EVJx14Cjpg|?5imdGKU1VcvS(ETr4Dx30`)j7z6qy%#P**Ito{gIyfH#Q z#KQ6A@egH2o`1Ss{jH*37OatsFn3@6izpAFz!TI*>9p&VKrKqvIqjS5k_D4RmPrb7 znP4X-HetDPvwLSdv>NEO2LGBB&(d8<&jls)zUS$rBUR7OfB=bLm`7wNR8Za|)IG-I zo^4EgqGxh~XIgraqJ3&K+Kb2DKcKX*h^quqR&EAtFauRqRi|UL8#=T>dGR^fy-tlU zKm$*zYx+h41_)i8!(*c}P7{OdCHfU9<8$-i>E$};rCY6|4QBhd39Qqfo!rY0=2$*0 zBXJX62TT_O0V(3nxE|{D&9_d^>TnW>V{IQ%JTHY9%`sz@mo?AEqu9i4eVg|&agl?G zaMhP$;A9TAC_RD@*imqUHAfP)JeKWs4UjMS`T<#vCf@)n8SoD8w;4WpYOye`_l+Zt zQ5?(&ZwL?G=_dOPvPso%IK)Dey$%vI3f-q^b{|hLihnC?-f%qFCyOAlxp44dbC}Cz;6VP||ODTAi#9?FJlWLYN=3I;~u#xXB4Y z1vIX`uTs$C`|K;`xLC18zC9K6b?65A=U15vJ8tK)aQva#=M2}L@8Payeq!)QLs8e)X3kL| z_e|pggHQ=PNK|T?Kd((rcFrqqn`HNrv|LS$NffXaEnQp{0k5qy1nFcHd&3J{T8uy) zu%@W~oHo!%1Gckk5H&K*IZl|EgikL%%li;H@3**)W0_qIfALXgV|{D3DweqSd7o+f z$WE);_~exF@O=G3_ikiw`GcA zXpp=H?Y&uUv0TBTMrAqT9t5NapWnir)NLz5fovf8?Zugo+G$F>w`wy^3K4)8nJpDl zY5nnZ5_xR4x~Es@%3lz07ZUEM!;5* zY%yblCJnDj+(Y|3wRnv~ZTDMpBIYE&n&vRnM{1+aGVEOcJ+x}w*JFg8pojdg7@{;MTg)Ux5*Kic&wbc&QvUleKd9c9Zn zZGkr+v!CcHm`vs?atx~YYn~%CiBq9eObyUhMUdr&kO{3TX&~~iILy})myR*4jrpre z31&b@e~ZI_hU<7OuCL=sMxtR9)XZzQm(L(JbU}?mS{QjUyMDBUX8za*h((Xlef#_7 zCJ$fD)|fiSVh!+!Q7VT_!t(i}13Xh;YrZq7)rHgodh&|!!)NCjA$*P!_Cs=K_r1~& kxxDPQ6$8)uL2wAb--BXj4ep+RfqU3~=~pPaR2-Rq0~fJZy#N3J literal 0 HcmV?d00001 diff --git a/webapp/src/components/menu/LateralMenu.jsx b/webapp/src/components/menu/LateralMenu.jsx index f51a592b..fa964b03 100644 --- a/webapp/src/components/menu/LateralMenu.jsx +++ b/webapp/src/components/menu/LateralMenu.jsx @@ -100,6 +100,7 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => { + 404Kiwi {isLoggedIn && ( diff --git a/webapp/src/pages/NotFound.jsx b/webapp/src/pages/NotFound.jsx index 1f80048e..d9ace535 100644 --- a/webapp/src/pages/NotFound.jsx +++ b/webapp/src/pages/NotFound.jsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { useTranslation } from 'react-i18next'; -import { Center, Heading, Stack, Box, Text, Image, Container, SimpleGrid} from '@chakra-ui/react'; +import { Center, Heading, Stack, Box, Text, Image, Container} from '@chakra-ui/react'; import { InfoIcon } from '@chakra-ui/icons'; import LateralMenu from '../components/menu/LateralMenu'; @@ -26,7 +26,7 @@ export default function About() { {t('page404.title')} - 404Kiwi + 404Kiwi {t("page404.text")} From e3c271faa61e2b56babcd3d50e65fe47844545d2 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 29 Apr 2024 22:18:26 +0200 Subject: [PATCH 45/47] feat: updated jose emilio --- webapp/public/img/kiwi.gif | Bin 5729 -> 5730 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/webapp/public/img/kiwi.gif b/webapp/public/img/kiwi.gif index fbd3f0fe304f639107b9d50b41d92207d81bf102..a5205b748cfa8ff922094cff6dc92070feab7460 100644 GIT binary patch delta 631 zcmaE;^GIhxcKzAi{}VL&QY_h4tn7OEs?1T7!#Vf<^Y zwp?QUj>JultOpX-t)IVb{{hynUGp|<+;{xc=_9Mow{E#SVRkWF#if;3&StYczCLg1 z9>+V6pB%pbpzYCXwx^RH7Mol(|MdFHv-cl=|C*?k=5yb!?w-+2=3vg_j+IP*Ggo~R zNOUT{c|m1XfZ-!K)vSMF%VsWGQh&&~zxmFIjm`N!ZOtpn-YUIpcXpU!R9w~2Vf=g@ zzm5K{FOr8AU07J^b*5WW!6A4_62pXxg$F%FbVN#m?m8UtnWo~m)#vUiMX%7+n#U$R z%{t;Vaa*?Kj9R1VM;dome5`sARn8v2yOLSH;o$cChlk^IWgHIdFl;(dBq%*G#^P{& z(lJf}jf^?d3#J@qQ2FhvljxAl$}N_Wc4ubp^$Ych&27Dj4$sc63@*O*`q$Q$ZTo7k zpX*U5e|5IM@_S$GF`Z)WZTZsoYp-v9v+ug2i~hRVJBqF^ewg+>q55mulZxF_kFHy6 z!}RDR|BC)1_lNl1_tKMc}x~ejLef2IGrcv zvu*ys@|h9B+pNz11IXO0z-bMY-#nlDD^wDs^d9dAB%Xx8LnNNF&}$@Kg~&@r080NV A2mk;8 delta 631 zcmaE)^H66(_T&UkmdSQPZ1t9$haT+vupsND<%XO!=jXV0na6m$U*Ry?7q8KwY?Zhs z^h?m@y)EaL7gR3^S!;RSVD;m?)IFU$JoRQ5=X~iY$UA=TVYE-{+1p=z!jt{fO6#-h z+Vxb^+GDz^6)O7LCQQno&^WVOzIS%#OzFuJB4*E#tY|JPT;|rlw0^-tiItn`qt;ll z?uuNoa^{AeyI5Pc%viO0$H5~<_boYDzwT_;)LgcrGmFn1Phq=zamM^@R@d&{+k5j? z!=0yW_j_;W>Yg`v^Yq<=m#@EmpSSs8 zz3<`0yrm5c?yY%@7gT-K8DxWeCQiDX*|{Qq#U_WY=9O241G^o+Uih-QbAv?C0@Kw= zxl!A-_)g5V7H17pwcT)T!RhdK9lHw{xzA-iWN13vA^731K}Bxz0ZATtzXk`!q+Rug zx#V_7GR4%Jc8Kak1pG93#ydf=IIK6EH}&*l_xN?)QC}nUGrp!tI7m9axj0Sq^18c{ z=AFBD3-9@7dt3jp%C27FYX@c*<*@8+7e0U6>KFUHrMm3%t9y^7Z+o!OT>pHTTj|p& zPv>R5FOmHF`^V?k_s{R&|9`z81B2pE7EUe(1_m7 Date: Mon, 29 Apr 2024 22:43:27 +0200 Subject: [PATCH 46/47] test: added test for 404 --- webapp/src/tests/PageNotFound404.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 webapp/src/tests/PageNotFound404.js diff --git a/webapp/src/tests/PageNotFound404.js b/webapp/src/tests/PageNotFound404.js new file mode 100644 index 00000000..1ae3a251 --- /dev/null +++ b/webapp/src/tests/PageNotFound404.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { MemoryRouter } from 'react-router-dom'; +import PageNotFound from '../pages/NotFound'; +import theme from '../styles/theme'; +import { ChakraProvider } from '@chakra-ui/react'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: key => key, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }), +})); + +describe('404 page', () => { + it('renders title and description correctly', () => { + const { getByText } = render(); + + expect(getByText('404 - Page not found')).toBeInTheDocument(); + expect(getByText('Woops! You seem lost! Click on go back to find your way.')).toBeInTheDocument(); + }); +}); From 396960a6c8280e03ea9e0fcaf67557d0c028083b Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 29 Apr 2024 22:51:48 +0200 Subject: [PATCH 47/47] test: fixed tests --- webapp/src/tests/PageNotFound404.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/tests/PageNotFound404.js b/webapp/src/tests/PageNotFound404.js index 1ae3a251..02fe0f55 100644 --- a/webapp/src/tests/PageNotFound404.js +++ b/webapp/src/tests/PageNotFound404.js @@ -19,7 +19,7 @@ describe('404 page', () => { it('renders title and description correctly', () => { const { getByText } = render(); - expect(getByText('404 - Page not found')).toBeInTheDocument(); - expect(getByText('Woops! You seem lost! Click on go back to find your way.')).toBeInTheDocument(); + expect(getByText('page404.title')).toBeInTheDocument(); + expect(getByText('page404.text')).toBeInTheDocument(); }); });