Testy aplikacji w Ferryt rzadko kiedy kończą się na weryfikacji interfejsu użytkownika. Ferryt umożliwia tworzenie arkuszy kalkulacyjnych, dokumentów PDF czy Word, a te również wymagają przetestowania. Ostatecznie, to przecież wygenerowane pismo jest wysyłane do klienta, a tabela z raportem jest używana przez pracowników. Niejako odsuwając uwagę od samego wniosku Ferryt, przybliżmy temat operowania na plikach zewnętrznych, a dokładniej jak można wykorzystać ich potencjał w automatyzacji testów w Playwright w środowisku Node.js. Poruszone zostaną następujące zagadnienia:
- organizacja danych testowych,
- generowanie tabeli wsadowych,
- generowanie przykładowych załączników,
- weryfikacja arkuszy kalkulacyjnych,
- weryfikacja treści dokumentów PDF i Word.
Od hardkodowania do DDT
Rozważanie nad zastosowaniem zewnętrznych plików w automatyzacji testów warto zacząć od danych wejściowych, czyli tego, co zasila proces i od czego zależy jego przepływ. W kontekście tworzenia skryptów automatycznych jednym z podstawowych błędów jest zaszywanie danych testowych bezpośrednio w kodzie, czyli tzw. hardkodowanie. Początkowo to podejście wydaje się bardzo wygodne i szybkie. Jednak w praktyce równie szybko na wierzch wychodzą jego wady.
Aplikacje biznesowe mają to do siebie, że są wyjątkowo wrażliwe na kontekst danych. Zmiana jednego parametru wejściowego może skierować proces na zupełnie inną ścieżkę. Co więcej, w środowiskach testowych wiernie odwzorowujących produkcję, dotkliwy staje się problem zużywania się danych. Przykładowo – w scenariuszu testowym występuje całkowita spłata kredytu. Jeżeli test przebiegnie pomyślnie, to kredyt będzie spłacony. Kolejny test na tych samych danych zakończy się negatywnie, bo spłaconego kredytu nie da się spłacić ponownie.
Aby unikać ręcznych podmianek danych i redundancji kodu, warto zastosować wzorzec testowania opartego na danych (Data-Driven Testing). Jego filarem jest separacja danych od logiki testu i ich strukturyzowanie. Skrypt zaprojektowany zgodnie z tym wzorcem staje się łatwo utrzymywalny i reużywalny. Wybór formatu danych zależy od specyfiki testu i zachodzi między formatem JSON a tabelami.
JSON
JSON to format tekstowy bazujący na składni słownika JavaScript. Umożliwia wygodne i czytelne modelowanie hierarchicznych, obiektowych danych. Ta obiektowość będzie tu kluczowa, gdyż pozwala na wygodne parametryzowanie testów – podmianę danych na takie, które mają jednakowe pola, czyli w praktyce należą do tej samej klasy.
Kiedy warto użyć JSON?
- Złożone struktury danych
Gdy pojedynczy przypadek testowy jest obszerny i zawiera hierarchiczne, zagnieżdżone dane, które trudno spłaszczyć do jednego wiersza w tabeli (np. dane klienta, który ma 3 różne adresy i 10 rachunków). - Łatwo parametryzowane testy
Kiedy scenariusze testowe są niemal identyczne dla wielu przypadków, warto rozważyć stworzenie jednego skryptu i jego parametryzację. - Niewielka liczebność
Przy kilku, kilkunastu przypadkach testowych plik JSON jest przejrzysty i prosty do ogarnięcia.
Przypadek użycia: Parametryzacja skryptu testowego
Przykładowy plik JSON z zamodelowanymi przypadkami testowymi:
[
{
"id": "KZK_NEG_01",
"opis": "Bezrobotny",
"input": {
"wiek": "45",
"kwotaKredytu": "700000",
"dochod": "0"
},
"oczekiwanyKomunikat": "Klient bezrobotny."
},
{
"id": "KZK_NEG_02",
"opis": "Niepełnoletni",
"input": {
"wiek": "17",
"kwotaKredytu": "1000000",
"dochod": "4500"
},
"oczekiwanyKomunikat": "Klient niepełnoletni."
}
]
Od razu rzuca się w oczy przejrzystość i czytelność JSON-a. Dzięki prostej formie słownika każda wartość ma swoją nazwę. W tym samym miejscu można było zdefiniować nazwę przypadku testowego, wszystkie dane wejściowe, a nawet oczekiwany komunikat. Takich przypadków można zdefiniować dowolną ilość, jaka akurat będzie potrzebna. Teraz ten plik z danymi posłuży do parametryzacji skryptu testowego. Najpierw należy go odczytać, a dostęp do pliku umożliwia biblioteka fs (filesystem).
const fs = require('fs');
const sciezkaDoPliku = './data/kzk_neg.json'
// Wczytanie danych z JSON
const scenariusze = JSON.parse(fs.readFileSync(sciezkaDoPliku, 'utf-8'));
test.describe('Kalkulator zdolności kredytowej, () => {
// Parametryzacja testu poprzez pętlę - przejście po wszystkich obiektach z JSON
for (const s of scenariusze) {
test(`${s.id}: ${s.opis}`, async ({ page }) => {
await page.getByLabel('Wiek').fill(s.input.wiek);
await page.getByLabel('Kwota kredytu').fill(s.input.kwotaKredytu);
await page.getByLabel('Dochód').fill(s.input.dochod);
await page.getByRole('button', { name: 'Sprawdź zdolność'}).click();
const komunikat = await page.locator('.message');
await expect(komunikat).toHaveText(s.oczekiwanyKomunikat);
});
}
});
Formaty tabelaryczne (CSV, XLSX)
Tabele znajdą zastosowanie tam, gdzie liczy się prostota struktury.
Kiedy warto sięgnąć po tabele?
- Duży wolumen danych
Kiedy liczba danych jest już znacząca, a same dane są dość proste i można je wyrazić podstawową tabelą. - Dane ulegające zużyciu
Kiedy dane zużywają się po jednym użyciu i potrzeba zapasowej listy. - Dane już przechowywane w skoroszycie
Jeżeli dostępny jest plik z danymi testowymi, który od razu nadaje się do automatyzacji, warto go wykorzystać. Tym lepiej, jeżeli jest na bieżąco uzupełniany nowymi danymi. - Proste raportowanie
W tabeli z danymi można odkładać dodatkowe informacje, np. numer wniosku, w którym dane zostały wykorzystane.
Przypadek użycia: Zużywające się dane
Wracając do wspomnianej wcześniej całkowitej spłaty kredytu, jest to świetny przykład do zobrazowania zużywania się danych testowych. Aby uporać się z tym problemem i koniecznością manualnego podmieniania danych, warto zaimplementować mechanizm odczytujący dane testowe z tabeli. Po zużyciu wystarczy oznaczyć rekord jako wykorzystany, np. zapisując numer sprawy, a świeży przypadek testowy „sam się podstawi” przy następnym uruchomieniu.
// Przykład pliku CSV
NumerKredytu,NumerSprawy
000011112222,
000012345678,OKA930JNE3420A2
000098765432,
Wykorzystanie pliku w skrypcie testowym z użyciem bibliotek csv-parse (odczyt) i csv-stringify (zapis).
const fs = require('fs');
const { parse } = require('csv-parse/sync');
const { stringify } = require('csv-stringify/sync');
const sciezkaDoPliku = './data/kredytyDoSplaty.csv';
test('Całkowita spłata kredytu', async ({ page }) => {
// Odczyt CSV
const inputData = fs.readFileSync(sciezkaDoPliku, 'utf-8');
const rekordy = parse(inputData, { columns: true, skip_empty_lines: true });
// Znalezienie pierwszego niezużytego rekordu
const rekordDoUzycia = rekordy.find(r => r. NumerSprawy != null);
if (!rekordDoUzycia) {
test.skip(`Brak wolnych kredytów. Zaktualizuj: ${sciezkaDoPliku}`);
return;
}
await page.goto(linkDoWniosku);
await page.getByLabel('Numer kredytu').fill(rekordDoUzycia.NumerKredytu);
await page.getByRole('button', { name: 'Spłać kredyt' }).click();
await expect(page.getByText('Spłacono kredyt')).toBeVisible();
const numerSprawy = await page.locator('[appnum]').getAttribute('appnum');
rekordDoUzycia.NumerSprawy = numerSprawy;
const outputData = stringify(rekordy, { header: true });
fs.writeFileSync(sciezkaDoPliku, outputData);
});
Zaktualizowany plik CSV.
„`csv
NumerKredytu,NumerSprawy
000011112222,OKH92DMN30154FAV
000012345678,OKA930JNE34210A2
000098765432,
„`
Warto zwrócić uwagę, że Playwright domyślnie uruchamia testy w wielu workerach. W testach, które modyfikują ten sam plik może to doprowadzić do konfliktów i nadpisywania danych. Aby zapewnić dostęp do pliku pojedynczo, można dany zestaw testów wykonać sekwencyjnie, czyli jeden po drugim.
test.describe.configure({ mode: 'serial' });

