OS-App

Zwei vom Framework mitgelieferte Apps für Datei-, Shell-, System-Info- und Windows-Registry-Interaktion — die Utility-Arbeit, die fast jede E2E-Suite braucht, ohne eigene Controls dafür zu schreiben.

← Zurück zur Übersicht · 🇬🇧 English · ← Credential Management · FTP-App →


Was die OS-App bietet

Zwei sofort nutzbare Apps werden neben _Skeleton und _ExampleWebApp mitgeliefert — wähle sie im Scaffold-Wizard, oder füge ihren Re-Export von Hand zu einem bestehenden Projekt hinzu:

App Plattform Controls Zweck
_OS_Common Web, Desktop, Mobile — jedes OS FileEditor, SystemInfo, Shell Dateien, Systemzeit/-version/-hostname, beliebige Shell-Kommandos
_OS_Windows Nur Windows Registry, SystemInfoWindows Windows-Registry lesen/schreiben, Windows-Update-Status, Liste installierter Programme, Systemzeit

Beide sind gewöhnliche Apps im Drei-Schichten-Sinn aus Konzepte — Controls, TestSteps und ein References.ts-Barrel — der einzige Unterschied zu einer selbst geschriebenen App ist, dass das Framework sie vorinstanziiert mitliefert. 13 TestSteps kommen aus _OS_Common, 9 aus _OS_Windows, alle benannt nach TS_Main_<Control>_<Action> gemäß der üblichen Naming-Konvention.


Die OS-Controls in deinen Test importieren

Die 2_Apps/1_Global/References.ts deines Projekts re-exportiert das Barrel jeder App, genau wie jede andere gescaffoldete App:

// 2_Apps/1_Global/References.ts
export * as OSCommon from '../_OS_Common/References';
export * as OSWindows from '../_OS_Windows/References';

Damit ist jeder Step über Project aus einem TestCase aufrufbar:

// 3_Cases/TC_MyFlow.spec.ts
import * as Project from '@GlobalRef';

await Project.OSCommon.TS_Main_FileEditor_CheckFileExists('./test-data/seed.json');

Anders als ein TS_Main_*-Step gegen deine eigene App (siehe TestSteps bauen) nehmen diese Steps nur ihre eigenen Business-Parameter entgegen — keine führende refId, kein pageLogName/sectionName. Keiner der OS-Steps adressiert ein Screen-Element, daher gibt es kein Referenz-Screenshot zu binden und keinen Seiten-Kontext zu loggen — die Call-Shape ist schlicht TS_Main_<Control>_<Action>(...businessArgs).

OSWindows importiert auch auf Linux und macOS ohne Fehler — nur der Aufruf einer seiner Methoden wirft auf einer Nicht-Windows-Maschine. Siehe Windows-exklusive Controls weiter unten.


Mit Dateien arbeiten (FileEditor)

Step Was er tut
TS_Main_FileEditor_CheckFileExists Gibt zurück, ob am angegebenen Pfad eine Datei existiert
TS_Main_FileEditor_CreateTextfile Schreibt eine Text-Datei, erstellt fehlende Eltern-Ordner
TS_Main_FileEditor_ReadTextfile Liest den vollständigen Inhalt einer Text-Datei als String zurück
TS_Main_FileEditor_AppendTextfile Hängt an eine bestehende Text-Datei an, ohne sie zu überschreiben
TS_Main_FileEditor_RenameFile Benennt eine Datei um oder verschiebt sie
TS_Main_FileEditor_GetFileModifyTime Gibt den Zeitstempel der letzten Änderung einer Datei zurück
TS_Main_FileEditor_DeleteFile Löscht eine Datei

Ein typischer Alltags-Ablauf — Test-Daten seeden, die zu testende App eine Output-Datei schreiben lassen, darauf prüfen, dann aufräumen:

// Eine Fixture-Datei seeden, bevor der Ablauf startet
await Project.OSCommon.TS_Main_FileEditor_CreateTextfile('./test-data/import.csv', 'id,name\n1,Acme Corp');

