FTP-App
Eine vom Framework mitgelieferte App für SFTP- und FTP/FTPS-Dateitransfer — verifiziere, dass eine zu testende Anwendung eine Datei auf einem entfernten Server abgelegt hat, oder seede und räume Test-Daten über eine Dateitransfer-Verbindung auf, ohne eine externe CLI aufzurufen.
← Zurück zur Übersicht · 🇬🇧 English · ← OS-App · Email-App →
Was die FTP-App bietet
Eine sofort nutzbare App, _FtpClient, wird neben _Skeleton, _ExampleWebApp und der OS-App mitgeliefert — wähle sie im Scaffold-Wizard, oder füge ihren Re-Export von Hand zu einem bestehenden Projekt hinzu. Sie kapselt sowohl modernes SFTP (SSH-basiert, der De-facto-Enterprise-Standard) als auch klassisches FTP/FTPS hinter einem einheitlichen Set von Steps, sodass derselbe TestCase-Code unabhängig davon funktioniert, welchen Protokoll-Dialekt der Ziel-Server spricht.
Greife darauf zurück, wann immer ein Test auf ein entferntes Dateisystem schauen muss statt auf die eigene UI oder API der Anwendung — zum Beispiel:
- Bestätigen, dass die zu testende Anwendung nach einem Hintergrund-Job tatsächlich eine Export-/Report-Datei auf einem SFTP-Server abgelegt hat
- Input-Dateien seeden, die ein Job oder eine Import-Funktion abholt, und sie danach wieder aufräumen
- Auf Größe, letzte Änderungszeit oder Inhalt einer Datei prüfen, ohne sie über die UI der Anwendung selbst herunterzuladen
_FtpClient ist eine gewöhnliche App im Drei-Schichten-Sinn aus Konzepte — ein Control, TestSteps und ein References.ts-Barrel — der einzige Unterschied zu einer selbst geschriebenen App ist, dass das Framework sie vorinstanziiert mitliefert. Alle 14 TestSteps sind benannt nach TS_Main_FtpClient_<Action>, gemäß der üblichen Naming-Konvention.
Das FtpClient-Control in deinen Test importieren
Die 2_Apps/1_Global/References.ts deines Projekts re-exportiert das Barrel der App, genau wie jede andere gescaffoldete App:
// 2_Apps/1_Global/References.ts
export * as FtpClient from '../_FtpClient/References';
Damit ist jeder Step über Project aus einem TestCase aufrufbar:
// 3_Cases/TC_MyFlow.spec.ts
import * as Project from '@GlobalRef';
await Project.FtpClient.TS_Main_FtpClient_CheckFileExists('reports', '/out/report.csv', true);
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 FTP-Steps adressiert ein Screen-Element, daher gibt es kein Referenz-Screenshot zu binden und keinen Seiten-Kontext zu loggen — der beim Connect gewählte Alias ist schlicht das erste Argument des Steps.
Einmal verbinden, über einen Alias arbeiten, am Ende trennen
Anders als ein zustandsloses Control wie FileEditor ist ein FTP-/SFTP-Client sitzungsbasiert — du öffnest eine Verbindung, führst eine Reihe von Operationen dagegen aus, und schließt sie danach wieder. Die App modelliert das über einen Connection-Alias: einen String, den du selbst beim Aufruf von TS_Main_FtpClient_Connect wählst, den jeder spätere Operations-Step dann als erstes Business-Argument entgegennimmt, um zu identifizieren, welche offene Verbindung genutzt wird. Der Alias ist ein einfacher String, daher liest er sich sauber im gerenderten Report — es gibt kein Live-Socket-Handle, das dein TestCase-Code halten oder weitergeben müsste.
// Eine Verbindung einmal öffnen, unter einem selbst gewählten Namen
await Project.FtpClient.TS_Main_FtpClient_Connect('reports', 'sftp', 'sftp.example.com', 22, username, password);
// ... beliebig viele Operationen gegen denselben Alias ausführen ...
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', './local/report.csv', '/out/report.csv');
// Am Ende des Ablaufs schließen
await Project.FtpClient.TS_Main_FtpClient_Disconnect('reports');
Eine offen gebliebene Verbindung leckt für den Rest des Test-Laufs, daher schließe sie in einem Teardown-Hook statt nur am Ende des Happy Path — so wird sie auch geschlossen, wenn eine frühere Assertion im Test fehlschlägt:
// 3_Cases/TC_ReportExport.spec.ts
import * as Project from '@GlobalRef';
test.afterEach(async () => {
await Project.FtpClient.TS_Main_FtpClient_Disconnect('reports');
});
Öffnet eine Suite denselben Alias über mehrere TestCases hinweg, schließe ihn einmal in test.afterAll, statt afterEach in jeder Datei zu wiederholen.
Ein Protokoll wählen
Das protocol-Argument von TS_Main_FtpClient_Connect wählt, welchen Server-Dialekt und Standard-Port genutzt wird:
| Protokoll | Standard-Port | Hinweise |
|---|---|---|
sftp | 22 | SSH-basierter Dateitransfer — der De-facto-Enterprise-Standard; empfohlen, außer der Ziel-Server spricht nur klassisches FTP |
ftp | 21 | Einfaches, unverschlüsseltes FTP — Zugangsdaten und Dateiinhalt reisen im Klartext |
ftps | 21 | FTP mit explizitem TLS (AUTH TLS, über die einfache Steuerverbindung ausgehandelt) — nutze das über ftp, wann immer der Server es unterstützt |
Übergib ein explizites port-Argument, um den Standard zu überschreiben (z.B. ein Server, der auf einem nicht-standardmäßigen Port lauscht), oder undefined, um den Standard des Protokolls zu nutzen.
Verbindungen verwalten (Connect / Disconnect)
| Step | Was er tut |
|---|---|
TS_Main_FtpClient_Connect | Öffnet eine Verbindung unter dem gegebenen Alias, mit gewähltem Protokoll, Host, Port, Benutzername und Passwort |
TS_Main_FtpClient_Disconnect | Schließt die unter dem gegebenen Alias registrierte Verbindung |
Dateien übertragen
| Step | Was er tut |
|---|---|
TS_Main_FtpClient_UploadFile | Lädt eine lokale Datei zu einem entfernten Pfad über einen offenen Alias hoch |
TS_Main_FtpClient_DownloadFile | Lädt eine entfernte Datei zu einem lokalen Pfad über einen offenen Alias herunter |
TS_Main_FtpClient_DeleteFile | Löscht eine entfernte Datei über einen offenen Alias |
TS_Main_FtpClient_RenameFile | Benennt eine entfernte Datei um oder verschiebt sie über einen offenen Alias |
TS_Main_FtpClient_MakeDirectory | Legt ein entferntes Verzeichnis an (rekursiv) über einen offenen Alias |
TS_Main_FtpClient_RemoveDirectory | Entfernt ein entferntes Verzeichnis (rekursiv) über einen offenen Alias |
// Eine Input-Datei seeden, die ein Hintergrund-Job abholt
await Project.FtpClient.TS_Main_FtpClient_MakeDirectory('reports', '/in');
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', './fixtures/import.csv', '/in/import.csv');
// Den Export des letzten Laufs archivieren, bevor dieser Lauf einen neuen schreibt
await Project.FtpClient.TS_Main_FtpClient_RenameFile('reports', '/out/report.csv', '/out/archive/report-previous.csv');
// Aufräumen in einem Teardown-Block
await Project.FtpClient.TS_Main_FtpClient_DeleteFile('reports', '/in/import.csv');
Entfernten Zustand abfragen
TS_Main_FtpClient_ListDirectory, GetFileSize, GetLastModifiedTime und ReadRemoteFile geben den abgerufenen Wert zurück, zusätzlich dazu, dass sie ihn im Test-Report festhalten — sodass ein TestCase ihn programmatisch weiterverarbeiten kann, statt nur darauf zu prüfen:
| Step | Rückgabe | Was er tut |
|---|---|---|
TS_Main_FtpClient_ListDirectory | FtpEntry[] | Listet den Inhalt eines entfernten Verzeichnisses (name, type, size, modifiedAt) |
TS_Main_FtpClient_GetFileSize | number | Ruft die Größe einer entfernten Datei in Bytes ab |
TS_Main_FtpClient_GetLastModifiedTime | Date | Ruft die letzte Änderungszeit einer entfernten Datei ab |
TS_Main_FtpClient_ReadRemoteFile | string | Liest den vollständigen Inhalt einer entfernten Datei als Text zurück |
// Die zu testende App hat einen Export abgelegt — prüfen, dass er nicht leer ist, und den Inhalt prüfen
const size = await Project.FtpClient.TS_Main_FtpClient_GetFileSize('reports', '/out/report.csv');
await Core.Check.exists(size > 0, true);
const content = await Project.FtpClient.TS_Main_FtpClient_ReadRemoteFile('reports', '/out/report.csv');
await Core.Check.textOrValueIsSet(content, true, 'Acme Corp');
// Ein Verzeichnis listen und die Einträge direkt untersuchen
const entries = await Project.FtpClient.TS_Main_FtpClient_ListDirectory('reports', '/out');
await Core.Check.exists(entries.some((e) => e.name === 'report.csv'), true);
Gegen den entfernten Server prüfen
| Step | Was er tut |
|---|---|
TS_Main_FtpClient_CheckFileExists | Prüft, ob eine entfernte Datei existiert (oder nicht, gemäß einem erwarteten Boolean) |
TS_Main_FtpClient_CheckDirectoryContains | Prüft, dass ein entferntes Verzeichnis einen Eintrag mit gegebenem Namen enthält |
await Project.FtpClient.TS_Main_FtpClient_CheckFileExists('reports', '/out/report.csv', true);
await Project.FtpClient.TS_Main_FtpClient_CheckDirectoryContains('reports', '/out', 'report.csv');
Ein vollständiger TestCase
Connect, Transfer, eine Rückgabewert-Assertion und Disconnect in einem Ablauf zusammengeführt:
// 3_Cases/TC_ReportExport.spec.ts
import * as Project from '@GlobalRef';
Project.Core.test('TC_ReportExport', async () => {
Project.Core.Step.setCurrentTestCaseName('TC_ReportExport');
await Project.Core.Step.numberedStepBlock('Verbinden und den Report hochladen', async () => {
await Project.FtpClient.TS_Main_FtpClient_Connect('reports', 'sftp', host, 22, user, password);
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', './local/report.csv', '/out/report.csv');
});
await Project.Core.Step.numberedStepBlock('Prüfen, dass der Upload korrekt gelandet ist', async () => {
const size = await Project.FtpClient.TS_Main_FtpClient_GetFileSize('reports', '/out/report.csv');
expect(size).toBeGreaterThan(0);
});
await Project.Core.Step.numberedStepBlock('Trennen', async () => {
await Project.FtpClient.TS_Main_FtpClient_Disconnect('reports');
});
});
Zugangsdaten aus dem Password-Manager beziehen
Hardcode password niemals in einer TestCase-Datei — beziehe es stattdessen aus dem Test-Account-Zugangsdaten-Speicher des Frameworks, genauso wie du ein Login-Passwort für die zu testende Anwendung selbst beziehen würdest:
import * as Core from '@Core/References';
const password = await Core.PasswordManager.getPassword('sftp-reports-user');
await Project.FtpClient.TS_Main_FtpClient_Connect('reports', 'sftp', host, 22, 'reports-user', password);
Lege die Zugangsdaten einmal an mit npx cc-testframework password set --username sftp-reports-user --stdin (siehe Eine Zugangsdaten anlegen), und jedes Team-Mitglied, das password init mit dem geteilten Projekt-Secret ausgeführt hat, kann sie zurücklesen — kein Klartext-Server-Passwort im Repository, in einer .env-Datei oder in deiner Shell-History.
Dependencies liefert das Framework mit
basic-ftp und ssh2-sftp-client sind reguläre Dependencies des Frameworks selbst, keine optionalen Zusatz-Pakete — die Installation des Frameworks installiert sie bereits mit, daher gibt es keinen zusätzlichen Setup-Schritt und keinen “nicht installiert”-Zweig, um den man sich kümmern müsste.
Migration auf v0.22.0
Breaking Change. Vor v0.22.0 nahm jeder TS_Main_FtpClient_*-Step dasselbe führende refId-, pageLogName-, sectionName-Tripel entgegen wie ein TS_Main_*-Step gegen deine eigene App (siehe TestSteps bauen), mit dem Connection-alias als viertem Argument danach. Seit v0.22.0 rückt alias an die erste Position, und die drei führenden Argumente entfallen.
// vorher (≤ 0.21.0)
await Project.FtpClient.TS_Main_FtpClient_UploadFile(refId, pageLogName, sectionName, 'reports', localPath, remotePath);
// nachher (0.22.0)
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', localPath, remotePath);
Warum: Die FTP-Steps adressieren einen entfernten Server, kein 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 FTP-Steps nutzen jetzt stattdessen eine eigene, kontextfreie Step-Form, und die Ceremony, die nur für UI-Steps Sinn ergab, entfällt.
Aktualisiere jede FTP-App-Aufrufstelle in deinem Projekt, indem du die ersten drei Argumente entfernst — alias und jedes andere Argument behalten ihre relative Reihenfolge.
Typische Fehler und ihre Behebung
| Fehler | Ursache | Fix |
|---|---|---|
No FTP connection registered for alias '...' | Ein Operations-Step lief vor TS_Main_FtpClient_Connect für diesen Alias, oder nachdem TS_Main_FtpClient_Disconnect ihn bereits geschlossen hat | Zuerst Connect aufrufen, und prüfen, ob afterEach/afterAll-Reihenfolge nicht zu früh trennt |
| Verbindung verweigert / Timeout | Falscher Host oder Port, oder der Server ist von der Test-Maschine aus nicht erreichbar | Host/Port/Protokoll zuerst gegen einen funktionierenden manuellen Client prüfen (z.B. eine SFTP-CLI) |
| Authentifizierung fehlgeschlagen | Falscher Benutzername/Passwort, oder der Account ist auf diesem Server nicht angelegt | Zugangsdaten per npx cc-testframework password get --username ... gegenprüfen; Account server-seitig bestätigen |
ftps-Verbindung gelingt, aber Transfers schlagen fehl | Server verlangt einen expliziten Passiv-Port-Bereich, der vom Test-Runner aus nicht erreichbar ist (üblich hinter NAT/Firewalls) | Prüfen, dass der Passiv-Port-Bereich des Servers erreichbar ist, oder sftp nutzen, wo verfügbar — es braucht nur den einen SSH-Port |
Wo es weitergeht
- TestSteps bauen — Einen Wert aus einem Get-/Read-Step liefern — der allgemeine Vertrag hinter
ListDirectory/GetFileSize/GetLastModifiedTime/ReadRemoteFile - Deinen ersten TestCase schreiben — Steps aus mehreren Apps zu einem TestCase komponieren
- Credential Management — das Server-Passwort speichern statt es hart zu codieren
- OS-App — die anderen vom Framework mitgelieferten Utility-Apps (Dateien, Shell, System-Infos, Windows-Registry)
- Email-App — eine Signup-Mail, OTP oder Bestätigungslink verifizieren
- FAQ — Setup-Probleme lösen
📧 Fragen? Kontakt: jens.szelag@itsbusiness.ch
itsbusiness AG · Bern · Schweiz