From 9435124f24b3ac08fd478f6aefdef5bea42df6f6 Mon Sep 17 00:00:00 2001 From: Quentin Bellanger Date: Fri, 13 Dec 2024 17:38:16 +0100 Subject: [PATCH] Ajoute des tests end-to-end pour les audits et rapports (#894) * add basic test * test audit edition * test audit deletion * improve seeds generation and document it * add notes test * add statement test * move tests documentation in cypress folder * add copy audit, criteria search and hide references tests * add tests commands * make getbylabel accept regex * test filters, download, transverse status, finish audit, na on topic and reset filters features * add some report tests * seed an audit before each test * seed audits for report tests * use object options in create test audit and fix wait tests * document tests script and command * add todo tests * fix regex in test to catch finish date on audit overview page * add account creation tests * handle error where user login with wrong token format * test user login and settings actions * temp: to reset * remove unecessary create test account function * use debug api endpoints to generate test audits * test audits list page * update tests doc * lint * only import debug controller on certain condition * remove faker * remove cypress boilerplate files --------- Co-authored-by: Adrien Boutigny --- confiture-rest-api/src/app.module.ts | 9 +- confiture-rest-api/src/auth/auth.module.ts | 3 +- confiture-rest-api/src/debug.controller.ts | 199 +++++++ confiture-web-app/README.md | 18 - .../components/account/dashboard/AuditRow.vue | 8 +- .../src/components/audit/AraTabs.vue | 3 +- .../src/components/audit/PagesSample.vue | 1 - .../src/pages/report/ReportPage.vue | 4 +- confiture-web-app/src/store/account.ts | 11 +- cypress/.gitignore | 1 + cypress/README.md | 38 ++ cypress/e2e/account.cy.ts | 423 +++++++++++++- cypress/e2e/audit.cy.ts | 520 +++++++++++++++++- cypress/e2e/report.cy.ts | 173 +++++- cypress/examples/1-getting-started/todo.cy.js | 143 ----- .../2-advanced-examples/actions.cy.js | 299 ---------- .../2-advanced-examples/aliasing.cy.js | 39 -- .../2-advanced-examples/assertions.cy.js | 176 ------ .../2-advanced-examples/connectors.cy.js | 98 ---- .../2-advanced-examples/cookies.cy.js | 118 ---- .../2-advanced-examples/cypress_api.cy.js | 185 ------- .../examples/2-advanced-examples/files.cy.js | 85 --- .../2-advanced-examples/location.cy.js | 32 -- .../examples/2-advanced-examples/misc.cy.js | 104 ---- .../2-advanced-examples/navigation.cy.js | 56 -- .../network_requests.cy.js | 163 ------ .../2-advanced-examples/querying.cy.js | 114 ---- .../spies_stubs_clocks.cy.js | 201 ------- .../2-advanced-examples/storage.cy.js | 110 ---- .../2-advanced-examples/traversal.cy.js | 121 ---- .../2-advanced-examples/utilities.cy.js | 108 ---- .../2-advanced-examples/viewport.cy.js | 58 -- .../2-advanced-examples/waiting.cy.js | 30 - .../examples/2-advanced-examples/window.cy.js | 22 - cypress/fixtures/audit.json | 40 +- cypress/fixtures/example.json | 5 - cypress/fixtures/statement.json | 13 + cypress/support/commands.ts | 106 +++- cypress/support/e2e.ts | 4 +- package.json | 2 + 40 files changed, 1502 insertions(+), 2341 deletions(-) create mode 100644 confiture-rest-api/src/debug.controller.ts create mode 100644 cypress/.gitignore create mode 100644 cypress/README.md delete mode 100644 cypress/examples/1-getting-started/todo.cy.js delete mode 100644 cypress/examples/2-advanced-examples/actions.cy.js delete mode 100644 cypress/examples/2-advanced-examples/aliasing.cy.js delete mode 100644 cypress/examples/2-advanced-examples/assertions.cy.js delete mode 100644 cypress/examples/2-advanced-examples/connectors.cy.js delete mode 100644 cypress/examples/2-advanced-examples/cookies.cy.js delete mode 100644 cypress/examples/2-advanced-examples/cypress_api.cy.js delete mode 100644 cypress/examples/2-advanced-examples/files.cy.js delete mode 100644 cypress/examples/2-advanced-examples/location.cy.js delete mode 100644 cypress/examples/2-advanced-examples/misc.cy.js delete mode 100644 cypress/examples/2-advanced-examples/navigation.cy.js delete mode 100644 cypress/examples/2-advanced-examples/network_requests.cy.js delete mode 100644 cypress/examples/2-advanced-examples/querying.cy.js delete mode 100644 cypress/examples/2-advanced-examples/spies_stubs_clocks.cy.js delete mode 100644 cypress/examples/2-advanced-examples/storage.cy.js delete mode 100644 cypress/examples/2-advanced-examples/traversal.cy.js delete mode 100644 cypress/examples/2-advanced-examples/utilities.cy.js delete mode 100644 cypress/examples/2-advanced-examples/viewport.cy.js delete mode 100644 cypress/examples/2-advanced-examples/waiting.cy.js delete mode 100644 cypress/examples/2-advanced-examples/window.cy.js delete mode 100644 cypress/fixtures/example.json create mode 100644 cypress/fixtures/statement.json diff --git a/confiture-rest-api/src/app.module.ts b/confiture-rest-api/src/app.module.ts index 149f65d6..dd870543 100644 --- a/confiture-rest-api/src/app.module.ts +++ b/confiture-rest-api/src/app.module.ts @@ -8,6 +8,8 @@ import { MailModule } from "./mail/mail.module"; import { AuthModule } from "./auth/auth.module"; import { ProfileModule } from "./profile/profile.module"; import { UserMiddleware } from "./auth/user.middleware"; +import { DebugController } from "./debug.controller"; +import { PrismaService } from "./prisma.service"; @Module({ imports: [ @@ -21,7 +23,12 @@ import { UserMiddleware } from "./auth/user.middleware"; AuthModule, ProfileModule ], - controllers: [HealthCheckController] + providers: process.env.DEBUG_ENDPOINTS ? [PrismaService] : [], + controllers: [ + HealthCheckController, + // enable debug enpoints only when the DEBUG_ENDPOINTS variable is set + ...(process.env.DEBUG_ENDPOINTS ? [DebugController] : []) + ] }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { diff --git a/confiture-rest-api/src/auth/auth.module.ts b/confiture-rest-api/src/auth/auth.module.ts index 8bf21ee1..7d65e862 100644 --- a/confiture-rest-api/src/auth/auth.module.ts +++ b/confiture-rest-api/src/auth/auth.module.ts @@ -30,6 +30,7 @@ import { CreateAccountController } from "./create-account.controller"; }), FeedbackModule, AuditsModule - ] + ], + exports: [AuthService] }) export class AuthModule {} diff --git a/confiture-rest-api/src/debug.controller.ts b/confiture-rest-api/src/debug.controller.ts new file mode 100644 index 00000000..dcf0d4e8 --- /dev/null +++ b/confiture-rest-api/src/debug.controller.ts @@ -0,0 +1,199 @@ +import { Body, Controller, Post } from "@nestjs/common"; +import { PrismaService } from "./prisma.service"; +import { AuthService } from "./auth/auth.service"; +import { + CriterionResultStatus, + CriterionResultUserImpact +} from "@prisma/client"; +import { nanoid } from "nanoid"; +import { CRITERIA } from "./audits/criteria"; + +@Controller("debug") +export class DebugController { + constructor( + private readonly prisma: PrismaService, + private readonly auth: AuthService + ) {} + + @Post("verification-token") + async getAccountVerificationToken(@Body() body: { username: string }) { + const token = this.auth.regenerateVerificationToken(body.username); + return token; + } + + @Post("password-reset-verification-token") + async getPasswordResetVerificationToken(@Body() body: { username: string }) { + const token = this.auth.generatePasswordResetVerificationToken( + body.username + ); + + return token; + } + + @Post("email-update-verification-token") + async getEmailUpdateVerificationToken(@Body() body: { uid: string }) { + const token = this.auth.regenerateEmailUpdateVerificationToken(body.uid); + + return token; + } + + @Post("create-verified-user") + async createVerifiedUser() { + const email = `john-doe${Math.random()}@example.com`; + const password = "pouetpouetpouet"; + + await this.auth.createUnverifiedUser(email, password); + const user = await this.prisma.user.update({ + data: { + isVerified: true, + verificationJti: null + }, + where: { + username: email + } + }); + + const authToken = await this.auth.signin(email, password); + + return { + username: email, + password, + authToken, + uid: user.uid + }; + } + + @Post("create-audit") + async createAudit( + @Body() + body: { + isComplete: boolean; + isPristine: boolean; + noImprovements: boolean; + auditorEmail?: string; + } + ) { + const editUniqueId = `edit-${nanoid()}`; + const reportUniqueId = `report-${nanoid()}`; + + const completedAudit = await this.prisma.audit.create({ + data: { + editUniqueId: editUniqueId, + consultUniqueId: reportUniqueId, + creationDate: new Date(), + publicationDate: body.isComplete ? new Date() : null, + auditTrace: { + create: { + auditConsultUniqueId: editUniqueId, + auditEditUniqueId: reportUniqueId + } + }, + auditType: "FULL", + procedureName: "Audit de mon petit site", + auditorEmail: body.auditorEmail || "etienne.durand@example.com", + auditorName: "Étienne Durand", + transverseElementsPage: { + create: { + name: "Éléments transverses", + url: "" + } + }, + pages: { + createMany: { + data: [ + { + name: "Accueil", + url: "https://example.com" + }, + { + name: "Contact", + url: "https://example.com/contact" + }, + { + name: "À propos", + url: "https://example.com/a-propos" + }, + { + name: "Blog", + url: "https://example.com/blog" + }, + { + name: "Article", + url: "https://example.com/blog/article" + }, + { + name: "Connexion", + url: "https://example.com/connexion" + }, + { + name: "Documentation", + url: "https://example.com/documentation" + }, + { + name: "FAQ", + url: "https://example.com/faq" + } + ] + } + } + }, + include: { + transverseElementsPage: true + } + }); + + const auditPages = await this.prisma.auditedPage.findMany({ + where: { + auditUniqueId: editUniqueId + } + }); + + if (!body.isPristine) { + await Promise.all( + [completedAudit.transverseElementsPage, ...auditPages].map(async (p) => + this.prisma.criterionResult.createMany({ + data: CRITERIA.map((c, i) => ({ + status: [ + CriterionResultStatus.COMPLIANT, + CriterionResultStatus.NOT_APPLICABLE, + CriterionResultStatus.NOT_COMPLIANT + ][i % 3], + notCompliantComment: "Une erreur ici", + notApplicableComment: body.noImprovements + ? null + : "Attention quand même si ça devient applicable", + compliantComment: body.noImprovements ? null : "Peut mieux faire", + quickWin: i % 7 === 0, + userImpact: [ + CriterionResultUserImpact.MINOR, + CriterionResultUserImpact.MAJOR, + CriterionResultUserImpact.BLOCKING, + null + ][i % 4], + topic: c.topic, + criterium: c.criterium, + pageId: p.id + })) + }) + ) + ); + } + + if (!body.isComplete && !body.isPristine) { + await this.prisma.criterionResult.delete({ + where: { + pageId_topic_criterium: { + topic: 1, + criterium: 1, + pageId: auditPages[0].id + } + } + }); + } + + return { + editId: editUniqueId, + reportId: reportUniqueId + }; + } +} diff --git a/confiture-web-app/README.md b/confiture-web-app/README.md index ed751b1e..5c28995a 100644 --- a/confiture-web-app/README.md +++ b/confiture-web-app/README.md @@ -31,24 +31,6 @@ Lancer le [serveur local sur le port 3000](http://localhost:3000) : yarn dev ``` -## Tests - -[Cypress](https://www.cypress.io/) est utilisé pour lancer des tests end-to-end (e2e) dans un navigateur pour reproduire le comportement des utilisateurs. - -Les tests peuvent être lancés de 2 manières : - -- Via l’application Cypress avec : - - ```sh - yarn cypress open - ``` - -- Via le terminal avec : - - ```sh - yarn cypress run - ``` - ## Guidelines - Utiliser les media queries en "desktop first" et avec la notation suivante avec les [valeurs de points de rupture du DSFR](https://www.systeme-de-design.gouv.fr/elements-d-interface/fondamentaux-techniques/grille-et-points-de-rupture) : diff --git a/confiture-web-app/src/components/account/dashboard/AuditRow.vue b/confiture-web-app/src/components/account/dashboard/AuditRow.vue index f6360a28..cccd75b6 100644 --- a/confiture-web-app/src/components/account/dashboard/AuditRow.vue +++ b/confiture-web-app/src/components/account/dashboard/AuditRow.vue @@ -133,7 +133,7 @@ function copyAuditLink(uniqueId: string) { } function copyReportLink(uniqueId: string) { - const url = `${window.location.origin}/rapports/${uniqueId}`; + const url = `${window.location.origin}/rapport/${uniqueId}`; navigator.clipboard.writeText(url).then(() => { notify( @@ -174,8 +174,8 @@ function copyStatementLink(uniqueId: string) { 'fr-badge--purple-glycine': isInProgress || isNotStarted }" > - Statut - {{ isInProgress || isNotStarted ? "En cours" : "Terminé" }} + Statut {{ isInProgress || isNotStarted ? "En cours" : "Terminé" }}

@@ -273,7 +273,7 @@ function copyStatementLink(uniqueId: string) { ? "Continuer l’audit" : "Voir le rapport" }} - + {{ audit.procedureName }} { @keydown.home.prevent="selectFirstTab" @keydown.end.prevent="selectLastTab" > - - {{ tab.label }} + {{ tab.label }} diff --git a/confiture-web-app/src/components/audit/PagesSample.vue b/confiture-web-app/src/components/audit/PagesSample.vue index f996f0c1..36984fe8 100644 --- a/confiture-web-app/src/components/audit/PagesSample.vue +++ b/confiture-web-app/src/components/audit/PagesSample.vue @@ -122,7 +122,6 @@ function updatePageOrder(startIndex: number, endIndex: number) { class="fr-btn fr-btn--tertiary-no-outline" type="button" :disabled="pages.length === 1" - data-cy="delete" @click="deletePage(i)" > Supprimer diff --git a/confiture-web-app/src/pages/report/ReportPage.vue b/confiture-web-app/src/pages/report/ReportPage.vue index 493b75df..d435f7aa 100644 --- a/confiture-web-app/src/pages/report/ReportPage.vue +++ b/confiture-web-app/src/pages/report/ReportPage.vue @@ -48,7 +48,9 @@ const tabs = computed(() => [ const showCopyAlert = ref(false); async function copyReportUrl() { - const url = `${window.location.origin}/rapports/${uniqueId}`; + const url = + window.location.origin + + router.resolve({ name: "report", params: { uniqueId } }).fullPath; navigator.clipboard.writeText(url).then(() => { showCopyAlert.value = true; diff --git a/confiture-web-app/src/store/account.ts b/confiture-web-app/src/store/account.ts index 8ed718b0..81ddbd9f 100644 --- a/confiture-web-app/src/store/account.ts +++ b/confiture-web-app/src/store/account.ts @@ -20,11 +20,14 @@ export const useAccountStore = defineStore("account", { let authToken = localStorage.getItem(AUTH_TOKEN_STORAGE_KEY) ?? null; if (authToken) { - const payload = jwtDecode(authToken) as AuthenticationJwtPayload; + try { + const payload = jwtDecode(authToken) as AuthenticationJwtPayload; - const isTokenExpired = payload.exp * 1000 < Date.now(); - - if (isTokenExpired) { + const isTokenExpired = payload.exp * 1000 < Date.now(); + if (isTokenExpired) { + throw "tokenIsExpired"; + } + } catch { authToken = null; localStorage.removeItem(AUTH_TOKEN_STORAGE_KEY); } diff --git a/cypress/.gitignore b/cypress/.gitignore new file mode 100644 index 00000000..361ae0fd --- /dev/null +++ b/cypress/.gitignore @@ -0,0 +1 @@ +downloads/ \ No newline at end of file diff --git a/cypress/README.md b/cypress/README.md new file mode 100644 index 00000000..833d2750 --- /dev/null +++ b/cypress/README.md @@ -0,0 +1,38 @@ +# Tests end-to-end + +[Cypress](https://www.cypress.io/) est utilisé pour lancer des tests end-to-end (e2e) dans un navigateur pour reproduire le comportement des utilisateurs. + +En parallèle des tests, le serveur back-end doit être lancé avec la variable `DEBUG_ENDPOINTS` à `1` : + +```sh +DEBUG_ENDPOINTS=1 yarn start:dev +``` + +Différentes données (audit, rapport ou compte) sont créées avant chaque test et de manière indépendante. Il est possible d’ajouter des options en fonction de la donnée souhaitée avec les fonctions [`createTestAccount` et `createTestAudit()`](/cypress/support/commands.ts). + +Les tests peuvent être lancés de 2 manières, depuis la racine du projet : + +- Via l’application Cypress avec : + + ```sh + yarn tests:open + ``` + +- Via le terminal avec : + + ```sh + yarn tests:run + ``` + +## Guidelines + +- Cypress ne supporte pas la gestion des liens externes avec `target="_blank"`. Dans ce cas, il faut ajouter `.invoke("removeAttr", "target")` sur le lien avant de cliquer dessus. Exemple : + + ```js + cy.contains("a", "Compléter").invoke("removeAttr", "target").click(); + ``` + +- Il est préférable d'indique le type d’élément ciblé avec `contains()` pour s’assurer qu’il ne s’agisse pas d’un autre texte sur la page. Exemple : + ```js + cy.contains("button", "Valider les paramètres").click(); + ``` diff --git a/cypress/e2e/account.cy.ts b/cypress/e2e/account.cy.ts index 2065a4ca..7bfcc977 100644 --- a/cypress/e2e/account.cy.ts +++ b/cypress/e2e/account.cy.ts @@ -1,11 +1,416 @@ +import * as auditJson from "../fixtures/audit.json"; + describe("Account", () => { - // it.skip("User can create a new account", () => {}); - // it.skip("User can sign in with their account", () => {}); - // it.skip("User can update their user profile", () => {}); - // it.skip("User can update their password", () => {}); - // it.skip("User can reset their password", () => {}); - // it.skip("User can update their email address", () => {}); - // it.skip("User can delete their account from Ara", () => {}); - // it.skip("User can access all their audits", () => {}); - // it.skip("User profile info are prefilled on new audits", () => {}); + describe("Account management", () => { + describe("User can create a new account", () => { + beforeEach(() => { + const email = `john-doe-${Math.random()}@example.com`; + cy.wrap(email).as("email"); + + cy.visit("http://localhost:3000"); + cy.contains("a", "Créer un compte").click(); + cy.getByLabel("Adresse e-mail").type(email); + cy.getByLabel("Mot de passe").type("pouetpouetpouet"); + cy.contains("button", "Valider").click(); + cy.contains("h1", "Consulter votre boite de réception"); + cy.contains( + `Un mail contenant un lien pour vérifier votre e-mail vient de vous être envoyé à l’adresse : ${email}`, + ); + }); + + it("(verification link)", () => { + cy.get("@email").then((email) => { + // Simulate receiving the verification link by email. + cy.request( + "POST", + "http://localhost:3000/api/debug/verification-token", + { + username: email, + }, + ) + .its("body") + .then((verificationToken) => { + const verificationLink = `http://localhost:3000/compte/validation?token=${verificationToken}`; + cy.visit(verificationLink); + }); + + cy.contains("Votre compte a été créé avec succès"); + cy.getByLabel("Adresse e-mail").should("have.value", email); + }); + }); + + it("(same tab, account verified elsewhere)", () => { + cy.get("@email").then((email) => { + // Simulate the account being verified in another tab + cy.request( + "POST", + "http://localhost:3000/api/debug/verification-token", + { + username: email, + }, + ) + .its("body") + .then((verificationToken) => { + cy.request("POST", "/api/auth/verify", { + token: verificationToken, + }); + }); + + cy.contains("h1", "Votre compte a été créé avec succès", { + timeout: 8000, + }); + + cy.contains("a", "Aller à la page de connexion").click(); + cy.contains("Votre compte a été créé avec succès"); + cy.getByLabel("Adresse e-mail").should("have.value", email); + }); + }); + }); + + it("User can not create account with already taken email", () => { + cy.createTestAccount().then(({ username }) => { + cy.visit("http://localhost:3000"); + cy.contains("a", "Créer un compte").click(); + cy.getByLabel("Adresse e-mail").type(username); + cy.getByLabel("Mot de passe").type("blablablablablabla"); + cy.contains("button", "Valider").click(); + cy.contains( + "Un compte est déjà associé à cette adresse e-mail. Veuillez choisir une autre adresse e-mail. Si vous êtes le propriétaire de cette adresse e-mail vous pouvez vous connecter.", + ); + cy.getByLabel("Adresse e-mail").should("have.focus"); + }); + }); + + it("User can not create account with expired verification token", () => { + cy.visit("http://localhost:3000/compte/validation?token=pouet"); + cy.contains("h1", "Désolé, votre lien n’est plus valide"); + }); + + it("User can sign in with their account", () => { + cy.createTestAccount().then(({ username, password }) => { + cy.visit("http://localhost:3000/compte/connexion"); + cy.getByLabel("Adresse e-mail").type(username); + cy.getByLabel("Mot de passe").type(password); + cy.contains("button", "Se connecter").click(); + cy.contains("h1", "Mes audits"); + cy.contains( + `Vous trouverez ici tous les audits associés à votre adresse e-mail : ${username}`, + ); + }); + }); + + it("User can update their user profile infos", () => { + cy.createTestAccount({ login: true }).then(({ username, password }) => { + cy.visit("http://localhost:3000/compte"); + cy.contains("button", username).click(); + cy.contains("a", "Mon compte").click(); + + cy.getByLabel("Prénom et nom").type("John Doe"); + cy.getByLabel("Nom de la structure").type("ACME"); + cy.contains("Mettre à jour").click(); + + cy.contains("Profil mis à jour avec succès"); + }); + }); + + it("User can update their password", () => { + cy.createTestAccount({ login: true }).then(({ username, password }) => { + cy.visit("http://localhost:3000/compte/parametres"); + + const newPassword = "totototototo"; + + cy.contains("button", "Changer de mot de passe").click(); + cy.getByLabel("Mot de passe actuel").type(password); + cy.getByLabel("Nouveau mot de passe").type(newPassword); + cy.contains("button", "Changer de mot de passe").click(); + + cy.contains("Votre mot de passe a été mis à jour avec succès"); + + cy.contains("button", username).click(); + cy.contains("button", "Me déconnecter").click(); + + cy.contains("Vous avez été deconnecté avec succès."); + + cy.getByLabel("Adresse e-mail").type(username); + cy.getByLabel("Mot de passe").type(password); + cy.contains("button", "Se connecter").click(); + + cy.contains("Le mot de passe saisi est incorrect."); + + cy.getByLabel("Adresse e-mail").clear().type(username); + cy.getByLabel("Mot de passe").clear().type(newPassword); + cy.contains("button", "Se connecter").click(); + + cy.contains("h1", "Mes audits"); + }); + }); + + it("User can reset their password", () => { + cy.createTestAccount().then(({ username, password }) => { + cy.visit("http://localhost:3000/compte/connexion"); + cy.contains("a", "Mot de passe oublié").click(); + cy.getByLabel("Adresse e-mail").type(username); + cy.contains("button", "Valider").click(); + cy.contains( + `Un lien de réinitialisation vient de vous être envoyé à l’adresse e-mail suivante : ${username}`, + ); + + cy.request( + "POST", + "http://localhost:3000/api/debug/password-reset-verification-token", + { username }, + ).then((resp) => { + const verificationLink = `http://localhost:3000/compte/reinitialiser-mot-de-passe?token=${resp.body}`; + cy.visit(verificationLink); + + const newPassword = "blablablabla"; + + cy.contains("h1", "Changer de mot de passe"); + cy.getByLabel("Mot de passe").type(newPassword); + cy.contains("button", "Changer de mot de passe").click(); + + cy.contains("Votre mot de passe a été mis à jour avec succès"); + + // Try login with old password + cy.getByLabel("Mot de passe").type(password); + cy.contains("button", "Se connecter").click(); + cy.contains("Le mot de passe saisi est incorrect."); + + // Login with new password + cy.getByLabel("Mot de passe").clear().type(newPassword); + cy.contains("button", "Se connecter").click(); + cy.contains("h1", "Mes audits"); + }); + }); + }); + + it("User can update their email address", () => { + cy.createTestAccount({ login: true }).then(({ password, uid }) => { + cy.visit("http://localhost:3000/compte/parametres"); + + const newEmail = `john-smith${Math.random()}@example.com`; + cy.contains("button", "Changer d’adresse e-mail").click(); + cy.getByLabel("Mot de passe").type(password); + cy.getByLabel("Nouvelle adresse e-mail").type(newEmail); + cy.contains("button", "Changer d’adresse e-mail").click(); + + cy.contains( + `Un lien pour confirmer votre nouvelle adresse e-mail vient de vous être envoyé à l’adresse suivante : ${newEmail}`, + ); + + // Simulate receiving the verification link by email. + cy.request( + "POST", + "http://localhost:3000/api/debug/email-update-verification-token", + { uid }, + ).then((resp) => { + const verificationLink = `http://localhost:3000/compte/email-update-validation?token=${resp.body.token}`; + cy.visit(verificationLink); + + cy.contains(`Votre adresse email : ${newEmail}`); + cy.contains("Votre adresse e-mail a été mise à jour avec succès."); + }); + }); + }); + + it("User can delete their account", () => { + cy.createTestAccount({ login: true }).then(({ password }) => { + cy.visit("http://localhost:3000/compte/parametres"); + + // Delete account form + cy.contains("button", "Supprimer mon compte").click(); + cy.getByLabel( + "Pour confirmer la suppression de votre compte veuillez saisir : je confirme vouloir supprimer mon compte", + ).type("je confirme vouloir supprimer mon compte"); + cy.getByLabel("Mot de passe").type(password); + cy.contains("button", "Supprimer mon compte").click(); + cy.contains( + "Vous avez été déconnecté et votre compte a été supprimé avec succès", + ); + + // Submit feedback form + cy.getByLabel( + "Pourriez-vous nous donner la raison de votre départ ?", + ).type("Quoi ? Ça n’est pas un outil d’audit automatique ?!"); + cy.contains("button", "Envoyer mon avis").click(); + cy.contains("Votre avis a bien été envoyé"); + }); + }); + }); + + describe("Audits list", () => { + // Create an logged in account and 4 associated audits (1 completed, 1 pristine and 2 in progress) + beforeEach(() => { + cy.createTestAccount({ login: true }).then(({ username }) => { + cy.createTestAudit({ auditorEmail: username, isComplete: true }); + cy.createTestAudit({ auditorEmail: username, isPristine: true }); + cy.createTestAudit({ auditorEmail: username }); + cy.createTestAudit({ auditorEmail: username }).as("audit"); + cy.visit("http://localhost:3000/compte"); + }); + }); + it("User can access all their audits", () => { + cy.contains("En cours (3)"); + cy.contains("Terminé (1)"); + + const expectedLabels = [ + "Continuer l’audit", + "Continuer l’audit", + "Commencer l’audit", + "Voir le rapport pour l’audit", + ]; + + cy.get(".audit-main-action").each(($el, i) => { + expect($el.text()).to.contain(expectedLabels[i]); + }); + }); + + it("User can start audit with prefilled infos", () => { + // FIXME: export function? + function fillPageField( + pageIndex: number, + field: string, + content: string, + ) { + cy.contains("Page " + pageIndex) + .parent() + .parent() + .contains(field) + .parent() + .find("input") + .clear() + .type(content); + } + + // Create a new audit but auditor email field should not be present + cy.contains("a", "Démarrer un nouvel audit").click(); + + cy.contains("106 critères").click(); + + cy.contains("Nom du site à auditer") + .parent() + .find("input") + .type(auditJson.procedureName); + + cy.contains("button", "Étape suivante").click(); + cy.contains("button", "Ajouter une page").click(); + + fillPageField(1, "Nom de la page", auditJson.pages[0].name); + fillPageField(1, "URL de la page", auditJson.pages[0].url); + fillPageField(2, "Nom de la page", auditJson.pages[1].name); + fillPageField(2, "URL de la page", auditJson.pages[1].url); + fillPageField(3, "Nom de la page", auditJson.pages[2].name); + fillPageField(3, "URL de la page", auditJson.pages[2].url); + fillPageField(4, "Nom de la page", auditJson.pages[3].name); + fillPageField(4, "URL de la page", auditJson.pages[3].url); + fillPageField(5, "Nom de la page", auditJson.pages[4].name); + fillPageField(5, "URL de la page", auditJson.pages[4].url); + fillPageField(6, "Nom de la page", auditJson.pages[5].name); + fillPageField(6, "URL de la page", auditJson.pages[5].url); + fillPageField(7, "Nom de la page", auditJson.pages[6].name); + fillPageField(7, "URL de la page", auditJson.pages[6].url); + fillPageField(8, "Nom de la page", auditJson.pages[7].name); + fillPageField(8, "URL de la page", auditJson.pages[7].url); + + cy.contains("button", "Étape suivante").click(); + + cy.getByLabel("Prénom et nom (optionnel)").type(auditJson.auditorName); + + cy.contains("button", "Valider les paramètres").click(); + + cy.contains("h1", "Audit de mon petit site"); + + // Create new audit but in only 2 steps (step 3 auto-filled) + cy.contains("a", "Mes audits").click(); + + cy.contains("a", "Démarrer un nouvel audit").click(); + + cy.contains("106 critères").click(); + + cy.contains("Nom du site à auditer") + .parent() + .find("input") + .type(auditJson.procedureName); + + cy.contains("button", "Étape suivante").click(); + cy.contains("button", "Ajouter une page").click(); + + fillPageField(1, "Nom de la page", auditJson.pages[0].name); + fillPageField(1, "URL de la page", auditJson.pages[0].url); + fillPageField(2, "Nom de la page", auditJson.pages[1].name); + fillPageField(2, "URL de la page", auditJson.pages[1].url); + fillPageField(3, "Nom de la page", auditJson.pages[2].name); + fillPageField(3, "URL de la page", auditJson.pages[2].url); + fillPageField(4, "Nom de la page", auditJson.pages[3].name); + fillPageField(4, "URL de la page", auditJson.pages[3].url); + fillPageField(5, "Nom de la page", auditJson.pages[4].name); + fillPageField(5, "URL de la page", auditJson.pages[4].url); + fillPageField(6, "Nom de la page", auditJson.pages[5].name); + fillPageField(6, "URL de la page", auditJson.pages[5].url); + fillPageField(7, "Nom de la page", auditJson.pages[6].name); + fillPageField(7, "URL de la page", auditJson.pages[6].url); + fillPageField(8, "Nom de la page", auditJson.pages[7].name); + fillPageField(8, "URL de la page", auditJson.pages[7].url); + + cy.contains("button", "Valider les paramètres").click(); + + cy.contains("h1", "Audit de mon petit site"); + }); + + it("User can duplicate audit", () => { + cy.contains("button", "Options").click(); + cy.contains("button", "Dupliquer l’audit").click(); + + cy.getByLabel("Nom de la copie").type("Audit de mon petit site (2)"); + cy.get("dialog").contains("button", "Dupliquer l’audit").click(); + + cy.contains("Audit Audit de mon petit site (2) dupliqué avec succès", { + timeout: 50_000, + }); + }); + + it("User can copy audit link", () => { + cy.contains("button", "Options").click(); + cy.contains("button", "Copier le lien de l’audit").click(); + cy.get("@audit").then((audit) => { + cy.assertClipboardValue( + `http://localhost:3000/audits/${audit.editId}/generation`, + ); + cy.contains( + "Le lien vers l’audit a bien été copié dans le presse-papier.", + ); + }); + }); + + it("User can copy report link", () => { + cy.contains("button", "Options").click(); + cy.contains("button", "Copier le lien du rapport").click(); + cy.get("@audit").then((audit) => { + cy.assertClipboardValue( + `http://localhost:3000/rapport/${audit.reportId}`, + ); + cy.contains( + "Le lien vers le rapport a bien été copié dans le presse-papier.", + ); + }); + }); + + it("User can download audit", () => { + cy.exec("rm -rf cypress/downloads"); + + cy.contains("Options").click(); + cy.contains("Télécharger l’audit").click(); + + cy.readFile("cypress/downloads/audit-audit-de-mon-petit-site.csv"); + }); + + it("User can delete audit", () => { + cy.contains("Options").click(); + cy.contains("Supprimer l’audit").click(); + + cy.get("dialog").contains("Supprimer l’audit").click(); + + cy.contains("Audit Audit de mon petit site supprimé avec succès"); + }); + }); }); diff --git a/cypress/e2e/audit.cy.ts b/cypress/e2e/audit.cy.ts index 12f2f722..b75b922c 100644 --- a/cypress/e2e/audit.cy.ts +++ b/cypress/e2e/audit.cy.ts @@ -1,4 +1,5 @@ import * as auditJson from "../fixtures/audit.json"; +import * as statementJson from "../fixtures/statement.json"; describe("Audit", () => { it("User can create an audit", () => { @@ -9,45 +10,55 @@ describe("Audit", () => { .contains(field) .parent() .find("input") + .clear() .type(content); } cy.visit("http://localhost:3000"); // Navigate to new audit page - cy.contains("Je démarre un audit").click(); + cy.contains("a", "Je réalise un audit").click(); // Fill fields - cy.contains("Complet, de conformité").click(); + cy.contains("106 critères").click(); cy.contains("Nom du site à auditer") .parent() .find("input") .type(auditJson.procedureName); + cy.contains("button", "Étape suivante").click(); + cy.contains("button", "Ajouter une page").click(); + fillPageField(1, "Nom de la page", auditJson.pages[0].name); fillPageField(1, "URL de la page", auditJson.pages[0].url); - fillPageField(2, "Nom de la page", auditJson.pages[1].name); fillPageField(2, "URL de la page", auditJson.pages[1].url); + fillPageField(3, "Nom de la page", auditJson.pages[2].name); + fillPageField(3, "URL de la page", auditJson.pages[2].url); + fillPageField(4, "Nom de la page", auditJson.pages[3].name); + fillPageField(4, "URL de la page", auditJson.pages[3].url); + fillPageField(5, "Nom de la page", auditJson.pages[4].name); + fillPageField(5, "URL de la page", auditJson.pages[4].url); + fillPageField(6, "Nom de la page", auditJson.pages[5].name); + fillPageField(6, "URL de la page", auditJson.pages[5].url); + fillPageField(7, "Nom de la page", auditJson.pages[6].name); + fillPageField(7, "URL de la page", auditJson.pages[6].url); + fillPageField(8, "Nom de la page", auditJson.pages[7].name); + fillPageField(8, "URL de la page", auditJson.pages[7].url); - // Delete empty pages "Supprimer" buttons - cy.get('[data-cy="delete"]').eq(2).click(); - cy.get('[data-cy="delete"]').eq(2).click(); - cy.get('[data-cy="delete"]').eq(2).click(); - cy.get('[data-cy="delete"]').eq(2).click(); - cy.get('[data-cy="delete"]').eq(2).click(); + cy.contains("button", "Étape suivante").click(); // Fill auditor informations - cy.contains("Prénom et nom (optionnel)") + cy.contains("Adresse e-mail") .parent() .find("input") - .type(auditJson.procedureAuditorName); + .type(auditJson.auditorEmail); - cy.contains("Adresse e-mail") + cy.contains("Prénom et nom (optionnel)") .parent() .find("input") - .type(auditJson.procedureAuditorEmail); + .type(auditJson.auditorName); // Submit new audit form cy.contains("Valider les paramètres").click(); @@ -55,13 +66,478 @@ describe("Audit", () => { // Check user is redirect to audit overview page cy.get("h1").contains(auditJson.procedureName); }); - // it.skip("User can fill an audit (status, description, recommendation, image, impact, easy to fix)", () => {}); - // it.skip("User can check Markdown syntax", () => {}); - // it.skip("User can complete a11y statement", () => {}); - // it.skip("User can update notes", () => {}); - // it.skip("User can filter criteria", () => {}); - // it.skip("User can search criteria", () => {}); - // it.skip("User can copy an audit", () => {}); - // it.skip("User can delete an audit", () => {}); - // it.skip("User can downlaod an audit", () => {}); + + it("User can go to settings page from audit (small viewport)", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.contains("Actions").click(); + cy.contains("Accéder aux paramètres").click(); + cy.get("h1").contains("Paramètres de l’audit"); + cy.url().should( + "eq", + `http://localhost:3000/audits/${editId}/parametres`, + ); + }); + }); + + it("User can go to settings page from audit (large viewport)", () => { + cy.createTestAudit().then(({ editId }) => { + cy.viewport(1400, 800); + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.contains("a", "Paramètres").click(); + cy.get("h1").contains("Paramètres de l’audit"); + cy.url().should( + "eq", + `http://localhost:3000/audits/${editId}/parametres`, + ); + }); + }); + + it("User can edit procedure name", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/parametres`); + + cy.getByLabel("Nom du site audité").should( + "have.value", + "Audit de mon petit site", + ); + + cy.getByLabel("Nom du site audité") + .clear() + .type("Audit de mon gros site"); + + cy.contains("Enregistrer les modifications").click(); + + cy.url().should( + "eq", + `http://localhost:3000/audits/${editId}/generation`, + ); + cy.get("h1").contains("Audit de mon gros site"); + }); + }); + + it("User can edit pages", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/parametres`); + + cy.get("fieldset .fr-input-group .fr-input[id^='page-name']").then( + (els) => { + expect(els).to.have.length(8); + const expectedPages = [ + "Accueil", + "Contact", + "À propos", + "Blog", + "Article", + "Connexion", + "Documentation", + "FAQ", + ]; + expectedPages.forEach((expectedPageName, i) => { + cy.wrap(els[i]).should("have.value", expectedPageName); + }); + }, + ); + + cy.contains("button", "Supprimer").click(); + cy.contains("button", "Supprimer").click(); + cy.contains("button", "Supprimer").click(); + + cy.getByLabel("Nom de la page").clear().type("Accueil du blog"); + cy.getByLabel("URL de la page") + .clear() + .type("https://example.com/blog/accueil"); + + cy.get("#page-order-1").select("0"); + + cy.get("#page-order-4").select("1"); + + cy.contains("Ajouter une page").click(); + cy.get("fieldset .fr-input-group .fr-input[id^='page-name']") + .last() + .type("Paramètres"); + cy.get("fieldset .fr-input-group .fr-input[id^='page-url']") + .last() + .type("https://example.com/parametres"); + + cy.contains("Enregistrer les modifications").click(); + cy.url().should( + "eq", + `http://localhost:3000/audits/${editId}/generation`, + ); + + cy.get("[role='tablist'] button").then((els) => { + expect(els).to.have.length(7); + const expectedPages = [ + "Éléments transverses", + "Article", + "FAQ", + "Accueil du blog", + "Connexion", + "Documentation", + "Paramètres", + ]; + expectedPages.forEach((expectedPageName, i) => { + cy.wrap(els[i]).should("have.text", expectedPageName); + }); + }); + }); + }); + + it("User can delete an audit", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains("Actions").click(); + cy.contains("Supprimer l’audit").click(); + + cy.contains("Vous allez supprimer l’audit"); + cy.get("dialog").contains("button", "Supprimer l’audit").click(); + + cy.url().should("eq", "http://localhost:3000/"); + cy.contains("L’audit a correctement été supprimé."); + }); + }); + + it("User can update notes", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains("Annoter l’audit").click(); + cy.getByLabel("Remarques et recommandations générales") + .clear() + .type("Annotations de l’audit"); + cy.get("dialog#notes-modal").contains("button", "Fermer").click(); + cy.contains("Annoter l’audit").click(); + cy.contains("Annotations de l’audit"); + }); + }); + + it("User can fill a11y statement", () => { + cy.createTestAudit().then(({ editId, reportId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/synthese`); + cy.contains("Compléter").invoke("removeAttr", "target").click(); + + // General infos + cy.getByLabel("Entité qui a demandé l’audit") + .clear() + .type(statementJson.auditInitiator); + cy.getByLabel("Entité qui a réalisé l’audit") + .clear() + .type(statementJson.auditorOrganisation); + cy.getByLabel("URL de la page d’accueil du site audité") + .clear() + .type(statementJson.procedureUrl); + + // Contact + cy.getByLabel("Nom et prénom du contact (optionnel)") + .clear() + .type(statementJson.contactName); + cy.getByLabel("Adresse e-mail").clear().type(statementJson.contactEmail); + cy.getByLabel("Formulaire de contact en ligne") + .clear() + .type(statementJson.contactFormUrl); + + // Technologies + cy.getByLabel("Ajouter des technologies") + .clear() + .type(statementJson.technologies); + + cy.contains("button", "Valider les technologies").click(); + + // Tools + cy.contains("Web Developer Toolbar").click(); + cy.contains("WCAG Contrast checker").click(); + cy.getByLabel("Ajouter des outils d’assistance") + .clear() + .type(statementJson.tools); + + cy.contains("button", "Valider les outils").click(); + + // Environments + cy.contains("Combinaison 1").click(); + cy.getByLabel("Appareil").clear().type("Ordinateur"); + cy.getByLabel("Logiciel d’exploitation").clear().type("Windows"); + cy.getByLabel("Technologie d’assistance").clear().type("JAWS"); + cy.getByLabel("Navigateur").clear().type("Edge"); + + // Not accessible content + cy.getByLabel("Non-conformités (optionnel)") + .clear() + .type(statementJson.notCompliantContent); + cy.getByLabel("Dérogations pour charge disproportionnée (optionnel)") + .clear() + .type(statementJson.derogatedContent); + cy.getByLabel( + "Contenus non soumis à l’obligation d’accessibilité, contenus tiers (optionnel)", + ) + .clear() + .type(statementJson.notInScopeContent); + + cy.contains("button", "Valider la déclaration").click(); + cy.contains(`http://localhost:3000/declaration/${reportId}`); + }); + }); + + it("User can copy an audit", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.contains("button", "Actions").click(); + cy.contains("button", "Dupliquer l’audit").click(); + + cy.getByLabel("Nom de la copie").type("Audit de mon petit site (2)"); + cy.get("dialog").contains("button", "Dupliquer l’audit").click(); + + cy.contains("Audit dupliqué avec succès", { timeout: 50_000 }); + cy.contains("Audit de mon petit site (2)"); + }); + }); + + it("User can search in criteria title", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.getByLabel("Rechercher par mots clés") + .clear() + .type("alternative") + .type("{enter}"); + + cy.contains("9 résultats"); + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(9); + }); + }); + }); + + it("User can hide tests and references", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.contains("Masquer les tests et références").click(); + cy.contains("Tests et références du critère 1.1").should("not.exist"); + }); + }); + + it("User can filter criteria", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + // Check total length + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(106); + }); + + // Check C criteria + cy.contains("Conforme (323)").click(); + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(36); + }); + cy.get("li.criterium-container").each((el) => { + cy.wrap(el) + .find("fieldset > div label") + .first() + .should("have.class", "green"); + }); + + // Add NA criteria + cy.contains("Non applicable (315)").click(); + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(71); + }); + cy.get("li.criterium-container").each((el) => { + cy.wrap(el) + .find("fieldset > div label") + .should( + "satisfy", + (el) => + el[0].classList.contains("green") || el.at(-1).contains("grey"), + ); + }); + + // Add NC criteria + cy.contains("Non conforme (315)").click(); + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(106); + }); + cy.get("li.criterium-container").each((el) => { + cy.wrap(el) + .find("fieldset > div label") + .should( + "satisfy", + (el) => + el[0].classList.contains("green") || + el[1].contains("red") || + el.at(-1).contains("grey"), + ); + }); + }); + }); + + it("User can filter evaluated criteria", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains("Masquer critères évalués").click(); + cy.contains("Tous les critères évalués ont été masqués"); + + cy.contains("Masquer critères évalués").click(); + + cy.get("li.criterium-container fieldset input:checked") + .first() + .click({ force: true }); + + cy.get("li.criterium-container fieldset input:checked") + .eq(0) + .click({ force: true }); + + cy.contains('button[role="tab"]', "Article").click(); + + cy.get("li.criterium-container fieldset input:checked") + .first() + .click({ force: true }); + + cy.contains("Masquer critères évalués").click(); + cy.get("li.criterium-container").should("have.length", 1); + + cy.contains('button[role="tab"]', "Éléments transverses").click(); + cy.get("li.criterium-container").should("have.length", 2); + + cy.get("li.criterium-container fieldset input") + .first() + .click({ force: true }); + + cy.contains("Mettre à jour critères masqués").click(); + + cy.get("li.criterium-container").should("have.length", 1); + }); + }); + + it("User can finish the audit", () => { + cy.createTestAudit().then(({ editId, reportId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains('button[role="tab"]', "Accueil").click(); + + cy.get("li.criterium-container fieldset input") + .first() + .click({ force: true }); + + cy.contains(/Audit terminé le \d{2}\/\d{2}\/\d{4}/); + cy.contains("Bravo ! Il semblerait que vous ayez terminé votre audit 💪"); + + cy.visit(`http://localhost:3000/audits/${editId}/synthese`); + + cy.contains(/Terminé le \d{1,2} [A-zÀ-ú]{3,9} \d{4}/); + cy.contains("a", "Accéder"); + + cy.contains("a", `http://localhost:3000/rapport/${reportId}`); + + cy.contains("button", "Copier le lien").click(); + cy.contains( + "Le lien vers le rapport d’audit a bien été copié dans le presse-papier.", + ); + cy.assertClipboardValue(`http://localhost:3000/rapport/${reportId}`); + }); + }); + + it("User can set a topic as NA", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.contains('button[role="tab"]', "Connexion").click(); + cy.contains(" Non applicable sur la page").click(); + + cy.get(".page-url + section fieldset input:checked + label").should( + "have.class", + "grey", + ); + }); + }); + + it("User can edit a criterium content", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains('button[role="tab"]', "FAQ").click(); + cy.get(".criterium-container").contains("Non conforme").click(); + + cy.focused().should("have.attr", "rows"); + + cy.getByLabel(/^Erreur et recommandation$/) + .clear({ force: true }) + .type("Il n’y a pas de alt sur l’image du hero"); + + cy.get(".criterium-container").contains("majeur").click(); + cy.get(".criterium-container").contains("Facile à corriger").click(); + }); + }); + + it("User can see transverse status and comment from other pages", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains('button[role="tab"]', "Documentation").click(); + cy.get(".criterium-container") + .eq(2) + .find(".criterium-transverse-notice") + .contains( + "Vous avez évalué ce critère Non conforme pour les éléments transverses.", + ); + + cy.get(".criterium-container") + .eq(2) + .contains("button", "Voir les erreurs") + .click(); + + cy.get(".criterium-container").eq(2).contains("Une erreur ici"); + + cy.get(".criterium-container") + .eq(2) + .contains("button", "Masquer les erreurs") + .click(); + + cy.get(".criterium-container") + .eq(2) + .contains("Une erreur ici") + .should("not.exist"); + }); + }); + + it("User can download an audit", () => { + cy.exec("rm -rf cypress/downloads"); + + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains("Actions").click(); + cy.contains("Exporter l’audit").click(); + + cy.readFile("cypress/downloads/audit-audit-de-mon-petit-site.csv"); + }); + }); + + it("User can reset filters", () => { + cy.createTestAudit().then(({ editId }) => { + cy.visit(`http://localhost:3000/audits/${editId}/generation`); + + cy.contains("Conforme (323)").click(); + cy.contains("Non applicable (315)").click(); + cy.contains("Masquer les tests et références").click(); + cy.getByLabel(" Rechercher par mots clés") + .clear() + .type("alternative") + .type("{enter}"); + + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(6); + }); + + cy.contains("button", "Réinitialiser").click(); + + cy.get("li.criterium-container").then((els) => { + expect(els).to.have.length(106); + }); + + cy.focused().should("have.attr", "placeholder", "Rechercher"); + cy.contains("button", "Réinitialiser").should("not.exist"); + }); + }); }); diff --git a/cypress/e2e/report.cy.ts b/cypress/e2e/report.cy.ts index a009eb95..4cf8d2f4 100644 --- a/cypress/e2e/report.cy.ts +++ b/cypress/e2e/report.cy.ts @@ -1,4 +1,173 @@ +import * as auditJson from "../fixtures/audit.json"; +import * as statementJson from "../fixtures/statement.json"; + +// FIXME: weird behaviour with page reloads (caused by "fake tabs"?) describe("Report", () => { - // it.skip("User can filter errors", () => {}); - // it.skip("User can download results", () => {}); + it("User can see audit in progress banner for in progress audit", () => { + cy.createTestAudit().then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.contains( + "Les résultats de ce rapport sont provisoires tant que l’audit n'est pas terminé.", + ); + }); + }); + + it("User can't see audit in progress banner for completed audit", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.get(".header").contains(auditJson.procedureName); + cy.contains( + "Les résultats de ce rapport sont provisoires tant que l’audit n'est pas terminé.", + ).should("not.exist"); + }); + }); + + it("User can see audit header infos", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.get(".header").contains(auditJson.procedureName); + cy.get(".header").contains(`URL du site : ${statementJson.procedureUrl}`); + cy.get(".header").contains("Type d’audit : 106 critères"); + cy.get(".header").contains( + `Auditeur ou auditrice : ${auditJson.auditorName}`, + ); + }); + }); + + it("User can copy report URL", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.contains("button", "Copier le lien du rapport").click(); + cy.assertClipboardValue(`http://localhost:3000/rapport/${reportId}`); + cy.contains( + "Le lien vers le rapport a bien été copié dans le presse-papier.", + ); + }); + }); + + it("User can download results", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.contains("button", "Télécharger").click(); + cy.contains("a", "Télécharger l'audit").click(); + + cy.readFile("cypress/downloads/audit.csv"); + }); + }); + + it("User can see correct pages count and transverse criteria count", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.get("#repartition-des-criteres-par-pages + .fr-table tbody tr").then( + (els) => { + expect(els).to.have.length(8); + }, + ); + + cy.contains( + "35 critères non conformes concernent des éléments transverses à toutes les pages de l’échantillon.", + ); + }); + }); + + it("User can’t see improvements tab if there are no improvements with comment", () => { + cy.createTestAudit({ + isComplete: true, + hasNoImprovementsComments: true, + }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.get(".header").contains(auditJson.procedureName); + cy.contains("button", "Points d’amélioration").should("not.exist"); + }); + }); + + it("User can see pages anchors in improvements tab", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.contains("button", "Détails des non-conformités").click(); + cy.get("#tabpanel-points-damelioration-panel .fr-sidemenu__item").then( + (els) => { + expect(els).to.have.length(9); + }, + ); + }); + }); + + it("User can see pages anchors in errors tab", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.contains("button", "Détails des non-conformités").click(); + cy.get( + "#tabpanel-details-des-non-conformites-panel .fr-sidemenu__item", + ).then((els) => { + expect(els).to.have.length(9); + }); + }); + }); + + it("User can filter errors", () => { + cy.createTestAudit({ isComplete: true }).then(({ reportId }) => { + cy.visit(`http://localhost:3000/rapport/${reportId}`); + cy.contains("button", "Détails des non-conformités").click(); + cy.contains("315 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).then((els) => { + expect(els).to.have.length(315); + }); + + // Check & uncheck easy to fix + cy.contains("Uniquement les erreurs faciles à corriger").click(); + cy.contains("45 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).then((els) => { + expect(els).to.have.length(45); + }); + cy.contains("Uniquement les erreurs faciles à corriger").click(); + + // Uncheck minor impact + cy.contains("Mineur (81)").click(); + cy.contains("234 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).then((els) => { + expect(els).to.have.length(234); + }); + + // Uncheck major impact + cy.contains("Majeur (81)").click(); + cy.contains("153 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).then((els) => { + expect(els).to.have.length(153); + }); + + // Uncheck blocking impact + cy.contains("Bloquant (81)").click(); + cy.contains("72 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).then((els) => { + expect(els).to.have.length(72); + }); + + // Uncheck not specified impact + cy.contains("Impact non renseigné (72)").click(); + cy.contains("0 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).should("not.exist"); + + // Reset filters + cy.contains("button", "Réinitialiser les filtres").click(); + cy.contains("315 résultats"); + cy.get( + "#tabpanel-details-des-non-conformites-panel .criterium-title", + ).then((els) => { + expect(els).to.have.length(315); + }); + }); + }); }); diff --git a/cypress/examples/1-getting-started/todo.cy.js b/cypress/examples/1-getting-started/todo.cy.js deleted file mode 100644 index 4768ff92..00000000 --- a/cypress/examples/1-getting-started/todo.cy.js +++ /dev/null @@ -1,143 +0,0 @@ -/// - -// Welcome to Cypress! -// -// This spec file contains a variety of sample tests -// for a todo list app that are designed to demonstrate -// the power of writing tests in Cypress. -// -// To learn more about how Cypress works and -// what makes it such an awesome testing tool, -// please read our getting started guide: -// https://on.cypress.io/introduction-to-cypress - -describe('example to-do app', () => { - beforeEach(() => { - // Cypress starts out with a blank slate for each test - // so we must tell it to visit our website with the `cy.visit()` command. - // Since we want to visit the same URL at the start of all our tests, - // we include it in our beforeEach function so that it runs before each test - cy.visit('https://example.cypress.io/todo') - }) - - it('displays two todo items by default', () => { - // We use the `cy.get()` command to get all elements that match the selector. - // Then, we use `should` to assert that there are two matched items, - // which are the two default items. - cy.get('.todo-list li').should('have.length', 2) - - // We can go even further and check that the default todos each contain - // the correct text. We use the `first` and `last` functions - // to get just the first and last matched elements individually, - // and then perform an assertion with `should`. - cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') - cy.get('.todo-list li').last().should('have.text', 'Walk the dog') - }) - - it('can add new todo items', () => { - // We'll store our item text in a variable so we can reuse it - const newItem = 'Feed the cat' - - // Let's get the input element and use the `type` command to - // input our new list item. After typing the content of our item, - // we need to type the enter key as well in order to submit the input. - // This input has a data-test attribute so we'll use that to select the - // element in accordance with best practices: - // https://on.cypress.io/selecting-elements - cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) - - // Now that we've typed our new item, let's check that it actually was added to the list. - // Since it's the newest item, it should exist as the last element in the list. - // In addition, with the two default items, we should have a total of 3 elements in the list. - // Since assertions yield the element that was asserted on, - // we can chain both of these assertions together into a single statement. - cy.get('.todo-list li') - .should('have.length', 3) - .last() - .should('have.text', newItem) - }) - - it('can check off an item as completed', () => { - // In addition to using the `get` command to get an element by selector, - // we can also use the `contains` command to get an element by its contents. - // However, this will yield the