// Die zu testende App holt sich das ab und schreibt später eine Export-Datei — prüfen, dass sie existiert
await Project.OSCommon.TS_Main_FileEditor_CheckFileExists('./output/export.csv');

// Zurücklesen und den Inhalt prüfen
const csv = await Project.OSCommon.TS_Main_FileEditor_ReadTextfile('./output/export.csv');
await Core.Check.textOrValueIsSet(csv, true, 'Acme Corp');

// Einen Lauf-Marker an ein gemeinsames Log anhängen, statt es zu überschreiben
await Project.OSCommon.TS_Main_FileEditor_AppendTextfile('./output/run.log', `\n[${new Date().toISOString()}] flow finished`);

// Den Export dieses Laufs archivieren, bevor der nächste Lauf startet
await Project.OSCommon.TS_Main_FileEditor_RenameFile('./output/export.csv', './output/archive/export-previous.csv');

// Aufräumen in einem afterEach-/Teardown-Block
await Project.OSCommon.TS_Main_FileEditor_DeleteFile('./test-data/import.csv');

TS_Main_FileEditor_GetFileModifyTime gibt die letzte Änderungszeit einer Datei als Date zurück — nützlich für eine Frische-Prüfung:

// Einen Zeitstempel erfassen, bevor die zu testende App ihren Export schreibt
const before = new Date();
await Project.OSCommon.TS_Main_FileEditor_CheckFileExists('./output/export.csv');

// Belegen, dass die Datei in diesem Lauf tatsächlich neu geschrieben wurde und nicht von einem vorherigen Lauf übrig ist
const modifiedAt = await Project.OSCommon.TS_Main_FileEditor_GetFileModifyTime('./output/export.csv');
await Core.Check.exists(modifiedAt > before, true);

System-Infos abfragen (SystemInfo)

Step Was er tut
TS_Main_SystemInfo_GetSystemTime Gibt die aktuelle lokale Zeit als Date zurück
TS_Main_SystemInfo_CheckSystemTime Vergleicht die lokale Zeit mit einer externen Zeit-Provider-URL, innerhalb einer erlaubten Drift
TS_Main_SystemInfo_GetOSVersion Gibt Plattform, Release und Version des OS zurück, auf dem der Test läuft
TS_Main_SystemInfo_GetHostname Gibt den Hostnamen der Maschine zurück

TS_Main_SystemInfo_CheckSystemTime ist derjenige Step, der für ein konkretes Problem gebaut wurde: Eine Test-Runner-Uhr, die unbemerkt aus dem Takt gerät, bricht lautlos alles, was sich auf Zeitstempel verlässt (Token-Ablauf, geplante Jobs, “innerhalb der letzten Minute erstellt”-Assertions), ohne einen offensichtlichen Fehler, der auf die Uhr selbst zeigt.

// Schnell fehlschlagen, wenn die Uhr dieser Maschine mehr als 5 Sekunden von einer vertrauenswürdigen externen Quelle abweicht
const drift = await Project.OSCommon.TS_Main_SystemInfo_CheckSystemTime('https://timeapi.io/api/Time/current/zone?timeZone=UTC', 5);
await Core.Check.exists(drift.withinTolerance, true);

// GetSystemTime gibt ein einfaches `Date` zurück, das du festhalten und in eigenen Vergleichen wiederverwenden kannst
const now = await Project.OSCommon.TS_Main_SystemInfo_GetSystemTime();

// OS-Version und Hostname kommen als strukturierte Werte zurück, bereit für eigene Diagnosen
const osVersion = await Project.OSCommon.TS_Main_SystemInfo_GetOSVersion();
const hostname = await Project.OSCommon.TS_Main_SystemInfo_GetHostname();
Core.Logger.info(`${now.toISOString()} — läuft auf ${osVersion.platform} ${osVersion.release} (${hostname})`);

Shell-Befehle starten (Shell)

