mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
test(freight-e2e): fix stale onboarding selectors, tighten Fayda assertions
The onboarding journey had been failing at the company step for a while: that step was restructured into StepSection cards, so its TIN and VAT fields no longer have <label> elements and the label-based fill() helper could not find them. Match on aria-label instead, which also sidesteps the "0012345678" placeholder both fields share. Two further staleness bugs were hiding behind that one, both in fill() itself. It chained clear() into type() on a subject captured beforehand, so a step-persist PATCH resolving between the two detached it; and re-querying by the captured id was no better, because the fields remount rather than re-render and Mantine mints a fresh generated id when they do. Resolve label -> for -> element afresh for each action. fillPhone gets the same treatment. Tighten what the journey proves about Fayda. It verified out-of-band and then never checked the result reached the UI, so assert the panel renders fayda-mock's own payload — name, phone and email — which only holds if it travelled Fayda -> API -> UI, and cross-check ownerFaydaSub on the company, since that sub is what locks the owner's fields server-side. The PoA step now asserts no file input exists while the PoA is unverified, covering the DARS gating; asserted structurally so rewording the document setting cannot turn a regression into a passing test.
This commit is contained in:
@@ -42,21 +42,44 @@ function latestOnboardJourney() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Fill a labelled Mantine input (label[for] → input id). */
|
||||
/**
|
||||
* Fill a labelled Mantine input (label[for] → input id).
|
||||
*
|
||||
* The input is resolved fresh for every action rather than captured once.
|
||||
* Each wizard step persists and re-seeds asynchronously, and when a field
|
||||
* remounts Mantine mints a NEW generated id — so both a subject and an id
|
||||
* captured a command earlier can be stale by the time the next command runs.
|
||||
* Going label → for → element each time always addresses what's on the page
|
||||
* now.
|
||||
*/
|
||||
function fill(label: string | RegExp, value: string) {
|
||||
cy.contains("label", label)
|
||||
const input = () =>
|
||||
cy
|
||||
.contains("label", label)
|
||||
.invoke("attr", "for")
|
||||
.then((id) => {
|
||||
cy.get(`[id="${id}"]`).clear({ force: true }).type(value, { force: true });
|
||||
});
|
||||
.then((id) => cy.get(`[id="${id}"]`));
|
||||
|
||||
input().clear({ force: true });
|
||||
input().type(value, { force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an input that has no <label> — the wizard's company step renders its
|
||||
* fields inside StepSection cards (the heading is the card's title, not a
|
||||
* label), so they're reachable only by aria-label. Both TIN and VAT share the
|
||||
* "0012345678" placeholder, which is why this matches on aria-label instead.
|
||||
*/
|
||||
function fillAria(ariaSelector: string, value: string) {
|
||||
const selector = `.mantine-Modal-content ${ariaSelector}`;
|
||||
cy.get(selector).clear({ force: true });
|
||||
cy.get(selector).type(value, { force: true });
|
||||
}
|
||||
|
||||
/** The wizard's phone inputs (react-phone-number-input, type=tel). */
|
||||
function fillPhone(index: number, national: string) {
|
||||
cy.get('.mantine-Modal-content input[type="tel"]')
|
||||
.eq(index)
|
||||
.clear({ force: true })
|
||||
.type(national, { force: true });
|
||||
const selector = '.mantine-Modal-content input[type="tel"]';
|
||||
cy.get(selector).eq(index).clear({ force: true });
|
||||
cy.get(selector).eq(index).type(national, { force: true });
|
||||
}
|
||||
|
||||
describe("customer onboarding journey", { retries: 0 }, () => {
|
||||
@@ -86,20 +109,48 @@ describe("customer onboarding journey", { retries: 0 }, () => {
|
||||
cy.get(".mantine-Modal-content").contains("button", "Continue").click();
|
||||
|
||||
// Ethiopian companies gate the "Owner identity" step on Fayda
|
||||
// verification — a real popup + SMS OTP flow that can't run in e2e.
|
||||
// Complete it via the API against fayda-mock-e2e (the profile this
|
||||
// verification — a real eSignet redirect + SMS OTP flow that can't run in
|
||||
// e2e. Complete it via the API against fayda-mock-e2e (the profile this
|
||||
// attaches to was just created by the nationality/role step above).
|
||||
// The wizard already fetched `identity` once when this step mounted —
|
||||
// completing verification out-of-band (no popup, so no onVerified
|
||||
// callback fires) leaves that fetch stale, so reload to force a fresh
|
||||
// one. Wizard progress resumes server-side, so this doesn't lose the
|
||||
// nationality/role step just completed.
|
||||
// The wizard already fetched `identity` once when this step mounted, and
|
||||
// completing verification out-of-band skips the redirect that would
|
||||
// normally remount everything — so reload to force a fresh fetch. Wizard
|
||||
// progress resumes server-side, so this doesn't lose the nationality/role
|
||||
// step just completed.
|
||||
completeFaydaVerification("owner");
|
||||
cy.reload();
|
||||
cy.contains("Confirm your VAT number", { timeout: 20000 }).should(
|
||||
"be.visible",
|
||||
);
|
||||
|
||||
// The owner's identity is the verification's output, never typed: the
|
||||
// panel must show it verified and render the name/phone/email that came
|
||||
// back from fayda-mock-e2e. Asserting the mock's own values is the only
|
||||
// way to prove the payload travelled Fayda → API → UI rather than the
|
||||
// panel simply flipping a "verified" flag.
|
||||
cy.get(".mantine-Modal-content").within(() => {
|
||||
cy.contains("Fayda verified").should("be.visible");
|
||||
cy.contains("Abebe Bekele").should("be.visible");
|
||||
cy.contains("+251911223344").should("be.visible");
|
||||
cy.contains("abebe.bekele@example.com").should("be.visible");
|
||||
});
|
||||
|
||||
// The verified sub is what locks the owner's fields server-side, so check
|
||||
// it actually landed on the company rather than trusting the panel alone.
|
||||
cy.task<{ rows: Array<{ owner_fayda_sub: string | null }> }>("db:query", {
|
||||
sql: `SELECT c.attributes->>'ownerFaydaSub' AS owner_fayda_sub
|
||||
FROM freight.companies c
|
||||
JOIN freight.external_profiles ep ON ep.company_id = c.id
|
||||
JOIN iam.users u ON u.id = ep.user_id
|
||||
WHERE u.email = $1`,
|
||||
params: [email],
|
||||
}).then(({ rows }) => {
|
||||
expect(rows, "company row").to.have.length(1);
|
||||
expect(rows[0].owner_fayda_sub, "owner Fayda sub").to.eq(
|
||||
"e2e-fayda-sub-0001",
|
||||
);
|
||||
});
|
||||
|
||||
// Company step. TIN auto-triggers the eTrade lookup once it's a full 10
|
||||
// digits (mocked in e2e — see docker-compose.e2e.yaml's etrade-mock-e2e).
|
||||
// A successful lookup locks Company Name/Region/Zone/Woreda/Kebele/House
|
||||
@@ -108,7 +159,7 @@ describe("customer onboarding journey", { retries: 0 }, () => {
|
||||
// Fayda-verified owner supplies contact details now). By label, not
|
||||
// placeholder: the VAT Number field on this same step shares the TIN
|
||||
// field's "0012345678" placeholder, so a placeholder selector matches 2.
|
||||
fill(/^TIN Number/, tin);
|
||||
fillAria('[aria-label^="TIN Number"]', tin);
|
||||
cy.contains("Verified with eTrade", { timeout: 15000 }).should(
|
||||
"be.visible",
|
||||
);
|
||||
@@ -117,7 +168,7 @@ describe("customer onboarding journey", { retries: 0 }, () => {
|
||||
// after the badge appears. Typing into VAT immediately raced one of
|
||||
// those and detached mid-type; let it finish before touching the form.
|
||||
cy.wait(500);
|
||||
fill(/^VAT Number/, vat);
|
||||
fillAria('[aria-label="VAT Number"]', vat);
|
||||
cy.get(".mantine-Modal-content").contains("button", "Continue").click();
|
||||
|
||||
// Personnel (general manager).
|
||||
@@ -131,7 +182,16 @@ describe("customer onboarding journey", { retries: 0 }, () => {
|
||||
fillPhone(0, "911234569");
|
||||
cy.get(".mantine-Modal-content").contains("button", "Continue").click();
|
||||
|
||||
// PoA — optional for an importer.
|
||||
// PoA — optional for an importer, and left unverified here. The DARS
|
||||
// delegation paper authorises the representative the verification names,
|
||||
// so with no verified PoA there is nothing for it to authorise: the
|
||||
// upload must not be offered, and the step must not block on it. Asserted
|
||||
// as "no file input on this step" rather than by label, so a reworded
|
||||
// document setting doesn't turn a real regression into a passing test.
|
||||
cy.get(".mantine-Modal-content")
|
||||
.contains("Power of Attorney")
|
||||
.should("be.visible");
|
||||
cy.get('.mantine-Modal-content input[type="file"]').should("not.exist");
|
||||
cy.get(".mantine-Modal-content").contains("button", "Continue").click();
|
||||
|
||||
// Documents: no company docs are configured in e2e, but every role needs
|
||||
|
||||
Reference in New Issue
Block a user