From 1eb944c99704d437ff46187e939a2088ba88cb4c Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Thu, 14 Nov 2024 11:09:31 +0100 Subject: [PATCH 01/10] created import page and spec and use page returns --- docs/struktur.md | 51 ++++++++++++++++++---------- pages/LandingView.page.ts | 2 +- pages/LoginView.page.ts | 4 +-- pages/MenuBar.page.ts | 8 +++++ pages/StartView.page.ts | 2 +- pages/admin/PersonImportView.page.ts | 14 ++++++++ tests/Import.spec.ts | 45 ++++++++++++++++++++++++ tests/Rolle.spec.ts | 10 +++--- tests/Schule.spec.ts | 8 ++--- 9 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 pages/admin/PersonImportView.page.ts create mode 100644 tests/Import.spec.ts diff --git a/docs/struktur.md b/docs/struktur.md index 632a822..28d5757 100644 --- a/docs/struktur.md +++ b/docs/struktur.md @@ -1,9 +1,7 @@ # Struktur der Playwright-Tests - In diesem Dokument wird beschrieben, wie wir unsere Playwright-Tests strukturieren. ## Zielsetzung - Das Ziel bei unseren Tests ist es, die Benutzerinteraktionen so abstrakt wie möglich zu beschreiben. Das heißt zum Beispiel, dass wir nicht in jedem Test erneut beschreiben wollen, wie man eine ComboBox bedient. Stattdessen soll der Test lediglich fordern, dass aus einer ComboBox ein Eintrag ausgewählt wird. @@ -19,31 +17,48 @@ Tests werden parallel ausgeführt, wenn sie in verschiedenen Dateien stehen. Es ist daher ratsam, Testdateien klein zu halten und entsprechend sinnvoll zu schneiden. ## Technische Umsetzung - ### Verzeichnisstruktur - -`/base`: Helper für Tests - -`/elements`: wiederkehrende Seitenelemente (TODO: über Umbenennung in "components" nachdenken) - -`/pages`: Seitenobjekte - -`/tests`: Die eigentlichen Tests - -### Verzeichnis `base` +#### Verzeichnis `base`: Helper für Tests Im `base`-Verzeichnis befinden sich Helper, die von den Tests aufgerufen werden aber nicht direkt zu den Tests gehören. Darunter fällt zum Beispiel die Erzeugung von Testdaten direkt über API. -### Verzeichnis `elements` -In Elements befinden sich semantische Wrapper um Locators, die wiederkehrende Elemente auf den Seiten testen. +#### Verzeichnis `elements`: wiederkehrende Seitenelemente (TODO: über Umbenennung in "components" nachdenken) +In `elements` befinden sich semantische Wrapper um Locators, die wiederkehrende Elemente auf den Seiten testen. Beispielsweise legen wir hier eine Klasse für "Comboboxen" ab, die die nötigen Schritte zur Auswahl von Elementen kapselt. -### Verzeichnis `pages` +#### Verzeichnis `pages`: Seitenobjekte Im Verzeichnis `pages` liegen Seitenrepräsentationen. Eine Seite hat dabei high-level-Funktionen, zur Navigation und zum Aufruf von Seitenfunktionalitäten. -### Tags +#### Verzeichnis `/tests`: Die eigentlichen Tests +### Tags Wir verwenden Tags, um diejenigen Tests auszuwählen, die ausgeführt werden sollen. Zur Zeit unterscheiden wir nach Ausführungslänge (@long, @short). -Das muss aber nicht die einzige Unterscheidungskategorie bleiben. \ No newline at end of file +Das muss aber nicht die einzige Unterscheidungskategorie bleiben. + +### Return Pages statt Import +Für die Verwendung von mehreren Pages in den einzelnen Tests müssen sie nicht in jedem Test importiert und einzeln navigiert werden. Stattdessen ist es sinnvoll, verlinkte Pages in dedizierten Funktionen der einzelnen Pages zu returnen und die Funktionen im Test aufzurufen. Das spart Code und Wartungsaufwand. + +#### Beispiel +``` +// tests/Import.spec.ts +import { LandingPage } from "../pages/LandingView.page"; + +const loginPage = await landingPage.goToLogin(); +const startPage = await loginPage.login(ADMIN, PW); +const menuPage = await startPage.goToAdministration(); +const importPage = await menuPage.goToBenutzerImport(); + +// pages/LandingView.page.ts +public async goToLogin(): Promise { + await this.button_Anmelden.click(); + return new LoginPage(this.page); +} + +// pages/StartView.page.ts +public async goToAdministration(): Promise { + await this.card_item_schulportal_administration.click(); + return new MenuPage(this.page); +} +``` \ No newline at end of file diff --git a/pages/LandingView.page.ts b/pages/LandingView.page.ts index 6b4e49e..2c6a632 100644 --- a/pages/LandingView.page.ts +++ b/pages/LandingView.page.ts @@ -12,7 +12,7 @@ export class LandingPage { this.button_Anmelden = page.getByTestId("login-button"); } - public async login(): Promise { + public async goToLogin(): Promise { await this.button_Anmelden.click(); return new LoginPage(this.page); } diff --git a/pages/LoginView.page.ts b/pages/LoginView.page.ts index 3290465..479219d 100644 --- a/pages/LoginView.page.ts +++ b/pages/LoginView.page.ts @@ -32,8 +32,8 @@ export class LoginPage { } async login( - username = process.env.USER, - password = process.env.PW, + username: string = process.env.USER as string, + password: string = process.env.PW as string, ): Promise { await expect(this.text_h1).toBeVisible(); await this.input_username.click(); diff --git a/pages/MenuBar.page.ts b/pages/MenuBar.page.ts index 1146d07..37fc495 100644 --- a/pages/MenuBar.page.ts +++ b/pages/MenuBar.page.ts @@ -3,6 +3,7 @@ import { RolleCreationViewPage } from "./admin/RolleCreationView.page"; import {RolleManagementViewPage} from "./admin/RolleManagementView.page"; import { SchuleManagementViewPage } from "./admin/SchuleManagementView.page"; import { SchuleCreationViewPage } from "./admin/SchuleCreationView.page"; +import { PersonImportViewPage } from "./admin/PersonImportView.page"; export class MenuPage { readonly page: Page; @@ -21,6 +22,7 @@ export class MenuPage { readonly menueItem_AlleSchulenAnzeigen: Locator; readonly menueItem_SchuleAnlegen: Locator; readonly label_Schultraegerverwaltung: Locator; + readonly menuItemBenutzerImportieren: Locator; constructor(page) { this.page = page; @@ -37,6 +39,7 @@ export class MenuPage { this.menueItem_BenutzerAnlegen = page.getByTestId( "person-creation-menu-item", ); + this.menuItemBenutzerImportieren = page.getByTestId("person-import-menu-item"); this.label_Klassenverwaltung = page.locator( '[data-testid="klasse-management-title"] .v-list-item-title', ); @@ -86,4 +89,9 @@ export class MenuPage { await this.menueItem_SchuleAnlegen.click(); return new SchuleCreationViewPage(this.page); } + + public async goToBenutzerImport(): Promise { + await this.menuItemBenutzerImportieren.click(); + return new PersonImportViewPage(this.page); + } } diff --git a/pages/StartView.page.ts b/pages/StartView.page.ts index a10141a..e2f4d22 100644 --- a/pages/StartView.page.ts +++ b/pages/StartView.page.ts @@ -21,7 +21,7 @@ export class StartPage { this.card_item_schulportal_administration = page.locator('[data-testid^="service-provider-card"]', { hasText: "Schulportal-Administration" }); } - public async administration(): Promise { + public async goToAdministration(): Promise { await this.card_item_schulportal_administration.click(); return new MenuPage(this.page); } diff --git a/pages/admin/PersonImportView.page.ts b/pages/admin/PersonImportView.page.ts new file mode 100644 index 0000000..9624ef0 --- /dev/null +++ b/pages/admin/PersonImportView.page.ts @@ -0,0 +1,14 @@ +import { type Locator, Page } from '@playwright/test'; + +export class PersonImportViewPage { + readonly page: Page; + readonly body: Locator; + readonly headlineBenutzerImport: Locator; + + constructor(page) { + // Benutzerimport + this.page = page; + this.body = page.locator('body'); + this.headlineBenutzerImport = page.getByTestId('layout-card-headline'); + } +} \ No newline at end of file diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts new file mode 100644 index 0000000..4a5c9de --- /dev/null +++ b/tests/Import.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from "@playwright/test"; +import { LandingPage } from "../pages/LandingView.page"; +import { HeaderPage } from "../pages/Header.page"; +import { LONG } from "../base/tags"; + +const PW = process.env.PW; +const ADMIN = process.env.USER; +const FRONTEND_URL = process.env.FRONTEND_URL || ""; + +test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGEBUNG}: URL: ${process.env.FRONTEND_URL}:`, () => { + test.beforeEach(async ({ page }) => { + await test.step(`Login and navigate to Benutzerimport`, async () => { + const landingPage = new LandingPage(page); + + await page.goto(FRONTEND_URL); + const loginPage = await landingPage.goToLogin(); + const startPage = await loginPage.login(ADMIN, PW); + const menuPage = await startPage.goToAdministration(); + const importPage = await menuPage.goToBenutzerImport(); + + await expect(importPage.headlineBenutzerImport).toBeVisible(); + }); + }); + + test.afterEach(async ({ page }) => { + await test.step(`Testdaten löschen via API`, async () => { + // delete test data + }); + + await test.step(`Abmelden`, async () => { + const header = new HeaderPage(page); + await header.logout(); + }); + }); + + test("Als Landesadmin eine CSV-Datei mit Benutzerdaten hochladen und importieren", {tag: [LONG]}, async ({ page }) => { + const schulname = ""; + const rollenname = ""; + const csvFile = ""; + + await test.step(``, async () => { + // upload CSV file + }); + }); +}); \ No newline at end of file diff --git a/tests/Rolle.spec.ts b/tests/Rolle.spec.ts index 6d5b13b..890be48 100644 --- a/tests/Rolle.spec.ts +++ b/tests/Rolle.spec.ts @@ -18,7 +18,7 @@ test.beforeEach(async ({ page }) => { startseite = await test.step(`Login`, async () => { const startPage = await FromAnywhere(page) .start() - .then((landing) => landing.login()) + .then((landing) => landing.goToLogin()) .then((login) => login.login()); loggedIn = true; return startPage; @@ -67,7 +67,7 @@ test.describe(`Testfälle für die Administration von Rollen: Umgebung: ${proces const rolleCreationView = await test.step(`Dialog Rolle anlegen öffnen`, async () => { const rolleCreationView = await startseite - .administration() + .goToAdministration() .then((menu) => menu.rolleAnlegen()); await expect(rolleCreationView.text_h2_RolleAnlegen).toHaveText( "Neue Rolle hinzufügen", @@ -124,7 +124,7 @@ test.describe(`Testfälle für die Administration von Rollen: Umgebung: ${proces test("Ergebnisliste Rollen auf Vollständigkeit prüfen als Landesadmin", {tag: [LONG, SHORT, STAGE]}, async () => { await test.step(`Rollenverwaltung öffnen und alle Elemente in der Ergebnisliste auf Existenz prüfen`, async () => { - const menu: MenuPage = await startseite.administration(); + const menu: MenuPage = await startseite.goToAdministration(); const rolleManagement = await menu.alleRollenAnzeigen(); await expect( rolleManagement.text_h1_Administrationsbereich, @@ -155,7 +155,7 @@ test.describe(`Testfälle für die Administration von Rollen: Umgebung: ${proces const rolleCreationView = await test.step(`Dialog Rolle anlegen öffnen`, async () => { return await startseite - .administration() + .goToAdministration() .then((menu) => menu.rolleAnlegen()); }); @@ -243,7 +243,7 @@ test.describe("Testet die Anlage einer neuen Rolle", () => { const rolleCreationView: RolleCreationViewPage = await test.step("Rolle anlegen aufrufen", async () => { return await startseite - .administration() + .goToAdministration() .then((menu) => menu.rolleAnlegen()); }); diff --git a/tests/Schule.spec.ts b/tests/Schule.spec.ts index 3890459..4bf5750 100644 --- a/tests/Schule.spec.ts +++ b/tests/Schule.spec.ts @@ -85,7 +85,7 @@ test.describe(`Testfälle für die Administration von Schulen: Umgebung: ${proce const { menue, schuleCreationView }: { menue: MenuPage; schuleCreationView: SchuleCreationViewPage } = await test.step(`Dialog Schule anlegen öffnen`, async (): Promise<{ menue: MenuPage; schuleCreationView: SchuleCreationViewPage }> => { - const menue: MenuPage = await startseite.administration(); + const menue: MenuPage = await startseite.goToAdministration(); const schuleCreationView: SchuleCreationViewPage = await menue.schuleAnlegen(); await menue.menueItem_SchuleAnlegen.click(); await expect(schuleCreationView.text_h2_SchuleAnlegen).toHaveText("Neue Schule hinzufügen"); @@ -127,7 +127,7 @@ test.describe(`Testfälle für die Administration von Schulen: Umgebung: ${proce const startseite = new StartPage(page); await test.step(`Schulverwaltung öffnen und Alle Elemente in der Ergebnisliste auf Existenz prüfen`, async () => { - const menue: MenuPage = await startseite.administration(); + const menue: MenuPage = await startseite.goToAdministration(); const schuleManagementView: SchuleManagementViewPage = await menue.alleSchulenAnzeigen(); await expect(schuleManagementView.text_h1_Administrationsbereich).toBeVisible(); await expect(schuleManagementView.text_h2_Schulverwaltung).toBeVisible(); @@ -155,7 +155,7 @@ test.describe(`Testfälle für die Administration von Schulen: Umgebung: ${proce await addSystemrechtToRolle(page, userInfo.rolleId, 'SCHULEN_VERWALTEN'); await header.logout(); - const login = await landing.login(); + const login = await landing.goToLogin(); const startseite = await login.login(userInfo.username, userInfo.password); userInfo.password = await login.UpdatePW(); await expect(startseite.text_h2_Ueberschrift).toBeVisible(); @@ -163,7 +163,7 @@ test.describe(`Testfälle für die Administration von Schulen: Umgebung: ${proce }); const schuleCreationView = await test.step(`Dialog Schule anlegen öffnen als Schuladmin`, async () => { - const menue: MenuPage = await startseite.administration(); + const menue: MenuPage = await startseite.goToAdministration(); const schuleCreationView: SchuleCreationViewPage = await menue.schuleAnlegen(); return schuleCreationView; }); From 88c1f5cb7bdb9c50ebff81f82116d72fc8d59425 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Fri, 15 Nov 2024 10:28:26 +0100 Subject: [PATCH 02/10] improved docs --- README.md | 2 -- docs/struktur.md | 40 ++++++++++++++++------------------------ 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index efe9ad1..14d4c9d 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ Empfohlen wird VS-Code #### npx playwright codegen https://spsh.staging.spsh.dbildungsplattform.de -#### npx playwright codegen https://test.dev.spsh.dbildungsplattform.de - #### npx playwright codegen https://localhost:8099/ --ignore-https-errors ### Tests lokal ausführen: diff --git a/docs/struktur.md b/docs/struktur.md index 28d5757..87a8798 100644 --- a/docs/struktur.md +++ b/docs/struktur.md @@ -18,47 +18,39 @@ Es ist daher ratsam, Testdateien klein zu halten und entsprechend sinnvoll zu sc ## Technische Umsetzung ### Verzeichnisstruktur -#### Verzeichnis `base`: Helper für Tests +#### `base`: Helper für Tests Im `base`-Verzeichnis befinden sich Helper, die von den Tests aufgerufen werden aber nicht direkt zu den Tests gehören. Darunter fällt zum Beispiel die Erzeugung von Testdaten direkt über API. -#### Verzeichnis `elements`: wiederkehrende Seitenelemente (TODO: über Umbenennung in "components" nachdenken) +#### `elements`: wiederkehrende Seitenelemente (TODO: über Umbenennung in "components" nachdenken) In `elements` befinden sich semantische Wrapper um Locators, die wiederkehrende Elemente auf den Seiten testen. Beispielsweise legen wir hier eine Klasse für "Comboboxen" ab, die die nötigen Schritte zur Auswahl von Elementen kapselt. -#### Verzeichnis `pages`: Seitenobjekte +#### `fixtures`: Ablage von Testdaten +Das `fixtures`-Verzeichnis beinhaltet Testdaten, die als JSON oder in beliebigen anderen Formaten abgelegt werden können. + +#### `pages`: Seitenobjekte Im Verzeichnis `pages` liegen Seitenrepräsentationen. Eine Seite hat dabei high-level-Funktionen, zur Navigation und zum Aufruf von Seitenfunktionalitäten. -#### Verzeichnis `/tests`: Die eigentlichen Tests +#### `/tests`: Die eigentlichen Tests ### Tags Wir verwenden Tags, um diejenigen Tests auszuwählen, die ausgeführt werden sollen. Zur Zeit unterscheiden wir nach Ausführungslänge (@long, @short). Das muss aber nicht die einzige Unterscheidungskategorie bleiben. -### Return Pages statt Import +### Navigation mit FromAnywhere Für die Verwendung von mehreren Pages in den einzelnen Tests müssen sie nicht in jedem Test importiert und einzeln navigiert werden. Stattdessen ist es sinnvoll, verlinkte Pages in dedizierten Funktionen der einzelnen Pages zu returnen und die Funktionen im Test aufzurufen. Das spart Code und Wartungsaufwand. +Als Einstieg dient dazu die Klasse `FromAnywhere`, über die man von jeder Route aus die LandingPage aufrufen kann, um von dort aus weiter zu navigieren. + #### Beispiel ``` -// tests/Import.spec.ts -import { LandingPage } from "../pages/LandingView.page"; - -const loginPage = await landingPage.goToLogin(); -const startPage = await loginPage.login(ADMIN, PW); -const menuPage = await startPage.goToAdministration(); -const importPage = await menuPage.goToBenutzerImport(); - -// pages/LandingView.page.ts -public async goToLogin(): Promise { - await this.button_Anmelden.click(); - return new LoginPage(this.page); -} - -// pages/StartView.page.ts -public async goToAdministration(): Promise { - await this.card_item_schulportal_administration.click(); - return new MenuPage(this.page); -} +FromAnywhere +.start() +.goToLogin() +.login(ADMIN, PW) +.goToAdminstration() +.goToBenutzerimport(); ``` \ No newline at end of file From 019026442ba8b51f008d99b6d35df8c6d30675e2 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Fri, 15 Nov 2024 10:29:03 +0100 Subject: [PATCH 03/10] renamed helper file --- base/api/testHelperPerson.page.ts | 2 +- base/{roles.ts => rollen.ts} | 0 tests/Person.spec.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename base/{roles.ts => rollen.ts} (100%) diff --git a/base/api/testHelperPerson.page.ts b/base/api/testHelperPerson.page.ts index 025398d..2fb16b5 100644 --- a/base/api/testHelperPerson.page.ts +++ b/base/api/testHelperPerson.page.ts @@ -5,7 +5,7 @@ import { UserInfo } from "./testHelper.page"; import { HeaderPage } from '../../pages/Header.page'; import { LoginPage } from '../../pages/LoginView.page'; import { faker } from '@faker-js/faker'; -import { lehrkraftOeffentlichRolle } from '../roles'; +import { lehrkraftOeffentlichRolle } from '../rollen'; const FRONTEND_URL = process.env.FRONTEND_URL || ""; diff --git a/base/roles.ts b/base/rollen.ts similarity index 100% rename from base/roles.ts rename to base/rollen.ts diff --git a/tests/Person.spec.ts b/tests/Person.spec.ts index e6d846d..d764f23 100644 --- a/tests/Person.spec.ts +++ b/tests/Person.spec.ts @@ -14,7 +14,7 @@ import { UserInfo } from "../base/api/testHelper.page"; import { addSystemrechtToRolle } from "../base/api/testHelperRolle.page"; import { LONG, SHORT, STAGE } from "../base/tags"; import { deletePersonByUsername, deleteRolleById, deleteRolleByName } from "../base/testHelperDeleteTestdata.ts"; -import { landesadminRolle, schuelerRolle, schuladminOeffentlichRolle } from "../base/roles.ts"; +import { landesadminRolle, schuelerRolle, schuladminOeffentlichRolle } from "../base/rollen.ts"; const PW = process.env.PW; const ADMIN = process.env.USER; From b1b72f75c6fecbe6d44361cffd057effff228880 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Fri, 15 Nov 2024 10:29:16 +0100 Subject: [PATCH 04/10] first successful run --- fixtures/Benutzerimport_Lernrolle_UTF-8.csv | 5 ++ pages/FromAnywhere.ts | 2 +- pages/admin/PersonImportView.page.ts | 10 ++++ tests/Import.spec.ts | 63 +++++++++++++++------ 4 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 fixtures/Benutzerimport_Lernrolle_UTF-8.csv diff --git a/fixtures/Benutzerimport_Lernrolle_UTF-8.csv b/fixtures/Benutzerimport_Lernrolle_UTF-8.csv new file mode 100644 index 0000000..3f113c9 --- /dev/null +++ b/fixtures/Benutzerimport_Lernrolle_UTF-8.csv @@ -0,0 +1,5 @@ +Nachname;Vorname;Klasse +Mustermann;Max;1a +Mustermann;John;2b +Musterfrau;Johanna;1b +Musterfrau;Maria;2a diff --git a/pages/FromAnywhere.ts b/pages/FromAnywhere.ts index 471700b..8813bc0 100644 --- a/pages/FromAnywhere.ts +++ b/pages/FromAnywhere.ts @@ -5,7 +5,7 @@ function FromAnywhere(page: Page) { return { async start(): Promise { return page - .goto(process.env.FRONTEND_URL) + .goto(process.env.FRONTEND_URL as string) .then(() => new LandingPage(page)); }, }; diff --git a/pages/admin/PersonImportView.page.ts b/pages/admin/PersonImportView.page.ts index 9624ef0..e7c8d08 100644 --- a/pages/admin/PersonImportView.page.ts +++ b/pages/admin/PersonImportView.page.ts @@ -4,11 +4,21 @@ export class PersonImportViewPage { readonly page: Page; readonly body: Locator; readonly headlineBenutzerImport: Locator; + readonly schuleSelectInput: Locator; + readonly rolleSelectInput: Locator; + readonly fileInput: Locator; + readonly discardFileUploadButton: Locator; + readonly submitFileUploadButton: Locator; constructor(page) { // Benutzerimport this.page = page; this.body = page.locator('body'); this.headlineBenutzerImport = page.getByTestId('layout-card-headline'); + this.schuleSelectInput = page.getByTestId('schule-select').locator('input'); + this.rolleSelectInput = page.getByTestId('rolle-select').locator('input'); + this.fileInput = page.getByTestId('file-input').locator('input'); + this.discardFileUploadButton = page.getByTestId('person-import-form-discard-button'); + this.submitFileUploadButton = page.getByTestId('person-import-form-submit-button'); } } \ No newline at end of file diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index 4a5c9de..38e92c1 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -1,22 +1,32 @@ import { test, expect } from "@playwright/test"; -import { LandingPage } from "../pages/LandingView.page"; import { HeaderPage } from "../pages/Header.page"; import { LONG } from "../base/tags"; +import { schuelerRolle } from "../base/rollen"; +import { PersonImportViewPage } from "../pages/admin/PersonImportView.page"; +import path from "path"; +import FromAnywhere from "../pages/FromAnywhere"; -const PW = process.env.PW; -const ADMIN = process.env.USER; -const FRONTEND_URL = process.env.FRONTEND_URL || ""; +const PW: string = process.env.PW as string; +const ADMIN: string = process.env.USER as string; +const FRONTEND_URL: string = process.env.FRONTEND_URL || ""; +let importPage: PersonImportViewPage = undefined as unknown as PersonImportViewPage; + +// schulen cannot be deleted yet, so we use this testschule, which should already exist +const schulname = "Testschule-PW665"; test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGEBUNG}: URL: ${process.env.FRONTEND_URL}:`, () => { test.beforeEach(async ({ page }) => { - await test.step(`Login and navigate to Benutzerimport`, async () => { - const landingPage = new LandingPage(page); - + await test.step(`Einloggen und zu Benutzerimport navigieren`, async () => { await page.goto(FRONTEND_URL); - const loginPage = await landingPage.goToLogin(); - const startPage = await loginPage.login(ADMIN, PW); - const menuPage = await startPage.goToAdministration(); - const importPage = await menuPage.goToBenutzerImport(); + importPage = (await + (await + (await + (await + (await FromAnywhere(page).start()) + .goToLogin()) + .login(ADMIN, PW)) + .goToAdministration()) + .goToBenutzerImport()); await expect(importPage.headlineBenutzerImport).toBeVisible(); }); @@ -24,7 +34,7 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE test.afterEach(async ({ page }) => { await test.step(`Testdaten löschen via API`, async () => { - // delete test data + // delete imported users }); await test.step(`Abmelden`, async () => { @@ -34,12 +44,33 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE }); test("Als Landesadmin eine CSV-Datei mit Benutzerdaten hochladen und importieren", {tag: [LONG]}, async ({ page }) => { - const schulname = ""; - const rollenname = ""; - const csvFile = ""; - await test.step(``, async () => { + // select schule + await importPage.schuleSelectInput.click(); + await importPage.schuleSelectInput.fill(schulname); + await page.getByRole('option', { name: `(${schulname})` }).click(); + // await page.getByText(schulname).click(); + + // select rolle + await importPage.rolleSelectInput.click(); + await page.getByRole('option', { name: schuelerRolle }).click(); + // upload CSV file + await importPage.fileInput.setInputFiles(path.join(__dirname, '../fixtures/Benutzerimport_Lernrolle_UTF-8.csv')); + + // submit + + // check success message with data sets + + // click import + + // check success message + + // download file + + // check file content + + // check imported users in person management view }); }); }); \ No newline at end of file From ca640f748f6356db7b6835051d440674bf517bc5 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Tue, 19 Nov 2024 15:26:58 +0100 Subject: [PATCH 05/10] finish test (deletion still WIP) --- base/testHelperDeleteTestdata.ts | 42 +++++------ elements/ComboBox.ts | 10 +++ fixtures/Benutzerimport_Lernrolle_UTF-8.csv | 9 ++- pages/admin/PersonImportView.page.ts | 27 +++++++ pages/admin/PersonManagementView.page.ts | 39 ++++++---- tests/Import.spec.ts | 84 ++++++++++++++++----- tests/Klasse.spec.ts | 4 +- 7 files changed, 152 insertions(+), 63 deletions(-) diff --git a/base/testHelperDeleteTestdata.ts b/base/testHelperDeleteTestdata.ts index 4c56dcd..1452640 100644 --- a/base/testHelperDeleteTestdata.ts +++ b/base/testHelperDeleteTestdata.ts @@ -4,34 +4,34 @@ import { getKlasseId, deleteKlasse } from "./api/testHelperOrganisation.page"; export async function deletePersonById(personId, page){ // personId ist ein array mit allen zu löschenden Benutzern - for (const item in personId){ - await deletePersonen(page, personId[item]); - } + for (const item in personId){ + await deletePersonen(page, personId[item]); + } } export async function deleteRolleById(roleId, page){ // roleId ist ein array mit allen zu löschenden Rollen - for (const item in roleId){ - await deleteRolle(page, roleId[item]); - } + for (const item in roleId){ + await deleteRolle(page, roleId[item]); + } } export async function deleteRolleByName(roleName, page){ // roleName ist ein array mit allen zu löschenden Rollen - for (const item in roleName){ - const roleId = await getRolleId(page, roleName[item]); - await deleteRolle(page, roleId); - } + for (const item in roleName){ + const roleId = await getRolleId(page, roleName[item]); + await deleteRolle(page, roleId); } +} - export async function deletePersonByUsername(username, page){ // username ist ein array mit allen zu löschenden Benutzern - for (const item in username){ - const personId = await getPersonId(page, username[item]); - await deletePersonen(page, personId); - } +export async function deletePersonByUsername(username, page){ // username ist ein array mit allen zu löschenden Benutzern + for (const item in username){ + const personId = await getPersonId(page, username[item]); + await deletePersonen(page, personId); } +} - export async function deleteClassByName(className, page){ // className ist ein array mit allen zu löschenden Klassen - for (const item in className){ - const classId = await getKlasseId(page, className[item]); - await deleteKlasse(page, classId); - } - } \ No newline at end of file +export async function deleteKlasseByName(klassenName, page){ // klassenName ist ein array mit allen zu löschenden Klassen + for (const item in klassenName){ + const klassenId = await getKlasseId(page, klassenName[item]); + await deleteKlasse(page, klassenId); + } +} \ No newline at end of file diff --git a/elements/ComboBox.ts b/elements/ComboBox.ts index 4347907..0a4342a 100644 --- a/elements/ComboBox.ts +++ b/elements/ComboBox.ts @@ -30,4 +30,14 @@ export class ComboBox { await item.waitFor({ state: "visible" }); await item.click(); } + + public async searchByTitle(title: string): Promise { + await this.locator.click(); + await this.locator.fill(title); + const item = this.itemsLocator.filter({ + has: this.page.getByText(title, { exact: true }), + }); + await item.waitFor({ state: "visible" }); + await item.click(); + } } diff --git a/fixtures/Benutzerimport_Lernrolle_UTF-8.csv b/fixtures/Benutzerimport_Lernrolle_UTF-8.csv index 3f113c9..48b49c3 100644 --- a/fixtures/Benutzerimport_Lernrolle_UTF-8.csv +++ b/fixtures/Benutzerimport_Lernrolle_UTF-8.csv @@ -1,5 +1,6 @@ Nachname;Vorname;Klasse -Mustermann;Max;1a -Mustermann;John;2b -Musterfrau;Johanna;1b -Musterfrau;Maria;2a +Mustermann-Importtest;Max;1A +Orton-Importtest;John;2B +Laser-Importtest;Eduard;1B +Cena-Importtest;Randy;1B +Musterfrau-Importtest;Maria;2A diff --git a/pages/admin/PersonImportView.page.ts b/pages/admin/PersonImportView.page.ts index e7c8d08..e7aa3c2 100644 --- a/pages/admin/PersonImportView.page.ts +++ b/pages/admin/PersonImportView.page.ts @@ -1,14 +1,25 @@ import { type Locator, Page } from '@playwright/test'; +import { PersonManagementViewPage } from "./PersonManagementView.page"; +import { ComboBox } from '../../elements/ComboBox'; export class PersonImportViewPage { readonly page: Page; readonly body: Locator; readonly headlineBenutzerImport: Locator; readonly schuleSelectInput: Locator; + readonly schuleSelectCombobox: ComboBox; readonly rolleSelectInput: Locator; readonly fileInput: Locator; readonly discardFileUploadButton: Locator; readonly submitFileUploadButton: Locator; + readonly uploadSuccessText: Locator; + readonly openConfirmationDialogButton: Locator; + readonly importConfirmationText: Locator; + readonly executeImportButton: Locator; + readonly importSuccessText: Locator; + readonly downloadFileButton: Locator; + readonly closeCardButton: Locator; + readonly confirmUnsavedChangesButton: Locator; constructor(page) { // Benutzerimport @@ -16,9 +27,25 @@ export class PersonImportViewPage { this.body = page.locator('body'); this.headlineBenutzerImport = page.getByTestId('layout-card-headline'); this.schuleSelectInput = page.getByTestId('schule-select').locator('input'); + this.schuleSelectCombobox = new ComboBox(this.page, this.schuleSelectInput,); this.rolleSelectInput = page.getByTestId('rolle-select').locator('input'); this.fileInput = page.getByTestId('file-input').locator('input'); this.discardFileUploadButton = page.getByTestId('person-import-form-discard-button'); this.submitFileUploadButton = page.getByTestId('person-import-form-submit-button'); + this.uploadSuccessText = page.getByTestId('person-upload-success-text'); + this.openConfirmationDialogButton = page.getByTestId('open-confirmation-dialog-button'); + this.importConfirmationText = page.getByTestId('person-import-confirmation-text'); + this.executeImportButton = page.getByTestId('execute-import-button'); + this.importSuccessText = page.getByTestId('person-import-success-text'); + this.downloadFileButton = page.getByTestId('download-file-button'); + this.closeCardButton = page.getByTestId('close-layout-card-button'); + this.confirmUnsavedChangesButton = page.getByTestId('confirm-unsaved-changes-button'); + } + + public async navigateToPersonManagementView(): Promise { + await this.closeCardButton.click(); + await this.confirmUnsavedChangesButton.click(); + + return new PersonManagementViewPage(this.page); } } \ No newline at end of file diff --git a/pages/admin/PersonManagementView.page.ts b/pages/admin/PersonManagementView.page.ts index 0f9f0c4..7ee94b5 100644 --- a/pages/admin/PersonManagementView.page.ts +++ b/pages/admin/PersonManagementView.page.ts @@ -1,4 +1,5 @@ import { type Locator, Page } from '@playwright/test'; +import { PersonDetailsViewPage } from "./PersonDetailsView.page"; export class PersonManagementViewPage{ readonly page: Page; @@ -19,21 +20,27 @@ export class PersonManagementViewPage{ readonly comboboxMenuIcon_Status: Locator; constructor(page){ - this.page = page; - this.text_h1_Administrationsbereich = page.getByTestId('admin-headline'); - this.text_h2_Benutzerverwaltung = page.getByTestId('layout-card-headline'); - this.input_Suchfeld = page.locator('[data-testid="search-filter-input"] input'); - this.button_Suchen = page.getByTestId('apply-search-filter-button'); - this.table_header_Nachname = page.getByTestId('person-table').getByText('Nachname', { exact: true }); - this.table_header_Vorname = page.getByTestId('person-table').getByText('Vorname', { exact: true }); - this.table_header_Benutzername = page.getByText('Benutzername', { exact: true }); - this.table_header_KopersNr = page.getByText('KoPers.-Nr.'); - this.table_header_Rolle = page.getByTestId('person-table').getByText('Rolle', { exact: true }); - this.table_header_Zuordnungen = page.getByText('Zuordnung(en)'); - this.table_header_Klasse = page.getByTestId('person-table').getByText('Klasse', { exact: true }); - this.comboboxMenuIcon_Schule = page.locator('[data-testid="schule-select"] .mdi-menu-down'); - this.comboboxMenuIcon_Rolle = page.locator('[data-testid="rolle-select"] .mdi-menu-down'); - this.comboboxMenuIcon_Klasse = page.locator('[data-testid="klasse-select"] .mdi-menu-down'); - this.comboboxMenuIcon_Status = page.locator('[data-testid="status-select"] .mdi-menu-down'); + this.page = page; + this.text_h1_Administrationsbereich = page.getByTestId('admin-headline'); + this.text_h2_Benutzerverwaltung = page.getByTestId('layout-card-headline'); + this.input_Suchfeld = page.locator('[data-testid="search-filter-input"] input'); + this.button_Suchen = page.getByTestId('apply-search-filter-button'); + this.table_header_Nachname = page.getByTestId('person-table').getByText('Nachname', { exact: true }); + this.table_header_Vorname = page.getByTestId('person-table').getByText('Vorname', { exact: true }); + this.table_header_Benutzername = page.getByText('Benutzername', { exact: true }); + this.table_header_KopersNr = page.getByText('KoPers.-Nr.'); + this.table_header_Rolle = page.getByTestId('person-table').getByText('Rolle', { exact: true }); + this.table_header_Zuordnungen = page.getByText('Zuordnung(en)'); + this.table_header_Klasse = page.getByTestId('person-table').getByText('Klasse', { exact: true }); + this.comboboxMenuIcon_Schule = page.locator('[data-testid="schule-select"] .mdi-menu-down'); + this.comboboxMenuIcon_Rolle = page.locator('[data-testid="rolle-select"] .mdi-menu-down'); + this.comboboxMenuIcon_Klasse = page.locator('[data-testid="klasse-select"] .mdi-menu-down'); + this.comboboxMenuIcon_Status = page.locator('[data-testid="status-select"] .mdi-menu-down'); + } + + public async navigateToPersonDetailsViewByNachname(nachname: string): Promise { + await this.page.getByRole("cell", { name: nachname, exact: true }).click(); + + return new PersonDetailsViewPage(this.page); } } \ No newline at end of file diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index 38e92c1..fd4b716 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -1,24 +1,30 @@ -import { test, expect } from "@playwright/test"; +import { test, expect, Download } from "@playwright/test"; import { HeaderPage } from "../pages/Header.page"; import { LONG } from "../base/tags"; import { schuelerRolle } from "../base/rollen"; +import FromAnywhere from "../pages/FromAnywhere"; import { PersonImportViewPage } from "../pages/admin/PersonImportView.page"; +import { PersonManagementViewPage } from "../pages/admin/PersonManagementView.page"; import path from "path"; -import FromAnywhere from "../pages/FromAnywhere"; +import fs from "fs"; const PW: string = process.env.PW as string; const ADMIN: string = process.env.USER as string; const FRONTEND_URL: string = process.env.FRONTEND_URL || ""; -let importPage: PersonImportViewPage = undefined as unknown as PersonImportViewPage; +let personImportPage: PersonImportViewPage = undefined as unknown as PersonImportViewPage; // schulen cannot be deleted yet, so we use this testschule, which should already exist const schulname = "Testschule-PW665"; test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGEBUNG}: URL: ${process.env.FRONTEND_URL}:`, () => { + // convert csv to array to make person data accessible, also trim data and filter empty lines + const csvPath = path.join(__dirname, '../fixtures/Benutzerimport_Lernrolle_UTF-8.csv'); + const csvAsArray: Array = fs.readFileSync(csvPath).toString().split('\n').map(el => el.trim()).filter(e => e !== ''); + test.beforeEach(async ({ page }) => { await test.step(`Einloggen und zu Benutzerimport navigieren`, async () => { await page.goto(FRONTEND_URL); - importPage = (await + personImportPage = (await (await (await (await @@ -28,13 +34,20 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE .goToAdministration()) .goToBenutzerImport()); - await expect(importPage.headlineBenutzerImport).toBeVisible(); + await expect(personImportPage.headlineBenutzerImport).toBeVisible(); }); }); test.afterEach(async ({ page }) => { await test.step(`Testdaten löschen via API`, async () => { - // delete imported users + // delete imported users - but how to we identify the imported users? + // csvAsArray.forEach(async (person, index) => { + // // index has to be greater than 0, because the first line is the header + // if (index > 0) { + // const nachname = person.split(';')[0]; + // await deletePersonByNachname(nachname, page); + // } + // }); }); await test.step(`Abmelden`, async () => { @@ -45,32 +58,63 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE test("Als Landesadmin eine CSV-Datei mit Benutzerdaten hochladen und importieren", {tag: [LONG]}, async ({ page }) => { await test.step(``, async () => { + // select schule - await importPage.schuleSelectInput.click(); - await importPage.schuleSelectInput.fill(schulname); - await page.getByRole('option', { name: `(${schulname})` }).click(); - // await page.getByText(schulname).click(); + await personImportPage.schuleSelectCombobox.searchByTitle(schulname); // select rolle - await importPage.rolleSelectInput.click(); + await personImportPage.rolleSelectInput.click(); await page.getByRole('option', { name: schuelerRolle }).click(); // upload CSV file - await importPage.fileInput.setInputFiles(path.join(__dirname, '../fixtures/Benutzerimport_Lernrolle_UTF-8.csv')); + await personImportPage.fileInput.setInputFiles(csvPath); - // submit + // submit and assert text + // calculate number of personen in csv + const personenTotal = csvAsArray.length - 1; + await personImportPage.submitFileUploadButton.click(); + await expect(personImportPage.uploadSuccessText).toHaveText(`Die Datei wurde erfolgreich hochgeladen. ${personenTotal} Datensätze stehen zum Import bereit.`); - // check success message with data sets - - // click import - - // check success message + // click import and assert success message + await personImportPage.openConfirmationDialogButton.click(); + await expect(personImportPage.importConfirmationText).toHaveText('Achtung, diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie den Import wirklich durchführen?'); + await personImportPage.executeImportButton.click(); + await expect(personImportPage.importSuccessText).toHaveText('Die Daten wurden erfolgreich importiert. Die importierten Daten stehen zum Download bereit.'); // download file - - // check file content + const downloadPromise = page.waitForEvent('download'); + await personImportPage.downloadFileButton.click(); + const download: Download = await downloadPromise; + expect(download.suggestedFilename()).toBe('Benutzerdaten.txt'); // check imported users in person management view + // get first person from csv and split data by ";" + const firstPerson: Array = csvAsArray[1].split(';'); + // get last name from first person to search in person management view + const firstPersonLastName: string = firstPerson[0]; + + const personManagementPage: PersonManagementViewPage = await personImportPage.navigateToPersonManagementView(); + await page.waitForURL('**/admin/personen'); + await personManagementPage.input_Suchfeld.fill(firstPersonLastName); + await personManagementPage.button_Suchen.click(); + await expect(page.getByRole("cell", { name: firstPersonLastName, exact: true })).toBeVisible(); + await personManagementPage.input_Suchfeld.clear(); + + // delete imported users + csvAsArray.forEach(async (person, index) => { + // index has to be greater than 0, because the first line is the header + if (index > 0) { + const nachname: string = person.split(';')[0]; + await personManagementPage.input_Suchfeld.clear(); + await personManagementPage.input_Suchfeld.fill(nachname); + await personManagementPage.button_Suchen.click(); + await page.getByRole("cell", { name: nachname, exact: true }).isVisible(); + const personDetailsPage = await personManagementPage.navigateToPersonDetailsViewByNachname(nachname); + await personDetailsPage.button_deletePerson.click(); + await personDetailsPage.button_deletePersonConfirm.click(); + await personDetailsPage.button_closeDeletePersonConfirm.click(); + } + }); }); }); }); \ No newline at end of file diff --git a/tests/Klasse.spec.ts b/tests/Klasse.spec.ts index 6a03bb6..befb231 100644 --- a/tests/Klasse.spec.ts +++ b/tests/Klasse.spec.ts @@ -8,7 +8,7 @@ import { KlasseManagementViewPage } from "../pages/admin/KlasseManagementView.pa import { faker } from "@faker-js/faker/locale/de"; import { HeaderPage } from "../pages/Header.page"; import { LONG, SHORT, STAGE } from "../base/tags"; -import { deleteClassByName } from "../base/testHelperDeleteTestdata.ts"; +import { deleteKlasseByName } from "../base/testHelperDeleteTestdata.ts"; const PW = process.env.PW; const ADMIN = process.env.USER; @@ -33,7 +33,7 @@ test.describe(`Testfälle für die Administration von Klassen: Umgebung: ${proce test.afterEach(async ({ page }) => { await test.step(`Testdaten löschen via API`, async () => { if (className) { // nur wenn der Testfall auch mind. eine Klasse angelegt hat - await deleteClassByName(className, page); + await deleteKlasseByName(className, page); className = []; } }); From c9a6792d011ddfe3473cf0fd0bb74e1a0512e714 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Wed, 20 Nov 2024 13:49:30 +0100 Subject: [PATCH 06/10] fixed dirname issue --- tests/Import.spec.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index fd4b716..0171c50 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -7,6 +7,7 @@ import { PersonImportViewPage } from "../pages/admin/PersonImportView.page"; import { PersonManagementViewPage } from "../pages/admin/PersonManagementView.page"; import path from "path"; import fs from "fs"; +import { fileURLToPath } from "url"; const PW: string = process.env.PW as string; const ADMIN: string = process.env.USER as string; @@ -18,7 +19,9 @@ const schulname = "Testschule-PW665"; test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGEBUNG}: URL: ${process.env.FRONTEND_URL}:`, () => { // convert csv to array to make person data accessible, also trim data and filter empty lines - const csvPath = path.join(__dirname, '../fixtures/Benutzerimport_Lernrolle_UTF-8.csv'); + const filename: string = fileURLToPath(import.meta.url); + const dirname: string = path.dirname(filename); + const csvPath: string = path.join(dirname, '../fixtures/Benutzerimport_Lernrolle_UTF-8.csv'); const csvAsArray: Array = fs.readFileSync(csvPath).toString().split('\n').map(el => el.trim()).filter(e => e !== ''); test.beforeEach(async ({ page }) => { @@ -39,17 +42,6 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE }); test.afterEach(async ({ page }) => { - await test.step(`Testdaten löschen via API`, async () => { - // delete imported users - but how to we identify the imported users? - // csvAsArray.forEach(async (person, index) => { - // // index has to be greater than 0, because the first line is the header - // if (index > 0) { - // const nachname = person.split(';')[0]; - // await deletePersonByNachname(nachname, page); - // } - // }); - }); - await test.step(`Abmelden`, async () => { const header = new HeaderPage(page); await header.logout(); @@ -100,7 +92,7 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE await expect(page.getByRole("cell", { name: firstPersonLastName, exact: true })).toBeVisible(); await personManagementPage.input_Suchfeld.clear(); - // delete imported users + // delete imported users via UI to clean up csvAsArray.forEach(async (person, index) => { // index has to be greater than 0, because the first line is the header if (index > 0) { From 6b33e05261e6084b52871d16ecde4da0bf9da3b5 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Wed, 20 Nov 2024 15:57:17 +0100 Subject: [PATCH 07/10] finished deletion of imported users --- tests/Import.spec.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index 0171c50..dc49721 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -93,20 +93,21 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE await personManagementPage.input_Suchfeld.clear(); // delete imported users via UI to clean up - csvAsArray.forEach(async (person, index) => { - // index has to be greater than 0, because the first line is the header - if (index > 0) { - const nachname: string = person.split(';')[0]; - await personManagementPage.input_Suchfeld.clear(); - await personManagementPage.input_Suchfeld.fill(nachname); - await personManagementPage.button_Suchen.click(); - await page.getByRole("cell", { name: nachname, exact: true }).isVisible(); - const personDetailsPage = await personManagementPage.navigateToPersonDetailsViewByNachname(nachname); - await personDetailsPage.button_deletePerson.click(); - await personDetailsPage.button_deletePersonConfirm.click(); - await personDetailsPage.button_closeDeletePersonConfirm.click(); - } - }); + // index has to be greater than 0, because the first line is the header + for (let index = 1; index < csvAsArray.length; index++) { + const person = csvAsArray[index]; + const nachname: string = person.split(';')[0]; + + await personManagementPage.input_Suchfeld.clear(); + await personManagementPage.input_Suchfeld.fill(nachname); + await personManagementPage.button_Suchen.click(); + + await page.getByRole("cell", { name: nachname, exact: true }).isVisible(); + const personDetailsPage = await personManagementPage.navigateToPersonDetailsViewByNachname(nachname); + await personDetailsPage.button_deletePerson.click(); + await personDetailsPage.button_deletePersonConfirm.click(); + await personDetailsPage.button_closeDeletePersonConfirm.click(); + } }); }); }); \ No newline at end of file From 827e6f2acf0870a1acf352a30d9cb068dcfd4f5b Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Mon, 25 Nov 2024 09:55:47 +0100 Subject: [PATCH 08/10] use schule from helper --- tests/Import.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index dc49721..5a069f5 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -9,14 +9,14 @@ import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; +// schulen cannot be deleted yet, so we use this testschule, which should already exist +import { testschule665 } from "../base/organisation.ts"; + const PW: string = process.env.PW as string; const ADMIN: string = process.env.USER as string; const FRONTEND_URL: string = process.env.FRONTEND_URL || ""; let personImportPage: PersonImportViewPage = undefined as unknown as PersonImportViewPage; -// schulen cannot be deleted yet, so we use this testschule, which should already exist -const schulname = "Testschule-PW665"; - test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGEBUNG}: URL: ${process.env.FRONTEND_URL}:`, () => { // convert csv to array to make person data accessible, also trim data and filter empty lines const filename: string = fileURLToPath(import.meta.url); @@ -52,7 +52,7 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE await test.step(``, async () => { // select schule - await personImportPage.schuleSelectCombobox.searchByTitle(schulname); + await personImportPage.schuleSelectCombobox.searchByTitle(testschule665); // select rolle await personImportPage.rolleSelectInput.click(); From 59cceea7bd037e146ad8d610a6e8ec36b0ce291d Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Tue, 26 Nov 2024 17:11:17 +0100 Subject: [PATCH 09/10] moved data deletion to after each hook and refactored helper methods --- base/api/testHelperPerson.page.ts | 6 +++--- base/testHelperDeleteTestdata.ts | 17 +++++++++------ tests/Import.spec.ts | 36 ++++++++++++------------------- tests/Person.spec.ts | 4 ++-- tests/PersonBearbeiten.spec.ts | 4 ++-- tests/Profile.spec.ts | 4 ++-- tests/login.spec.ts | 4 ++-- 7 files changed, 36 insertions(+), 39 deletions(-) diff --git a/base/api/testHelperPerson.page.ts b/base/api/testHelperPerson.page.ts index 4487f22..02966ba 100644 --- a/base/api/testHelperPerson.page.ts +++ b/base/api/testHelperPerson.page.ts @@ -83,13 +83,13 @@ export async function addSecondOrganisationToPerson(page: Page, personId: string expect(response.status()).toBe(200); } -export async function deletePersonen(page: Page, personId: string): Promise { +export async function deletePerson(page: Page, personId: string): Promise { const response = await page.request.delete(FRONTEND_URL + `api/personen/${personId}`, {}); expect(response.status()).toBe(204); } -export async function getPersonId(page: Page, Benutzername: string): Promise { - const response = await page.request.get(FRONTEND_URL + `api/personen-frontend?suchFilter=${Benutzername}`, {}); +export async function getPersonId(page: Page, searchString: string): Promise { + const response = await page.request.get(FRONTEND_URL + `api/personen-frontend?suchFilter=${searchString}`, {}); expect(response.status()).toBe(200); const json = await response.json(); return json.items[0].person.id; diff --git a/base/testHelperDeleteTestdata.ts b/base/testHelperDeleteTestdata.ts index 1452640..374c7a2 100644 --- a/base/testHelperDeleteTestdata.ts +++ b/base/testHelperDeleteTestdata.ts @@ -1,11 +1,11 @@ import { deleteRolle, getRolleId} from "./api/testHelperRolle.page"; -import { deletePersonen, getPersonId } from "./api/testHelperPerson.page"; +import { deletePerson, getPersonId } from "./api/testHelperPerson.page"; import { getKlasseId, deleteKlasse } from "./api/testHelperOrganisation.page"; export async function deletePersonById(personId, page){ // personId ist ein array mit allen zu löschenden Benutzern for (const item in personId){ - await deletePersonen(page, personId[item]); + await deletePerson(page, personId[item]); } } @@ -22,13 +22,18 @@ export async function deleteRolleByName(roleName, page){ // roleName ist ein ar } } -export async function deletePersonByUsername(username, page){ // username ist ein array mit allen zu löschenden Benutzern - for (const item in username){ - const personId = await getPersonId(page, username[item]); - await deletePersonen(page, personId); +export async function deletePersonenBySearchStrings(page, searchStringArray){ + for (const item in searchStringArray){ + const personId = await getPersonId(page, searchStringArray[item]); + await deletePerson(page, personId); } } +export async function deletePersonBySearchString(page, searchString){ + const personId = await getPersonId(page, searchString); + await deletePerson(page, personId); +} + export async function deleteKlasseByName(klassenName, page){ // klassenName ist ein array mit allen zu löschenden Klassen for (const item in klassenName){ const klassenId = await getKlasseId(page, klassenName[item]); diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index 5a069f5..0910582 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -8,6 +8,7 @@ import { PersonManagementViewPage } from "../pages/admin/PersonManagementView.pa import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; +import { deletePersonBySearchString } from "../base/testHelperDeleteTestdata.ts"; // schulen cannot be deleted yet, so we use this testschule, which should already exist import { testschule665 } from "../base/organisation.ts"; @@ -25,7 +26,7 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE const csvAsArray: Array = fs.readFileSync(csvPath).toString().split('\n').map(el => el.trim()).filter(e => e !== ''); test.beforeEach(async ({ page }) => { - await test.step(`Einloggen und zu Benutzerimport navigieren`, async () => { + await test.step('Einloggen und zu Benutzerimport navigieren', async () => { await page.goto(FRONTEND_URL); personImportPage = (await (await @@ -42,15 +43,23 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE }); test.afterEach(async ({ page }) => { + await test.step('Importierte Daten über die API löschen', async () => { + for (let index = 1; index < csvAsArray.length; index++) { + const person = csvAsArray[index]; + const nachname: string = person.split(';')[0]; + + await deletePersonBySearchString(page, nachname); + }; + }); + await test.step(`Abmelden`, async () => { const header = new HeaderPage(page); await header.logout(); }); }); - test("Als Landesadmin eine CSV-Datei mit Benutzerdaten hochladen und importieren", {tag: [LONG]}, async ({ page }) => { - await test.step(``, async () => { - + test('Als Landesadmin eine CSV-Datei mit Benutzerdaten hochladen und importieren', {tag: [LONG]}, async ({ page }) => { + await test.step('CSV-Datei hochladen, importieren und importierte Daten downloaden', async () => { // select schule await personImportPage.schuleSelectCombobox.searchByTitle(testschule665); @@ -89,25 +98,8 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE await page.waitForURL('**/admin/personen'); await personManagementPage.input_Suchfeld.fill(firstPersonLastName); await personManagementPage.button_Suchen.click(); - await expect(page.getByRole("cell", { name: firstPersonLastName, exact: true })).toBeVisible(); + await expect(page.getByRole('cell', { name: firstPersonLastName, exact: true })).toBeVisible(); await personManagementPage.input_Suchfeld.clear(); - - // delete imported users via UI to clean up - // index has to be greater than 0, because the first line is the header - for (let index = 1; index < csvAsArray.length; index++) { - const person = csvAsArray[index]; - const nachname: string = person.split(';')[0]; - - await personManagementPage.input_Suchfeld.clear(); - await personManagementPage.input_Suchfeld.fill(nachname); - await personManagementPage.button_Suchen.click(); - - await page.getByRole("cell", { name: nachname, exact: true }).isVisible(); - const personDetailsPage = await personManagementPage.navigateToPersonDetailsViewByNachname(nachname); - await personDetailsPage.button_deletePerson.click(); - await personDetailsPage.button_deletePersonConfirm.click(); - await personDetailsPage.button_closeDeletePersonConfirm.click(); - } }); }); }); \ No newline at end of file diff --git a/tests/Person.spec.ts b/tests/Person.spec.ts index 5fbcc32..2dfc86b 100644 --- a/tests/Person.spec.ts +++ b/tests/Person.spec.ts @@ -13,7 +13,7 @@ import { getSPId } from "../base/api/testHelperServiceprovider.page"; import { UserInfo } from "../base/api/testHelper.page"; import { addSystemrechtToRolle } from "../base/api/testHelperRolle.page"; import { LONG, SHORT, STAGE, BROWSER } from "../base/tags"; -import { deletePersonByUsername, deleteRolleById, deleteRolleByName } from "../base/testHelperDeleteTestdata.ts"; +import { deletePersonenBySearchStrings, deleteRolleById, deleteRolleByName } from "../base/testHelperDeleteTestdata.ts"; import { landesadminRolle, schuelerRolle, schuladminOeffentlichRolle } from "../base/rollen.ts"; import { generateLehrerNachname, generateLehrerVorname, generateRolleName } from "../base/testHelperGenerateTestdataNames.ts"; @@ -51,7 +51,7 @@ test.describe(`Testfälle für die Administration von Personen": Umgebung: ${pro await login.login(ADMIN, PW); await expect(startseite.text_h2_Ueberschrift).toBeVisible(); - await deletePersonByUsername(username, page); + await deletePersonenBySearchStrings(page, username); username = []; } diff --git a/tests/PersonBearbeiten.spec.ts b/tests/PersonBearbeiten.spec.ts index 9ce47c4..b3b4e27 100644 --- a/tests/PersonBearbeiten.spec.ts +++ b/tests/PersonBearbeiten.spec.ts @@ -11,7 +11,7 @@ import {getSPId} from "../base/api/testHelperServiceprovider.page.ts"; import {UserInfo} from "../base/api/testHelper.page.ts"; import {addSystemrechtToRolle} from "../base/api/testHelperRolle.page.ts"; import {LONG, STAGE} from "../base/tags.ts"; -import {deletePersonByUsername, deleteRolleById} from "../base/testHelperDeleteTestdata.ts"; +import {deletePersonenBySearchStrings, deleteRolleById} from "../base/testHelperDeleteTestdata.ts"; import {typelehrer} from "../base/rollentypen.ts"; import {testschule} from "../base/organisation.ts"; import {email} from "../base/sp.ts"; @@ -50,7 +50,7 @@ test.describe(`Testfälle für die Administration von Personen": Umgebung: ${pro await login.login(ADMIN, PW); await expect(startseite.text_h2_Ueberschrift).toBeVisible(); - await deletePersonByUsername(username, page); + await deletePersonenBySearchStrings(page, username); username = []; } diff --git a/tests/Profile.spec.ts b/tests/Profile.spec.ts index c29e560..eaff949 100644 --- a/tests/Profile.spec.ts +++ b/tests/Profile.spec.ts @@ -11,7 +11,7 @@ import { getOrganisationId } from "../base/api/testHelperOrganisation.page"; import { UserInfo } from "../base/api/testHelper.page"; import { addSystemrechtToRolle } from "../base/api/testHelperRolle.page"; import { LONG, SHORT, STAGE, BROWSER } from "../base/tags"; -import { deleteRolleById, deletePersonByUsername} from "../base/testHelperDeleteTestdata"; +import { deleteRolleById, deletePersonenBySearchStrings} from "../base/testHelperDeleteTestdata"; import { generateLehrerNachname, generateLehrerVorname, generateRolleName } from "../base/testHelperGenerateTestdataNames.ts"; const PW: string | undefined = process.env.PW; @@ -49,7 +49,7 @@ test.describe(`Testfälle für das eigene Profil anzeigen: Umgebung: ${process.e await expect(startseite.text_h2_Ueberschrift).toBeVisible(); await expect(startseite.card_item_schulportal_administration).toBeVisible(); - await deletePersonByUsername(username, page); + await deletePersonenBySearchStrings(page, username); username = []; } diff --git a/tests/login.spec.ts b/tests/login.spec.ts index 3f7b4d3..7a3b5b3 100644 --- a/tests/login.spec.ts +++ b/tests/login.spec.ts @@ -8,7 +8,7 @@ import { createRolleAndPersonWithUserContext, lockPerson } from "../base/api/tes import { getSPId } from "../base/api/testHelperServiceprovider.page.ts"; import { faker } from "@faker-js/faker/locale/de"; import { UserInfo } from "../base/api/testHelper.page.ts"; -import { deletePersonByUsername, deleteRolleById } from "../base/testHelperDeleteTestdata.ts"; +import { deletePersonenBySearchStrings, deleteRolleById } from "../base/testHelperDeleteTestdata.ts"; import { getOrganisationId } from "../base/api/testHelperOrganisation.page.ts"; import { generateRolleName } from '../base/testHelperGenerateTestdataNames.ts'; @@ -48,7 +48,7 @@ test.describe(`Testfälle für die Authentifizierung: Umgebung: ${process.env.UM await login.login(ADMIN, PW); await expect(startseite.text_h2_Ueberschrift).toBeVisible(); - await deletePersonByUsername(username, page); + await deletePersonenBySearchStrings(page, username); username = []; } From ceefa5d2e9f5c25ef556dc9140cf36d28f483fd5 Mon Sep 17 00:00:00 2001 From: jaggarnaut Date: Thu, 28 Nov 2024 13:19:43 +0100 Subject: [PATCH 10/10] removed extraneous line --- tests/Import.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Import.spec.ts b/tests/Import.spec.ts index 0910582..18b12c6 100644 --- a/tests/Import.spec.ts +++ b/tests/Import.spec.ts @@ -99,7 +99,6 @@ test.describe(`Testfälle für den Benutzerimport": Umgebung: ${process.env.UMGE await personManagementPage.input_Suchfeld.fill(firstPersonLastName); await personManagementPage.button_Suchen.click(); await expect(page.getByRole('cell', { name: firstPersonLastName, exact: true })).toBeVisible(); - await personManagementPage.input_Suchfeld.clear(); }); }); }); \ No newline at end of file