Step Was er tut
TS_Main_Shell_ExecuteCommand Führt ein einzelnes Shell-Kommando aus, gibt stdout/stderr/Exit-Code zurück, ohne bei einem Nicht-Null-Exit zu werfen
TS_Main_Shell_RunScript Führt eine Skript-Datei aus, wählt den Interpreter anhand ihrer Endung (.sh, .ps1, .bat/.cmd)
// Eine Datenbank-Fixture vorbereiten, bevor die Suite läuft
const result = await Project.OSCommon.TS_Main_Shell_ExecuteCommand('npm run seed:test-db');
await Core.Check.exists(result.exitCode === 0, true);

// Ein gepflegtes Setup-Skript ausführen, statt seine Logik inline zu duplizieren
await Project.OSCommon.TS_Main_Shell_RunScript('./scripts/prepare-fixtures.sh', ['--env', 'test']);

Behandle jedes aus Nutzer- oder Test-Daten zusammengesetzte Kommando als nicht vertrauenswürdig. TS_Main_Shell_ExecuteCommand/TS_Main_Shell_RunScript laufen exakt so, wie übergeben, durch die OS-Shell — einen unbereinigten Wert per String-Konkatenation ins Kommando einzubauen (z.B. einen aus einer Fixture-Datei oder einer externen API-Antwort gelesenen Wert) öffnet dasselbe Injection-Risiko wie eine SQL-Abfrage aus unbereinigtem Input zu bauen. Halte den Kommando-String selbst fest codiert oder aus eigenen, vertrauenswürdigen Skripten stammend, und übergib variable Daten als separate Argumente, statt sie in den Kommando-Text einzufügen.


Windows-exklusive Controls

Erfordert die Windows-Plattform. Registry und SystemInfoWindows funktionieren nur, wenn der Test-Runner selbst unter Windows läuft — nicht, wenn die zu testende App zufällig eine Windows-Desktop-App ist, die von einem anderen Host aus getestet wird. Das Importieren von Project.OSWindows gelingt auf Linux und macOS; der Aufruf einer der folgenden Methoden auf einer Nicht-Windows-Maschine wirft This Control requires the Windows platform — eine Suite, die diese Steps bedingt nutzt, sollte den Aufruf zuerst mit einer Plattform-Prüfung absichern.

Die Windows-Registry lesen und schreiben

Step Was er tut
TS_Main_Registry_CheckKeyExists Gibt zurück, ob ein Registry-Key existiert
TS_Main_Registry_CheckValueContains Gibt zurück, ob ein Wert einen erwarteten Teilstring enthält
TS_Main_Registry_ReadValue Liest einen Registry-Wert
TS_Main_Registry_AddKey Legt einen Registry-Key an (No-op, falls er schon existiert)
TS_Main_Registry_DeleteKey Löscht einen Registry-Key und alle seine Sub-Keys

Jeder Step akzeptiert eine der 5 Standard-Hives — HKLM, HKCU, HKCR, HKU, HKCC — als erstes Argument, dann den Key-Pfad:

// Prüfen, dass die zu testende App ihre "Angemeldet bleiben"-Einstellung persistiert hat
const remembersLogin = await Project.OSWindows.TS_Main_Registry_CheckValueContains(
    'HKCU', 'Software\\YourApp\\Settings', 'RememberLogin', 'true',
);
await Core.Check.exists(remembersLogin, true);

// Einen Wert direkt zurücklesen, um darauf zu prüfen oder ihn in einen Folgeschritt einzuspeisen
const theme = await Project.OSWindows.TS_Main_Registry_ReadValue('HKCU', 'Software\\YourApp\\Settings', 'Theme');
await Core.Check.textOrValueIsSet(theme, true, 'Dark');

// Eine Config-Persistenz-Test-Fixture aufsetzen, danach wieder aufräumen
await Project.OSWindows.TS_Main_Registry_AddKey('HKCU', 'Software\\YourApp\\TestFixture');
await Project.OSWindows.TS_Main_Registry_DeleteKey('HKCU', 'Software\\YourApp\\TestFixture');

Windows-spezifische System-Infos abfragen

