waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
| 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 | }); |