Generowanie plików wsadowych i załączników
Dane wejściowe w procesie mogą być nie tylko uzupełniane w formularzu, ale też importowane z plików wsadowych. Tworzenie takich plików również można zautomatyzować, co okazuje się przydatne szczególnie, gdy takie dane mogą być losowe. Losowe dane można pozyskiwać poprzez bibliotekę faker-js. Pozwala ona na generowanie danych, takich jak nazwiska, IBAN czy PESEL. Natomiast wygenerowanie skoroszytu umożliwia biblioteka SheetJS (xlsx).
const fs = require('fs');
const path = require('path');
const XLSX = require('xlsx');
const { fakerPL: faker } = require('@faker-js/faker');
const daneKlientow = './data/dane_klientow.json'
const TEMP_DIR = path.resolve('./temp');
if (!fs.existsSync(TEMP_DIR)) fs.mkdirSync(TEMP_DIR);
const createMassTransferFile = (clientData, count = 100) => {
const rows = [];
for (let i = 0; i < count; i++) {
rows.push({
'Nadawca': clientData.nazwa,
'Rachunek nadawcy': clientData.numerRachunkuBiezacego,
'Odbiorca': faker.person.fullName(),
'Rachunek odbiorcy': faker.finance.iban({ country: 'PL' }),
'Kwota': faker.number.float({ min: 100, max: 10000, precision: 0.01 }),
'Tytuł': `FV ${faker.string.alphanumeric(8).toUpperCase()}`,
'Data': faker.date.recent().toISOString().split('T')[0]
});
}
// Wygenerowanie skoroszytu z powyższą tabelą
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Dane');
const filePath = path.join(TEMP_DIR, `wsad_${Date.now()}.xlsx`);
XLSX.writeFile(wb, filePath);
return filePath;
};
test('Przelew masowy na rachunki zewnętrzne', async ({ page }) => {
const clientData = JSON.parse(fs.readFileSync(daneKlientow));
const filePath = createMassTransferFile(clientData.przedsiebiorca);
await page.locator('input[name="fileUpload"]').setInputFiles(filePath);
fs.unlinkSync(filePath);
});
Poza tworzeniem plików wsadowych, generowanie plików może się przydać do testów pól załącznikowych. Takie testy mogą obejmować sprawdzenie czy pole przyjmuje jedynie określone formaty plików, czy wyświetla odpowiednie walidacje, czy weryfikuje nazwę pliku lub jego rozmiar. Dzięki tworzeniu plików na bieżąco, nie trzeba trzymać ich lokalnie ani w repozytorium, co oszczędza trochę pamięci. Przy generowaniu warto zastosować trick, który zaoszczędzi zasoby obliczeniowe i czasowe. Mianowicie, jeżeli nie jest istotna treść, można generować proste pliki binarne, które tylko przypominają właściwe formaty, zaczynając zapis konkretnym nagłówkiem. Każdy format ma swoją sygnaturę, którą można podejrzeć otwierając plik w notatniku. Przykładowo, każdy PDF rozpoczyna sygnatura „%PDF-„, a każdy .exe rozpoczynają inicjały „MZ” od nazwiska Marka Zbikowskiego, jednego z deweloperów w MS-DOS. Często sama sygnatura wystarczy, by oszukać pole załącznikowe, że załączono plik o danym formacie.
const fs = require('fs');
const path = require('path');
const TEMP_DIR = path.resolve('./temp');
const HEADERS = {
pdf: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D]),
exe: Buffer.from([0x4D, 0x5A]),
};
const createTempFile = (filename, type, size = 1024) => {
const header = HEADERS[type];
const filePath = path.join(TEMP_DIR, `${Date.now()}_${filename}`);
const buf = Buffer.alloc(size);
header.copy(buf);
fs.writeFileSync(filePath, buf);
return filePath;
};
test.describe('Walidacja pól załącznikowych', () => {
test('Niedozwolone rozszerzenie', async ({ page }) => {
const filePath = createTempFile('skrypt.exe', 'exe');
await page.locator('input[name="zalacznik"]').setInputFiles(filePath);
await expect(page.getByText('Niedozwolony format pliku')).toBeVisible();
});
test('Przekroczenie limitu rozmiaru', async ({ page }) => {
const filePath = createTempFile('big.pdf', 'pdf', 11 * 1024 * 1024);
await page.locator('input[name="zalacznik"]').setInputFiles(filePath);
await expect(page.getByText('Przekroczono maksymalny rozmiar pliku')).toBeVisible();
});
test.afterAll(() => {
const dirPath = path.join(__dirname, TEMP_DIR);
if (fs.existsSync(dirPath)) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
});
});