Step Was er tut
TS_Main_SystemInfoWindows_GetWindowsUpdateInformation Gibt letzte Check-/Install-Zeiten zurück, sowie ob ein Neustart aussteht
TS_Main_SystemInfoWindows_CheckAppNotExists Gibt zurück, ob ein Programm in der Liste installierter Programme fehlt
TS_Main_SystemInfoWindows_ListInstalledPrograms Gibt die vollständige Liste installierter Programme zurück (Name, Version, Publisher)
// Prüfen, dass der Uninstall-Pfad deines Installers das Programm tatsächlich entfernt hat
const stillInstalled = await Project.OSWindows.TS_Main_SystemInfoWindows_CheckAppNotExists('Your App Name');
await Core.Check.exists(stillInstalled, true);

// GetWindowsUpdateInformation gibt ein strukturiertes Objekt zurück — vor einem Clean-Update-Test prüfen, dass kein Neustart aussteht
const updateInfo = await Project.OSWindows.TS_Main_SystemInfoWindows_GetWindowsUpdateInformation();
await Core.Check.exists(updateInfo.pendingRebootRequired, false);

// ListInstalledPrograms gibt die vollständige Liste zurück — prüfen, dass deine App auf der Maschine gelandet ist
const programs = await Project.OSWindows.TS_Main_SystemInfoWindows_ListInstalledPrograms();
await Core.Check.exists(programs.some((p) => p.displayName === 'Your App Name'), true);

Systemzeit umstellen (destruktiv — erfordert Admin-Rechte)

TS_Main_SystemInfoWindows_SetSystemTime stellt die tatsächliche Systemuhr der Maschine um — es gibt keinen Dry-Run- oder Simulations-Modus. Das erfordert Administrator-Rechte auf der Maschine, die den Test ausführt, und die meisten CI-Runner verweigern diese Berechtigung bewusst. Reserviere ihn für eine dedizierte, wegwerfbare Windows-VM, die eigens für Uhr-abhängige Test-Szenarien genutzt wird (Ablauf-Logik, geplante Tasks, Sommerzeit-Übergänge) — richte ihn nie gegen eine gemeinsam genutzte Entwickler-Maschine oder einen CI-Runner, von dem andere Tests abhängen.

// Nur jemals gegen eine wegwerfbare, dedizierte Test-VM ausführen
await Project.OSWindows.TS_Main_SystemInfoWindows_SetSystemTime(new Date('2026-12-31T23:59:00Z'));

OS-Steps mit deinen App-Tests kombinieren

Die OS-App ist am nützlichsten, wenn sie im selben Ablauf mit einem TestStep gegen deine eigene App kombiniert wird — zum Beispiel um zu verifizieren, dass deine Windows-Desktop-Anwendung beim Start tatsächlich eine Einstellung aus der Registry liest:

// 3_Cases/TC_ConfigPersistence.spec.ts
import * as Project from '@GlobalRef';

Project.Core.test('TC_ConfigPersistence', async () => {
    Project.Core.Step.setCurrentTestCaseName('TC_ConfigPersistence');

    await Project.Core.Step.numberedStepBlock('Bekannten Config-Wert vorbereiten', async () => {
        await Project.OSWindows.TS_Main_Registry_AddKey('HKCU', 'Software\\YourApp\\Settings');
        await Project.OSWindows.TS_Main_Registry_ReadValue('HKCU', 'Software\\YourApp\\Settings', 'Theme');
    });

    await Project.Core.Step.numberedStepBlock('App starten und prüfen, dass sie die Einstellung übernommen hat', async () => {
        await Project.YourApp.TS_Execution_Start();
        await Project.YourApp.TS_Main_CheckActiveTheme('', '', '', 'Dark');
        await Project.YourApp.TS_Execution_Close();
    });

    await Project.Core.Step.numberedStepBlock('Registry-Fixture aufräumen', async () => {
        await Project.OSWindows.TS_Main_Registry_DeleteKey('HKCU', 'Software\\YourApp\\Settings');
    });
});

Migration auf v0.22.0

