waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ app e2e melden.spec.ts

5,1 KB Rohdatei
app/e2e/melden.spec.ts — 135 Zeilen
1 /**
2 * „Fehler zu dieser Frage melden“ im echten Fenster.
3 *
4 * Hier zählt vor allem eines: dass der Text wirklich in der Zwischenablage
5 * des Systems landet. Die Oberflächentests prüfen das gegen eine Attrappe;
6 * ob Electrons `clipboard` an dieser Stelle tut, was es soll, sagt nur der
7 * echte Lauf. Der Weg über `navigator.clipboard` ist hier nachweislich
8 * versperrt – siehe `shared/ipc.ts`, Kanal `system:kopieren`.
9 */
10
11 import { type ElectronApplication, type Page } from '@playwright/test';
12
13 import { amStart, appStarten, bauPruefen } from './electron-hilfe';
14 import { expect, test } from './konsolenwache';
15
16 let app: ElectronApplication | undefined;
17 let fenster: Page;
18
19 test.beforeAll(async () => {
20 bauPruefen();
21 ({ app, fenster } = await appStarten());
22 });
23
24 test.afterAll(async () => {
25 await app?.close();
26 });
27
28 test.describe.configure({ mode: 'serial' });
29
30 async function zumStart(): Promise<void> {
31 for (let i = 0; i < 4; i++) {
32 if (await amStart(fenster)) {
33 return;
34 }
35 const zurueck = fenster.getByRole('button', { name: /^(Zum Start|Sitzung beenden)$/u });
36 if ((await zurueck.count()) === 0) {
37 return;
38 }
39 await zurueck.first().click();
40 await fenster.waitForTimeout(250);
41 }
42 }
43
44 async function sitzungOeffnen(): Promise<void> {
45 await zumStart();
46 await fenster.getByRole('button', { name: /Weiterlernen/u }).click();
47 await fenster.getByRole('heading', { level: 1, name: /, Frage \d/u }).waitFor();
48 }
49
50 test.describe('Fehler melden', () => {
51 test('legt genau den angezeigten Text in die Zwischenablage', async () => {
52 await sitzungOeffnen();
53
54 const aufklapper = fenster.getByText('Fehler zu dieser Frage melden');
55 await aufklapper.scrollIntoViewIfNeeded();
56 await aufklapper.click();
57
58 const feld = fenster.getByLabel('Meldetext');
59 await expect(feld).toBeVisible();
60 const angezeigt = await feld.inputValue();
61 expect(angezeigt).toContain('Fehlermeldung zur Waffensachkunde-Lernsoftware');
62 expect(angezeigt).toContain('Kennung ');
63
64 await fenster.getByRole('button', { name: 'Text kopieren' }).click();
65 await expect(fenster.getByText(/liegt in der Zwischenablage/u)).toBeVisible();
66
67 /* Der Kern der Sache: Was in der Zwischenablage des Systems liegt, muss
68 zeichengleich mit dem sein, was dastand. */
69 const inZwischenablage = await app!.evaluate(({ clipboard }) => clipboard.readText());
70 expect(inZwischenablage).toBe(angezeigt);
71 });
72
73 test('nennt Fragenkennung, Katalogstand und Fassung', async () => {
74 /* Die drei Angaben, ohne die eine Meldung nicht zu bearbeiten ist. Die
75 Kennung ist dabei die wichtigere: 227 der 575 Fragen teilen ihre
76 amtliche Nummer mit einer anderen. */
77 const inhalt = await fenster.getByLabel('Meldetext').inputValue();
78
79 expect(inhalt).toMatch(/Kennung [IVX]+/u);
80 expect(inhalt).toContain('Stand 16.12.2024');
81 expect(inhalt).toContain('Programm: Fassung ');
82 });
83
84 test('verrät nichts über die lernende Person', async () => {
85 /* Der Text verlässt später das Gerät. Diese Prüfung läuft am echten
86 Profil und am echten Lernstand – anders als der Einheitentest, der
87 gegen erfundene Werte prüft. */
88 const inhalt = (await fenster.getByLabel('Meldetext').inputValue()).toLowerCase();
89
90 for (const verboten of ['standard', 'appdata', 'sqlite', '.db', 'lernstand von']) {
91 expect(inhalt).not.toContain(verboten);
92 }
93 });
94
95 test('klappt bei der nächsten Frage wieder ein', async () => {
96 /* Ein von Hand aufgeklapptes <details> bekommt React nicht wieder zu –
97 es vergleicht Eigenschaften mit Eigenschaften, nie mit dem DOM. Der
98 Block hängt deshalb an der Identität der Frage: Beim Wechsel entsteht
99 ein neues Element, und das ist zu.
100
101 Eigene Sitzung, damit dieser Test nicht davon abhängt, wo der vorige
102 aufgehört hat. */
103 await sitzungOeffnen();
104
105 const aufklapper = fenster.getByText('Fehler zu dieser Frage melden');
106 await aufklapper.scrollIntoViewIfNeeded();
107 await aufklapper.click();
108 await expect(fenster.getByLabel('Meldetext')).toBeVisible();
109
110 const auswahl = fenster.getByRole('checkbox').or(fenster.getByRole('radio'));
111 if ((await auswahl.count()) > 0) {
112 await auswahl.first().check();
113 await fenster.getByRole('button', { name: /^Antwort bestätigen$/u }).click();
114 } else {
115 await fenster.getByRole('button', { name: /^Musterantwort anzeigen$/u }).click();
116 await fenster.getByRole('button', { name: /^Gewusst$/u }).click();
117 }
118 await fenster.getByRole('button', { name: /^Nächste Frage$/u }).click();
119
120 /* Am Zustand des <details> selbst gemessen, nicht daran, ob das Feld im
121 DOM steht: Ein zugeklapptes <details> behält seine Kinder, es zeigt sie
122 nur nicht. Genau diese Feinheit hat den ersten Anlauf dieses Tests
123 fälschlich rot gemeldet. */
124 await expect(fenster.getByLabel('Meldetext')).toBeHidden();
125 expect(
126 await fenster
127 .locator('details.meldung')
128 .evaluate((element) => (element as HTMLDetailsElement).open),
129 ).toBe(false);
130
131 await expect(fenster.getByText('Fehler zu dieser Frage melden')).toBeVisible();
132
133 await zumStart();
134 });
135 });