chore: seminarhof customer project
This commit is contained in:
60
e2e/tests/steps/invoice.steps.ts
Normal file
60
e2e/tests/steps/invoice.steps.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { When } from "@cucumber/cucumber";
|
||||
import path from "node:path";
|
||||
import type { ActorSession } from "@pikku/cucumber/browser";
|
||||
import type { AppWorld } from "../support/world.js";
|
||||
|
||||
/**
|
||||
* Project-specific invoice steps. Talk to the Invoices tab on the admin
|
||||
* booking-detail page (Mantine form). Locale-aware: the form uses German
|
||||
* labels by default in this app.
|
||||
*/
|
||||
|
||||
When(
|
||||
"{actor} create(s) an invoice numbered {string} for {float} €",
|
||||
async function (this: AppWorld, actor: ActorSession, invoiceNumber: string, amountEuro: number) {
|
||||
// The form sits in the "Neue Rechnung" panel — use data-testid hooks so we
|
||||
// are not coupled to localized labels.
|
||||
const numberField = actor.page
|
||||
.getByTestId("invoice-create-form")
|
||||
.locator('input[data-testid="invoice-number"], [data-testid="invoice-number"] input')
|
||||
.first();
|
||||
await numberField.fill(invoiceNumber);
|
||||
const amount = actor.page
|
||||
.getByTestId("invoice-create-form")
|
||||
.locator('input[data-testid="invoice-amount"], [data-testid="invoice-amount"] input')
|
||||
.first();
|
||||
await amount.fill("");
|
||||
await amount.fill(String(amountEuro.toFixed(2)));
|
||||
await actor.page
|
||||
.getByRole("button", { name: /rechnung erstellen|create invoice/i })
|
||||
.click();
|
||||
// Wait for the row to appear in the invoices table above the form.
|
||||
await actor.page.getByText(invoiceNumber, { exact: false }).first().waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
"{actor} attach(es) invoice PDF {string}",
|
||||
async function (this: AppWorld, actor: ActorSession, filename: string) {
|
||||
const filePath = path.resolve(process.cwd(), "tests", "fixtures", filename);
|
||||
await actor.page.getByTestId("invoice-pdf").setInputFiles(filePath);
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
"{actor} mark(s) invoice {string} as paid on {string}",
|
||||
async function (this: AppWorld, actor: ActorSession, invoiceNumber: string, paidOn: string) {
|
||||
// Stub the prompt before clicking — the UI uses window.prompt to collect
|
||||
// the payment date.
|
||||
await actor.page.evaluate((value) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).prompt = () => value;
|
||||
}, paidOn);
|
||||
|
||||
const row = actor.page.getByRole("row", { name: new RegExp(invoiceNumber) });
|
||||
await row.getByTestId(`invoice-mark-paid-${invoiceNumber}`).click();
|
||||
await row.getByText(/bezahlt|paid/i).waitFor({ state: "visible" });
|
||||
},
|
||||
);
|
||||
114
e2e/tests/steps/lifecycle.steps.ts
Normal file
114
e2e/tests/steps/lifecycle.steps.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { When, Then } from "@cucumber/cucumber";
|
||||
import assert from "node:assert/strict";
|
||||
import type { ActorSession } from "@pikku/cucumber/browser";
|
||||
import type { AppWorld } from "../support/world.js";
|
||||
import { config } from "../support/types.js";
|
||||
|
||||
When("the lifecycle cron runs", async function (this: AppWorld) {
|
||||
await this.runLifecycle();
|
||||
});
|
||||
|
||||
Then(
|
||||
"the lifecycle cron sent {int} contracts",
|
||||
async function (this: AppWorld, count: number) {
|
||||
const result = await this.runLifecycle();
|
||||
assert.equal(result.contractsSent, count);
|
||||
},
|
||||
);
|
||||
|
||||
Then(
|
||||
"the lifecycle cron sent {int} reminders",
|
||||
async function (this: AppWorld, count: number) {
|
||||
const result = await this.runLifecycle();
|
||||
assert.equal(result.reminders, count);
|
||||
},
|
||||
);
|
||||
|
||||
Then(
|
||||
"the lifecycle cron completed {int} bookings",
|
||||
async function (this: AppWorld, count: number) {
|
||||
const result = await this.runLifecycle();
|
||||
assert.equal(result.ended, count);
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
"{actor} search(es) the bookings list for {string}",
|
||||
async function (this: AppWorld, actor: ActorSession, text: string) {
|
||||
await actor.page
|
||||
.getByPlaceholder(/suchen/i)
|
||||
.first()
|
||||
.fill(text);
|
||||
await actor.page.waitForTimeout(400); // debounce
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
"{actor} trigger(s) booking action {string}",
|
||||
async function (this: AppWorld, actor: ActorSession, actionId: string) {
|
||||
const action = actor.page.getByTestId(actionId).first();
|
||||
await action.waitFor({ state: "visible", timeout: config.responseTimeout });
|
||||
await action.click({ timeout: config.responseTimeout });
|
||||
const confirm = actor.page.getByTestId(`${actionId}-confirm`).first();
|
||||
await confirm.waitFor({ state: "visible", timeout: config.responseTimeout });
|
||||
await confirm.click({ timeout: config.responseTimeout });
|
||||
await actor.page.waitForLoadState("networkidle", { timeout: 1000 }).catch(() => {
|
||||
// Persistent connections never go idle; the assertion below waits anyway.
|
||||
});
|
||||
await actor.page.waitForTimeout(750);
|
||||
},
|
||||
);
|
||||
|
||||
Then(
|
||||
"the audit log tab includes {string}",
|
||||
async function (this: AppWorld, entry: string) {
|
||||
const actor = await this.actor(undefined);
|
||||
await actor.page.getByRole("tab", { name: /protokoll|audit/i }).click();
|
||||
await actor.page
|
||||
.getByText(entry, { exact: false })
|
||||
.first()
|
||||
.waitFor({ state: "visible", timeout: config.responseTimeout });
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
"the admin approves the booking {string}",
|
||||
async function (this: AppWorld, bookingId: string) {
|
||||
const actor = await this.actor(undefined);
|
||||
await actor.gotoApp(`/admin/bookings/${bookingId}`);
|
||||
await actor.page.getByRole("button", { name: /reserv/i }).first().click();
|
||||
await actor.page.waitForTimeout(500);
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
"the admin records the deposit received for {string}",
|
||||
async function (this: AppWorld, bookingId: string) {
|
||||
const actor = await this.actor(undefined);
|
||||
await actor.gotoApp(`/admin/bookings/${bookingId}`);
|
||||
await actor.page.getByRole("button", { name: /anzahlung/i }).first().click();
|
||||
await actor.page.waitForTimeout(500);
|
||||
},
|
||||
);
|
||||
|
||||
Then(
|
||||
"the booking {string} has status {string}",
|
||||
async function (this: AppWorld, bookingId: string, status: string) {
|
||||
const actor = await this.actor(undefined);
|
||||
await actor.gotoApp(`/admin/bookings/${bookingId}`);
|
||||
await actor.expectText(status);
|
||||
},
|
||||
);
|
||||
|
||||
Then(
|
||||
"the audit log for {string} includes {string}",
|
||||
async function (this: AppWorld, bookingId: string, entry: string) {
|
||||
const actor = await this.actor(undefined);
|
||||
await actor.gotoApp(`/admin/bookings/${bookingId}`);
|
||||
await actor.page.getByRole("tab", { name: /protokoll|audit/i }).click();
|
||||
await actor.page
|
||||
.getByText(entry, { exact: false })
|
||||
.first()
|
||||
.waitFor({ state: "visible", timeout: config.responseTimeout });
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user