Breaking Change. Vor v0.22.0 nahm jeder _OS_Common/_OS_Windows-Step dasselbe führende refId-, pageLogName-, sectionName-Tripel entgegen wie ein TS_Main_*-Step gegen deine eigene App (siehe TestSteps bauen). Seit v0.22.0 nehmen alle TS_Main_FileEditor_*-, TS_Main_SystemInfo_*-, TS_Main_Shell_*-, TS_Main_Registry_*- und TS_Main_SystemInfoWindows_*-Steps nur noch ihre eigenen Business-Parameter entgegen.

// vorher (≤ 0.21.0)
await Project.OSCommon.TS_Main_FileEditor_ReadTextfile(refId, pageLogName, sectionName, filePath);

// nachher (0.22.0)
await Project.OSCommon.TS_Main_FileEditor_ReadTextfile(filePath);

Warum: Keiner dieser Steps adressiert ein Screen-Element — es gibt kein Referenz-Screenshot, das für Self-Healing gebunden werden müsste, und keine Seite, die im Report benannt werden müsste. Die drei führenden Argumente existierten nur, weil früher jeder Step dieselbe Factory wie UI-Steps nutzte; die OS-Steps nutzen jetzt stattdessen eine eigene, kontextfreie Step-Form, und die Ceremony, die nur für UI-Steps Sinn ergab, entfällt.

Aktualisiere jede OS-App-Aufrufstelle in deinem Projekt, indem du die ersten drei Argumente entfernst — sonst ändert sich am Step nichts (Name, restliche Argumente, Rückgabewert bleiben gleich).


Typische Fehler und ihre Behebung

Fehler Ursache Fix
ENOENT FileEditor wurde auf einen Pfad gerichtet, dessen Eltern-Ordner nicht existiert (bei readTextfile/deleteFile/renameFile) oder die Datei selbst existiert noch nicht createTextfile erstellt fehlende Eltern-Ordner automatisch; bei den anderen Steps den Pfad zuerst mit TS_Main_FileEditor_CheckFileExists prüfen
EACCES Der OS-Benutzer, unter dem die Tests laufen, hat keine Berechtigung, den Ziel-Pfad zu lesen/schreiben/löschen Auf einen Pfad zeigen, den dein Test-Benutzer besitzt (z.B. einen projekt-lokalen test-data/-Ordner), statt auf einen System- oder fremden Benutzer-Ordner
This Control requires the Windows platform Eine Registry-/SystemInfoWindows-Methode wurde auf Linux oder macOS aufgerufen Den Aufruf mit einer Plattform-Prüfung absichern, oder diesen TestStep nur in einem Windows-only-Test-Projekt/CI-Job einbinden
winreg nicht installiert Registry wird unter Windows genutzt, aber npm install lief zuerst auf einer Nicht-Windows-Maschine (winreg ist ein optionalDependencies-Eintrag) npm install erneut von einer Windows-Maschine oder in einem Windows-CI-Job ausführen, damit die optionale native Dependency tatsächlich installiert wird
PowerShell nicht verfügbar SystemInfoWindows läuft auf einer abgespeckten Windows-Umgebung ohne powershell.exe im PATH Sicherstellen, dass PowerShell installiert und im PATH ist — Standard bei jeder vollen Windows-Desktop-/Server-Installation, aber manchmal fehlend bei minimalen Container-Images

Wo es weitergeht

  • TestSteps bauen — die allgemeine TS_Main_*-Call-Shape und Naming-Konvention, denen diese Steps folgen
  • Deinen ersten TestCase schreiben — Steps aus mehreren Apps zu einem TestCase komponieren
  • Skeleton-Konventionen — die Naming-/Signatur-Regeln, denen die mitgelieferten _OS_Common-/_OS_Windows-Dateien folgen
  • Credential Management — die anderen vom Framework mitgelieferten Speicher-/Secret-Utilities
  • FTP-App — die andere vom Framework mitgelieferte Utility-App, für SFTP- und FTP/FTPS-Dateitransfer
  • FAQ — Setup-Probleme lösen

📧 Fragen? Kontakt: jens.szelag@itsbusiness.ch

itsbusiness AG · Bern · Schweiz