From 1da37b0d9cd783a1c076c37da5d847d75583fb5e Mon Sep 17 00:00:00 2001 From: Happy Mahlangu Date: Mon, 17 Aug 2026 02:01:37 +0200 Subject: [PATCH] docs(examples): purchase info-records report (SAP Fiori + Excel) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P-190-040-080-2 from the UAT test-case PDF: run and export a purchasing info records report in Fiori, then review the export in Excel — unlike manage-info-records.flow.yaml (P-190-040-070-2, editing one record), this case never stays inside the browser. First example exercising the full stack this PR series built: a captured launch command (`EXCEL.EXE ${captured.pir_export}`), a pinned browser download (`Wait until the download completes as`), and the out-of-band spreadsheet read (`assert_spreadsheet`). Drafted with `flowproof author-from-doc` against the source PDF, then hand-extended with the `excel:` surface and the two new step forms. Two steps are left as flagged TODOs rather than guessed: the source PDF's own tester hit the same ambiguity live ("three near-identical tile options, no clear single target given" and "extract... no specific element identified") — resolving them needs the live app, which is `flowproof record`'s job, not a guess made from a document. Also documents the `Wait until the download completes as` step and the `${captured.x}`-in-a-launch-command capability in docs/authoring.md, alongside the multi-surface section — both landed in earlier PRs in this stack without their prose, deferred here to pair with the flow that actually demonstrates them. Not yet run against a live SAP system (no Windows/SAP/Excel access in this environment) — validated by `FlowSpec::parse` accepting the whole file and a live `flowproof record` attempt reaching actual browser navigation before failing on a placeholder domain, proving every step form resolves. The unattended CI recording job against the real self-hosted runner is a separate PR (touches .github/workflows/, which CLAUDE.md reserves for a human to open). --- docs/authoring.md | 43 ++++++++- .../purchase-info-records-report.flow.yaml | 93 +++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 examples/fiori/purchase-info-records-report.flow.yaml diff --git a/docs/authoring.md b/docs/authoring.md index 2e529115..a6b56d9d 100644 --- a/docs/authoring.md +++ b/docs/authoring.md @@ -899,7 +899,8 @@ What holds, and why: as always. - **Steps author against their surface's own grammar** (what SAP performs differs from what a browser does), and out-of-band asserts - (`assert_api`, `assert_sql`) run fine inside any block. + (`assert_api`, `assert_sql`, `assert_spreadsheet`) run fine inside any + block. - **A web surface carries its own `browser:`** — viewport/device emulation, user-agent, pinned clock, seeded random, exactly the single-surface block, one level deeper: @@ -1086,6 +1087,46 @@ actually there: "path 'page' is an object, count requires an array (status expected exactly 9 (status 200)". Both are soft failures, so on a `GET` they auto-wait: "poll until the collection has N rows" is a real pattern. +### assert_spreadsheet: an exported file, read directly + +```yaml +- assert_spreadsheet: + path: ${captured.pir_export} # may carry ${captured.x} / ${VAR} refs + sheet: Sheet1 # optional; the workbook's first sheet if absent + at: B2 # an absolute A1 reference ... +- assert_spreadsheet: + path: ${captured.pir_export} + column: Net Price # ... OR a header + row anchor, not both + row_contains: "100-100" + equals: "12.50" +``` + +Reads the file directly (`calamine`) rather than through UI Automation over +Excel's own grid — the manual test's own second check ("open the file and +review it") is a screen a person can look at, but its GRID support over UIA +is untested and known-flaky, so the out-of-band read is the one that must +hold. `path` resolves `${captured.x}` then `${VAR}`, exactly like a typed +field — the common case is a path a `Wait until the download completes as +` step captured moments earlier in another surface. + +The cell is addressed EITHER by `at` (an absolute `A1` reference) OR by +`column`+`row_contains` together — never both, and never neither, both +parse-time errors. `column` resolves against the sheet's first row the same +two-rung ladder a web table cell uses: exact match after trim, then a +unique substring match; an ambiguous or missing header is a parse-time-shaped +failure naming what was asked for. `row_contains` is the unique data row +(excluding the header) where ANY cell's text contains it — ambiguous or +absent is reported the same way. + +`equals`/`contains` compare the cell's text (its canonical rendering — a +number reads as `"12.5"`, not `"12.50000"`); at most one may be set, a +parse-time error otherwise. With neither, resolving the cell is the whole +assertion — mirroring `assert_sql`, where omitting `equals` means a row +merely has to exist. Like `assert_sql`, this is always a READ: a +just-landed download may still be mid-write when the first poll fires, so +the auto-wait loop keeps re-opening the file until it resolves or the bound +(`timeout_seconds`, default 10s) elapses. + ### Retries: reads are polled, writes are sent once A failing assertion auto-waits by RE-SENDING its probe until the bound diff --git a/examples/fiori/purchase-info-records-report.flow.yaml b/examples/fiori/purchase-info-records-report.flow.yaml new file mode 100644 index 00000000..586ed773 --- /dev/null +++ b/examples/fiori/purchase-info-records-report.flow.yaml @@ -0,0 +1,93 @@ +# SAP Fiori + Excel: run and export a purchasing info records report, then +# review the export in Excel — P-190-040-080-2 from the UAT test case PDF +# (HP ALM/QC export). Unlike manage-info-records.flow.yaml (P-190-040-070-2, +# editing one record), this case never stays inside the browser: it exports +# to a spreadsheet and finishes the assertion in a second, unrelated Windows +# application. That is exactly the multi-surface shape docs/multi-surface.md +# describes (`apps:` + `in:` blocks) combined with the download/spreadsheet +# primitives it names as the missing pieces. +# +# Every value that is test DATA travels as a ${VAR} reference, minted by the +# sibling suite.yaml's env_from (DataMaker), same as manage-info-records.flow.yaml. +# +# Two steps below are left as flagged TODOs rather than guessed: the source +# PDF itself records the tester choosing between three near-identical tile +# options with "no clear single target... given", and separately extracting +# "the complete purchase info record from the search results" with "no +# specific element or button identified" — the same ambiguity the manual +# script's own author hit. `flowproof record` resolves these against the +# live screen; guessing selector text here would record a lie, not a step. +name: Purchase info-records report +apps: + fiori: + app: web + url: ${FIORI_BASE_URL}/ui#Shell-home + # No `browser: {downloads_dir: ...}` needed — downloads are enabled by + # default at every launch, into a per-launch temp directory the driver + # creates and owns, so `wait until the download completes` always has + # somewhere to look. + excel: + # A generic Windows-mapping surface (docs/multi-surface.md) — Excel + # needs no bespoke app kind. `${captured.pir_export}` resolves at THIS + # surface's own activation (after the fiori block has captured it), + # never before, so the download must have already landed. + app: + command: EXCEL.EXE ${captured.pir_export} + window_title: Excel +steps: + - in: fiori + steps: + # -- Log in on the launchpad's login screen ------------------------ + - Type ${SAP_USER} into the "User" field + - Type ${SAP_PASSWORD} into the "Password" field + - Press the "Log On" button + - Wait until page shows Home within 60s + + # -- Navigate: module tile, then the report app -------------------- + - Click "PTP Process Area BU Apps" + # TODO: the PDF's Step 3 names three near-identical options with no + # single clear target ("Manage Info Records" / "Display purchasing + # info records by Supplier" / "Display purchasing info records by + # material") — resolve against the live tile catalog at record time. + # This is a best-effort guess at the tile's visible text, not a + # confirmed label. + - Click "Display Purchasing Info Record" + - assert: page shows Info Record + + # -- Search the standard info record by the minted test data ------- + - Type ${SUPPLIER} into the "Supplier" field + - Type ${MATERIAL} into the "Material" field + - Type ${PLANT} into the "Plant" field + - Press the "Go" button + - assert: page shows ${MATERIAL} + + # TODO: unexplained step — the PDF's Step 5 ("Extract the complete + # purchase info record from the search results") names no specific + # element or button, and its own "Actual" note records the tester + # hitting this exact ambiguity mid-run ("Report extracted but not + # usable... updated to ALV grid view"). Resolve against the live + # screen at record time — a "select all" header checkbox before + # export is one live possibility. + - resolve the action needed here against the live screen — a keyboard-only action like Enter may be the cause. Observed - "Extract the complete purchase info record from the search results", with no specific element or button identified. + + # -- Export, and hand the download to the excel surface ------------ + - Press the "Export" button + - Wait until the download completes as pir_export within 60s + + - in: excel + steps: + # Visual/manual-test parity: the same "open it and look" check the + # manual script's Step 7 performed by eye. + - assert: page shows Net Price + + # Out-of-band truth: the exported file itself, not the pixels Excel + # renders (Excel's grid UIA support is untested and known-flaky). + # column/row_contains resolve against the report's actual header row + # and data rows, verified live at the first `flowproof record` pass — + # the export's exact layout (a summary block above the real table, + # per the PDF's own screenshots) isn't knowable from the document + # alone. + - assert_spreadsheet: + path: ${captured.pir_export} + column: Net Price + row_contains: ${MATERIAL}