Weryfikacja artefaktów wyjściowych
Zarządzanie danymi wejściowymi to wykorzystanie plików zewnętrznych w automatyzacji testów. Główną motywacją do stworzenia tego artykułu było przedstawienie, jak weryfikować pliki wyjściowe. Ferryt umożliwia generowanie dokumentów PDF, Word oraz arkuszy kalkulacyjnych XLSX. Biblioteki w środowisku Node.js pozwalają na odczyt wymienionych formatów i dzięki temu automatyczną weryfikację ich treści. Wyjście poza weryfikację graficznego interfejsu użytkownika to znaczące uzupełnienie zestawu testów, szczególnie, że to często PDF-y, Wordy i Excele najbardziej interesują użytkownika końcowego. Są wizytówką firmy, a ta powinna być reprezentatywna i profesjonalna. W tej sekcji przyjrzymy się temu, jak pobrać dokument, a następnie zweryfikować jego treść.
Pobranie pliku
Aby pobrać plik, należy ustawić czekanie na zdarzenie „download” przez kliknięciem w przycisk pobierania. Dopiero po kliknięciu można skonsumować obietnicę i wyciągnąć z niej ścieżkę do pliku, z którego będziemy później korzystać.
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Pobierz raport' }).click();
const download = await downloadPromise;
const path = await download.path();
Arkusze kalkulacyjne
Weryfikację arkuszy kalkulacyjnych umożliwia biblioteka SheetJS (xlsx), która udostępnia metody odczytu całego skoroszytu oraz pojedynczych arkuszy i ich zawartości.
const XLSX = require('xlsx');
test('Przelew masowy', async ({ page }) => {
// Pobranie pliku
const workbook = XLSX.readFile(path);
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const dane = XLSX.utils.sheet_to_json(sheet);
const kwota = parseFloat(dane[0]['Kwota transakcji']);
expect(kwota).toBeGreaterThan(0);
});
Audyt dokumentów PDF
Dokumenty PDF to często spotykane podsumowania w procesach – pisma są wysyłane do klienta jako decyzje, harmonogramy, umowy, raport etc. Weryfikacja manualna pism może być żmudna i niezbyt dokładna, szczególnie, gdy dokument jest długi. Biblioteka pdf-parse ekstraktuje tekst z pliku PDF, co pozwala sprawdzić, czy wszystkie dane zostały poprawnie wstrzyknięte do szablonu dokumentu. Ponadto umożliwia weryfikację różnych metadanych, takich jak nazwa pliku czy liczba stron.
const fs = require('fs');
const PDFParser = require('pdf-parse');
test('Decyzja kredytowa', async ({ page }) => {
// Pobranie pliku
expect(download.suggestedFilename()).toMatch(/^Decyzja_Kredytowa_.*\.pdf$/);
const path = await download.path();
const dataBuffer = fs.readFileSync(path);
const data = await PDFParser(dataBuffer);
expect(data.text).toContain(`Kwota kredytu: ${oczekiwanaKwota}`);
expect(data.numpages).toBeGreaterThanOrEqual(liczbaStron);
});
Dokumenty Word
Podobnie sytuacja jawi się przy weryfikacji treści dokumentów tekstowych typu word. Tutaj z pomocą przychodzi biblioteka mammoth, która pozwala wyodrębnić tekst z dokumentu.
- `convertToHtml` – zwraca dokument w formacie HTML, umożliwia weryfikację formatowania
- `extractRawText` – zwraca czysty tekst pozbawiony formatowania
const mammoth = require('mammoth');
test('Projekt umowy kredytowej', async ({ page }) => {
// Pobranie pliku
const result = await mammoth.extractRawText({ path: path });
const trescDokumentu = result.value;
expect(trescDokumentu).toContain(`Umowa zawarta w dniu: ${data}`);
)};
Podsumowanie
Słowem końca, włączenie obsługi plików zewnętrznych do automatyzacji testów otwiera drzwi na nowe możliwości. Uwzględnienie weryfikacji dokumentów wygenerowanych przez Ferryt to nie tylko zwiększenie pokrycia testami, ale i większa dbałość o jakość procesu. Dokumenty i arkusze kalkulacyjne to jego niezwykle istotna część, która nie musi być testowana jedynie manualnie.
Warto przytoczyć również inne interesujące pomysły na wykorzystanie plików:
- Tworzenie spersonalizowanych logów i raportów
- Weryfikacja treści e-maili
- Proste mockowanie odpowiedzi serwisów przy wywoływaniu API
Niech przytoczone zastosowania wniosą niejeden zestaw testów na nowy poziom i zachęcą do dalszej eksploracji tematu.