FTP App
A framework-shipped App for SFTP and FTP/FTPS file transfer — verify that an application under test deposited a file on a remote server, or seed and tear down test data over a file-transfer connection, without shelling out to an external CLI.
← Back to overview · 🇩🇪 Deutsch · ← OS App · Email App →
What the FTP App provides
One ready-to-use App, _FtpClient, ships alongside _Skeleton, _ExampleWebApp, and the OS App — pick it in the scaffold wizard, or add its re-export by hand to an existing project. It wraps both modern SFTP (SSH-based, the enterprise de-facto standard) and legacy FTP/FTPS behind one unified set of steps, so the same TestCase code works regardless of which protocol the target server speaks.
Reach for it whenever a test needs to look at a remote file system rather than the application’s own UI or API — for example:
- Confirming that the application under test actually deposited an export/report file on an SFTP server after a background job ran
- Seeding input files a job or import feature will pick up, then cleaning them up afterwards
- Asserting on a file’s size, last-modified time, or content, without downloading it through the application’s own UI
_FtpClient is an ordinary App in the three-layer sense described in Concepts — a Control, TestSteps, and a References.ts barrel — the only difference from an App you’d write yourself is that the framework ships it pre-instantiated. All 14 TestSteps are named TS_Main_FtpClient_<Action>, per the usual naming convention.
Import the FtpClient Control into your test
Your project’s 2_Apps/1_Global/References.ts re-exports the App’s barrel, exactly like any other scaffolded App:
// 2_Apps/1_Global/References.ts
export * as FtpClient from '../_FtpClient/References';
With that in place, every step is callable from a TestCase through Project:
// 3_Cases/TC_MyFlow.spec.ts
import * as Project from '@GlobalRef';
await Project.FtpClient.TS_Main_FtpClient_CheckFileExists('reports', '/out/report.csv', true);
Unlike a TS_Main_* step written against your own App (see Build TestSteps), these steps take only their own business parameters — no leading refId, and no pageLogName/sectionName. None of the FTP steps address a screen element, so there is no reference screenshot to bind and no page context to log — the alias you chose at Connect time is simply the step’s first argument.
Connect once, operate on an alias, disconnect when done
Unlike a stateless Control such as FileEditor, an FTP/SFTP client is session-based — you open a connection, run a series of operations against it, then close it again. The App models this with a connection alias: a string you choose yourself when you call TS_Main_FtpClient_Connect, which every later operation step then takes as its first business parameter to identify which open connection to use. The alias is a plain string, so it reads cleanly in the rendered report — there is no live socket handle for your TestCase code to hold onto or pass around.
// Open a connection once, under a name you choose
await Project.FtpClient.TS_Main_FtpClient_Connect('reports', 'sftp', 'sftp.example.com', 22, username, password);
// ... run any number of operations against the same alias ...
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', './local/report.csv', '/out/report.csv');
// Close it when the flow is done
await Project.FtpClient.TS_Main_FtpClient_Disconnect('reports');
A connection left open leaks for the rest of the test run, so close it in a teardown hook rather than only at the end of the happy path — that way it still closes if an assertion earlier in the test fails:
// 3_Cases/TC_ReportExport.spec.ts
import * as Project from '@GlobalRef';
test.afterEach(async () => {
await Project.FtpClient.TS_Main_FtpClient_Disconnect('reports');
});
If a suite opens the same alias across multiple TestCases, close it once in test.afterAll instead of repeating afterEach in every file.
Choose a protocol
TS_Main_FtpClient_Connect’s protocol argument selects which server dialect and default port to use:
| Protocol | Default port | Notes |
|---|---|---|
sftp | 22 | SSH-based file transfer — the enterprise de-facto standard; recommended unless the target server only speaks legacy FTP |
ftp | 21 | Plain, unencrypted FTP — credentials and file content travel in clear text |
ftps | 21 | FTP with explicit TLS (AUTH TLS, negotiated over the plain control connection) — use this over ftp whenever the server supports it |
Pass an explicit port argument to override the default (e.g. a server listening on a non-standard port), or undefined to use the protocol’s default.
Manage connections (Connect / Disconnect)
| Step | What it does |
|---|---|
TS_Main_FtpClient_Connect | Opens a connection under the given alias, using the chosen protocol, host, port, username, and password |
TS_Main_FtpClient_Disconnect | Closes the connection registered under the given alias |
Transfer files
| Step | What it does |
|---|---|
TS_Main_FtpClient_UploadFile | Uploads a local file to a remote path over an open alias |
TS_Main_FtpClient_DownloadFile | Downloads a remote file to a local path over an open alias |
TS_Main_FtpClient_DeleteFile | Deletes a remote file over an open alias |
TS_Main_FtpClient_RenameFile | Renames or moves a remote file over an open alias |
TS_Main_FtpClient_MakeDirectory | Creates a remote directory (recursively) over an open alias |
TS_Main_FtpClient_RemoveDirectory | Removes a remote directory (recursively) over an open alias |
// Seed an input file a background job will pick up
await Project.FtpClient.TS_Main_FtpClient_MakeDirectory('reports', '/in');
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', './fixtures/import.csv', '/in/import.csv');
// Archive last run's export before this run writes a new one
await Project.FtpClient.TS_Main_FtpClient_RenameFile('reports', '/out/report.csv', '/out/archive/report-previous.csv');
// Clean up in a teardown block
await Project.FtpClient.TS_Main_FtpClient_DeleteFile('reports', '/in/import.csv');
Query remote state
TS_Main_FtpClient_ListDirectory, GetFileSize, GetLastModifiedTime, and ReadRemoteFile return the value they fetch, in addition to recording it in the test report — so a TestCase can consume it programmatically instead of only asserting on it:
| Step | Returns | What it does |
|---|---|---|
TS_Main_FtpClient_ListDirectory | FtpEntry[] | Lists a remote directory’s contents (name, type, size, modifiedAt) |
TS_Main_FtpClient_GetFileSize | number | Fetches a remote file’s size in bytes |
TS_Main_FtpClient_GetLastModifiedTime | Date | Fetches a remote file’s last-modified time |
TS_Main_FtpClient_ReadRemoteFile | string | Reads a remote file’s full content back as text |
// The app under test deposited an export — confirm it's non-empty and check its content
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');
// List a directory and inspect the entries directly
const entries = await Project.FtpClient.TS_Main_FtpClient_ListDirectory('reports', '/out');
await Core.Check.exists(entries.some((e) => e.name === 'report.csv'), true);
Assert against the remote server
| Step | What it does |
|---|---|
TS_Main_FtpClient_CheckFileExists | Asserts whether a remote file exists (or not, per an expected boolean) |
TS_Main_FtpClient_CheckDirectoryContains | Asserts that a remote directory contains an entry with a given name |
await Project.FtpClient.TS_Main_FtpClient_CheckFileExists('reports', '/out/report.csv', true);
await Project.FtpClient.TS_Main_FtpClient_CheckDirectoryContains('reports', '/out', 'report.csv');
A full worked TestCase
Putting connect, transfer, a return-value assertion, and disconnect together in one flow:
// 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('Connect and upload the report', 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('Verify the upload landed correctly', async () => {
const size = await Project.FtpClient.TS_Main_FtpClient_GetFileSize('reports', '/out/report.csv');
expect(size).toBeGreaterThan(0);
});
await Project.Core.Step.numberedStepBlock('Disconnect', async () => {
await Project.FtpClient.TS_Main_FtpClient_Disconnect('reports');
});
});
Source credentials from the password manager
Never hard-code password in a TestCase file — source it from the framework’s test-account credential store instead, the same way you’d source a login password for the application under test itself:
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);
Add the credential once with npx cc-testframework password set --username sftp-reports-user --stdin (see Add a credential), and every teammate who has run password init with the shared project secret can read it back — no plaintext server password in the repository, in a .env file, or in your shell history.
Dependencies ship with the framework
basic-ftp and ssh2-sftp-client are regular dependencies of the framework itself, not optional add-ons — installing the framework already installs them, so there is no extra setup step and no “not installed” branch to work around.
Migrating to v0.22.0
Breaking change. Before v0.22.0, every TS_Main_FtpClient_* step took the same leading refId, pageLogName, sectionName triple as a TS_Main_* step written against your own App (see Build TestSteps), with the connection alias following as the fourth argument. As of v0.22.0, alias moves up to become the first argument, and the leading three arguments are gone.
// before (≤ 0.21.0)
await Project.FtpClient.TS_Main_FtpClient_UploadFile(refId, pageLogName, sectionName, 'reports', localPath, remotePath);
// after (0.22.0)
await Project.FtpClient.TS_Main_FtpClient_UploadFile('reports', localPath, remotePath);
Why: the FTP steps address a remote server, not a screen element — there is no reference screenshot to bind for Self-Healing, and no page to name in the report. The leading three arguments existed only because every step used to share one factory with UI-facing steps; the FTP steps now use a dedicated, context-free step shape instead, and the ceremony that only made sense for UI steps drops away.
Update every FTP-App call site in your project by dropping the first three arguments — alias and every other argument keep their relative order.
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
No FTP connection registered for alias '...' | An operation step ran before TS_Main_FtpClient_Connect for that alias, or after TS_Main_FtpClient_Disconnect already closed it | Call Connect first, and check afterEach/afterAll ordering isn’t disconnecting too early |
| Connection refused / timed out | Wrong host or port, or the server isn’t reachable from the machine running the test | Verify host/port/protocol against a working manual client first (e.g. an SFTP CLI) |
| Authentication failed | Wrong username/password, or the account isn’t provisioned on that server | Re-check the credential via npx cc-testframework password get --username ...; confirm the account exists server-side |
ftps connection succeeds but transfers fail | Server requires an explicit passive-port range that isn’t reachable from the test runner (common behind NAT/firewalls) | Confirm the server’s passive-port range is reachable, or use sftp where available — it needs only the single SSH port |
Where to go next
- Build TestSteps — Return a value from a Get/Read step — the general contract behind
ListDirectory/GetFileSize/GetLastModifiedTime/ReadRemoteFile - Writing Your First TestCase — composing steps from multiple Apps into one TestCase
- Credential Management — storing the server password instead of hard-coding it
- OS App — the other framework-shipped utility Apps (files, shell, system info, Windows Registry)
- Email App — verifying a signup mail, OTP, or confirmation link
- FAQ — troubleshooting setup issues
📧 Questions? Contact: jens.szelag@itsbusiness.ch
itsbusiness AG · Bern · Switzerland