Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion docs/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
<name>` 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
Expand Down
93 changes: 93 additions & 0 deletions examples/fiori/purchase-info-records-report.flow.yaml
Original file line number Diff line number Diff line change
@@ -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}
Loading