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}