| name | build-scripts |
| description | Standalone Phase 6 of SAP UI testing. Rediscovers the configured test folder, program, approved cases, and primary system from disk; validates upstream inputs; then writes one tests/<PROGRAM>/test-scripts/TC-XXX.spec.ts per case using @sap-testing/runtime. Use when the user asks to write, generate, or convert Playwright scripts from test cases. |
Build Scripts — Phase 6 (of 7)
If the scenario crosses SAP connections, load multi-system-workflows alongside this
skill. Keep one system per script and implement only the documented explicit JSON handoff
between linked stage cases.
Phase order: analyze-and-plan (1) → explore-ui (2) → design-cases (3) → define-data (4) → prepare-data (5) → build-scripts (6) → run-scripts (7).
For bounded, self-contained support work, use sap-task-helper with explicit inputs, allowed writes, and an output contract. Never delegate the whole phase (e.g. "write specs for all 30 cases") to sap-task-helper — it is a bounded per-file helper and will reject phase-scale work. Convert cases yourself, or delegate at most a small, explicitly-listed batch of disjoint files with a per-file write boundary.
Tool availability (read this if a tool seems missing)
Naming: when these docs say call X, X is a tool (you invoke it and get a result); delegate to / invoke X is an agent (a subagent you launch); load / follow X is a skill (a procedure you read). A name without a verb: see the overview's "Skills, tools, and agents" list.
The editor may hide tools until searched for. Before Step 0, ensure abapfs_get_test_folder, abapfs_get_connected_systems, and abapfs_verify_test_data_usage are available; if any is missing, search your available tools for it by name. If one cannot be found, tell the user.
Non-negotiable execution gate
Every required step and artifact below is recorded as a Phase 6 prerequisite. The abapfs_run_playwright_tests tool verifies script generation and validation and will reject every affected case if anything was skipped, deferred, incomplete, stale, or unverified. A .spec.ts file existing by itself cannot bypass the tool's validation.
Why
The test case says what to verify; the spec is the ONLY thing SAP will actually be asked. If the spec drifts from the case — skips a control, ignores a default, uses a hardcoded value instead of the resolved data — the automation reports GREEN while the real bug lives on. Every deviation from the case's state table is a hole in coverage. Every hardcoded material or plant makes the spec unrunnable in the next system. Fidelity here is what turns a plan into protection.
Goal: 1:1 .md → .spec.ts conversion using only the helper API, with runtime data resolution.
Process
Step 0 — Standalone bootstrap and input gate (mandatory)
Say before acting: "Starting Step 0: standalone bootstrap and upstream input gate."
Run these actions in this exact order in every chat:
- Call
abapfs_get_test_folder before reading or writing any artifact. Treat the returned absolute path as <TEST_FOLDER>; never infer it from the workspace or a prior chat.
- If unset, STOP and ask the user to run "ABAP FS: Enable SAP UI Testing Features". If the folder is not open in the workspace, STOP and ask the user to add it via File > Add Folder to Workspace.
- Resolve
<PROGRAM> and selected TC-IDs from the current request. If omitted, inspect <TEST_FOLDER>/tests/*/test-cases/_index.md. Auto-select only when exactly one candidate exists; otherwise ask. Use _index.md approval, runnability, and Data required? state, not prior-chat memory.
- Enforce the upstream input gate for the selected program:
_index.md, _screens.md, and every selected TC-XXX.md must exist. If _index.md/TC-XXX.md is missing or inconsistent, STOP and follow design-cases; if _screens.md is missing, STOP and follow explore-ui. Read a matching TC-XXX.data.md only when the index says Data required? = yes (authored in define-data); no means no sidecar may exist.
- Call
abapfs_get_connected_systems and confirm the primary connectionId. Ask only if ambiguous. The spec remains system-agnostic; this identity is used to check readiness and verify source facts.
- If a selected case has
Data required? = yes, inspect <TEST_FOLDER>/tests/<PROGRAM>/test-results/<connectionId>/<TC-ID>/data.json or run the available readiness check. Missing prepared data does not prevent writing a structurally valid spec, but it must be reported in the handoff as a Phase 5 (prepare-data) blocker before execution.
- Confirm ABAP tools are available if source verification is needed. Every ABAP-tool call passes explicit
connectionId.
Do not continue to Step 1 until <TEST_FOLDER>, <PROGRAM>, selected TC-IDs, required upstream artifacts, and primary connectionId are known.
Say before continuing: "Step 0 completed. Evidence: test folder, program, selected cases, required planning artifacts, and primary connection confirmed. Next: Step 1 — read source artifacts."
Step 1 — Read the test case, screens map, and helpers
Say before acting: "Starting Step 1: read every source artifact required for script generation."
MANDATORY reads (parallel):
tests/<PROGRAM>/test-cases/<TC-XXX>.md (frontmatter + steps + expected)
tests/<PROGRAM>/test-cases/<TC-XXX>.data.md when _index.md says Data required? = yes
tests/<PROGRAM>/test-cases/_screens.md — authoritative source of labels, groups, dialogs, buttons. If missing, STOP and follow explore-ui — no reliable way to write locators without it.
tests/<PROGRAM>/test-cases/_index.md — the reviewer's map of all cases. If a case or its runnability must change, STOP and follow design-cases to update the TC source artifact, then rerun abapfs_build_test_index; never hand-edit the index tables.
- Every
<TEST_FOLDER>/recordings/*.recording.ts path explicitly referenced by a screen used in _screens.md. Do not scan unrelated recordings or depend on a recording that _screens.md has not reconciled.
The SapSession/SapArtifacts implementation is not present in the test folder or workspace. Use the method reference in Step 2 and the exposed @sap-testing/runtime type declarations as the authoritative API. Load helpers-reference before generating sap.se16n() proof, and also use it whenever another capability seems missing — you cannot add runtime methods from the test workspace.
WRITE to: tests/<PROGRAM>/test-scripts/<TC-XXX>.spec.ts (create test-scripts/ if it doesn't exist)
Be restart-safe — never blindly overwrite an existing spec. This phase can resume in a fresh chat or after a context compaction, so do not rely on memory of what you've written. Before writing <TC-XXX>.spec.ts, check whether it already exists:
- If it exists and you are NOT explicitly rebuilding it, treat it as already-built — leave it and move on (re-creating it from scratch silently discards any fix applied to it since).
- If it exists and genuinely needs a change, READ it and EDIT the specific part — do not overwrite the whole file.
- At Step 0, enumerate
test-scripts/*.spec.ts up front so you know which selected TC-IDs are already done vs still to write; that list, not your memory, is the source of truth for progress.
Rules:
- Labels used in
sap.setField(...), sap.selectRadio(...), sap.clickButton(...) MUST come from _screens.md
- If a step references a control not in
_screens.md, STOP and follow explore-ui to re-explore it, or ask the user for the missing evidence.
- Do NOT invent helper methods. If a required capability is genuinely missing from the method reference below, follow
helpers-reference: contain one role/name-based implementation in sap.raw(), tell the user the gap exists, and never pretend the runtime itself was extended.
- Every
sap.raw() locator MUST scope through the ITS iframe. sap.raw() returns the top-level Playwright Page, not the SAP DOM — a bare sap.raw().getByRole("textbox", { name: "F" }) queries the outer document, which contains no SAP content, and either times out or (worse) passes vacuously (toBeHidden() on a never-scoped element). Always use sap.raw().frameLocator("iframe#ITSFRAME1").getByRole(...). Do NOT write frameLocator("iframe") — the page has two iframes (ITSFRAME1 and ITSTERMFRAME); the unqualified selector triggers a strict-mode violation.
- Whenever a TC changes runnability status, follow
design-cases to update its frontmatter and rerun abapfs_build_test_index; never edit the generated index row directly.
Say before continuing: "Step 1 completed. Evidence: selected test cases, data specs, _screens.md, and _index.md were read. Next: Step 2 — confirm the runtime API."
Step 2 — Method reference
Say before acting: "Starting Step 2: confirm every required interaction and assertion exists in the runtime API."
Load the sap-webgui skill before choosing locators or helper scopes. Follow its guidance for iframes, accessible names, generated IDs, dialogs, ALV, toolbars, uploads, themes, recording interpretation, and known reliability. In this phase, translate the approved case into runtime calls.
Interaction (via SapSession):
sap.open(), sap.openTx(tcode), sap.runReport(programName)
sap.setField(name, value, { group?, nth?, technicalName? }) — use technicalName: "<ABAP_FIELD>" (e.g. "EKORG", "LIFNR") when the accessible name is ambiguous (shared by more than one field) OR absent (a nameless control), and the ABAP field name was recorded in _screens.md (read from the live lsdata during exploration — see sap-webgui). The runtime tries the accessible name first and falls back to input[lsdata*="-<FIELD>"] whenever the name matches 0 or >1 elements, so this is the correct tool for a nameless field, not only a duplicate-label one. For a nameless control, still pass a human-readable name (used only for the step description) plus the technicalName that actually locates it — e.g. sap.setField("Header amount", data.sample_amount, { technicalName: "GV_SAMPLE_FIELD" }). Never guess the technical name.
sap.setRange(name, from, to, { group? })
sap.check(name, { group?, value? })
sap.selectRadio(name, { group? })
sap.clickButton(name, { group?, dialog?, nth? })
sap.clickTab(tabName) — for dynpro header/detail tab strips (Delivery/Invoice, Conditions, Org. Data on ME21N; Where, Detail Data on MIGO; etc.). NEVER use clickButton on a tab — the ITS DOM has no role="tab" and no title on tabs, so the button fallback finds nothing. sap-webgui covers the ItemTitle/ItmWidthHelper trap.
sap.setGridCell(columnTitle, rowIndex, value) — for editable ALV/table cells whose inputs have no accessible name (item-overview / posting tables). columnTitle is the header title/text; rowIndex is 1-based (row 0 is the header). It handles BOTH WebGUI editable-grid renderers — the dynpro table-control and the CL_GUI_ALV_GRID grid — finding the column by header, clicking the cell to materialise the editor, filling, and COMMITTING the value (the ALV grid needs a commit/blur to move the value into its buffer, which a bare fill() does not do). NEVER use setField on a grid-cell input (the input has no accessible name or title), and NEVER hand-roll a sap.raw() on a grid cell — a raw sets the DOM value but skips the event ITS's ALV listens for, so the cell silently reverts and the test passes green on empty data. If genuinely cannot drive a particular grid, that is a runtime gap to REPORT (see ), not something to work around in the spec.
Assertions (via SapSession):
sap.expectAlert(text), sap.expectNoAlert()
sap.expectTitle(text)
sap.expectDialogOpen(title), sap.expectNoDialog()
sap.expectGridHasRow(text), sap.expectGridEmpty()
sap.expectNoRuntimeError() — redundant most of the time (every guarded() action already runs detectRuntimeError), but useful as an explicit post-condition after a long batch execution / job step.
Runtime-safety mechanisms (automatic, no explicit call needed unless noted):
- Popup guard runs before and after every action against a curated allow-list (License, System messages, Multiple Logon, Copyright, Data Privacy, Password). Unknown dialogs are left alone — including "Exit Document" ("Do you want to save?") which tests must handle explicitly with
sap.clickButton("Yes"|"No", { dialog: "Exit Document" }).
- Program-specific interrupters go via
new SapSession(page, { tcId, title, extraInterrupters: [{ matchTitle, dismissButton, note }] }) — never edit the built-in list.
- Runtime-error detection catches ABAP short dumps, ITS/ICM errors, and dropped-to-logon states after every action; throws with
kind, title, URL, and a body snippet.
sap.openTx(...) verifies the transaction actually loaded and throws a specific S_TCODE/client-missing message if SAP silently bounces to Easy Access.
Load helpers-reference for sap.se16n() usage and the auto-mechanism table; load sap-webgui for the popup allow-list and dump-signature list.
Background artifacts (via SapArtifacts) — only these two methods exist. For anything about job status, IDoc counts, or table rows, use the TC's ## Post-test verification section instead (see design-cases and run-scripts) — that covers all DB-backed state now. These two remain because a filesystem path and rendered spool output have no SQL equivalent:
artifacts.verifyAL11FilePresent(fullPath) — does NOT actually use AL11's tree UI (that's fragile to navigate deterministically); it uses CG3Y (file download) as a proxy, which fails loudly if the file doesn't exist. Slower than it sounds. Still unverified against a real system — suspect this helper first if it misbehaves.
artifacts.captureLatestSpoolForUser(user) → returns text, via SP01. Also unverified.
Data (top-level): resolveTestData(tcId, testInfo) → returns { [key]: string } from the .data.md + cache. ALWAYS pass testInfo (available as the test function's second argument) — it scopes the lookup to THIS program, preventing a same-numbered TC-ID in another program from being resolved by mistake.
Say before continuing: "Step 2 completed. Evidence: required runtime methods and any genuine capability gaps were identified. Next: Step 3 — select the spec template."
Step 3 — Spec template — follow exactly
Say before acting: "Starting Step 3: select and apply the correct spec template."
Two variants depending on whether the case needs test data. Read the case's _index.md row first:
Data required? = yes → require .data.md and use variant A (imports resolveTestData, calls it, references data.<key>)
Data required? = no → require no .data.md and use variant B (no resolveTestData import, no data variable)
Never infer this by rereading the TC body. abapfs_build_test_index already verifies that dataRequired and .data.md existence agree.
Variant A — case WITH .data.md
/**
* TC-001 <title>
* Source: tests/<PROGRAM>/test-cases/TC-001.md
* Data: tests/<PROGRAM>/test-cases/TC-001.data.md
*/
import { test } from "@playwright/test";
import {
SapSession,
SapArtifacts,
resolveTestData,
} from "@sap-testing/runtime";
test("TC-001 <title>", async ({ page }, testInfo) => {
const sap = new SapSession(
page,
{ tcId: "TC-001", title: "<title>" },
testInfo,
);
try {
// Pass testInfo so resolveTestData scopes its search to THIS spec's program —
// without it, a TC-ID that also exists in another program's test-cases/ could
// silently resolve the wrong one's data.
const data = await resolveTestData("TC-001", testInfo);
await sap.openTx("SE38");
await sap.setField("Program", "Z_YOUR_REPORT_NAME");
await sap.pressKey("F8", "Run report");
// From TC-001.md steps — `{ group: ... }` is emitted ONLY when
// _screens.md records a real ARIA group. Plain ITS selection screens
// don't expose one, so most cases just pass the label:
await sap.setField("Material", data.sample_material);
await sap.setField("Plant", data.sample_plant);
await sap.execute();
// From TC-001.md expected:
await sap.expectAlert(/completed/i);
await sap.expectGridHasRow(data.sample_material);
// SQL still runs later in run-scripts. For verification: sql|mixed, also add
// sap.se16n() here after the business action to prove the resulting state is
// visible to business users and capture criteria/output screenshots.
await sap.finish("pass");
} catch (err) {
await sap.finish("fail", (err as Error).message);
throw err;
}
});
Mandatory SE16N proof for SQL/mixed cases
Before generating any SE16N block, load the helpers-reference skill and follow its
sap.se16n() contract, especially the outputFields and expect decision rules. The
short examples here show translation patterns; helpers-reference is the authoritative
usage reference for parameters, operators, result fields, screenshot behavior, and
cardinality assertions.
If TC frontmatter says verification: sql or verification: mixed, read its mandatory
se16nTables list and write a meaningful sap.se16n() assertion for EVERY listed table
after the business action, using the same resolved keys. Do not optimize for merely
passing the gate. If a purchase-order case declares [EKKO, EKPO], show the created
header in EKKO and its expected item rows in EKPO. This is supporting screenshot proof:
keep every declared Phase 7 SQL/manual check.
Run abapfs_verify_test_data_usage after writing the spec. It compares literal table values in
the calls against se16nTables; comments and unrelated calls do not count. The run tool
enforces the same coverage. For complex joins/deltas, each listed table's SE16N assertion
shows its meaningful business-visible state while SQL proves the full technical condition.
Example for a created purchase order:
await sap.se16n({
table: "EKKO",
outputFields: ["EBELN", "BUKRS", "LIFNR", "BSTYP"],
where: [{ field: "EBELN", low: data.purchase_order }],
expect: { exactRows: 1 }
})
await sap.se16n({
table: "EKPO",
outputFields: ["EBELN", "EBELP", "MATNR", "WERKS", "MENGE"],
where: [{ field: "EBELN", low: data.purchase_order }],
expect: { minRows: 1 }
})
In this example, replace EKPO's minRows: 1 with exactRows: <expected item count> when
the TC knows how many items it created. Likewise, keep outputFields narrowly focused on
the keys and business values the screenshot must prove; never substitute "*" to avoid
identifying technical fields.
Variant B — case WITHOUT .data.md (no dynamic test data needed)
Use this for cases that only test validation errors, static defaults, mandatory-field messages, or anything that doesn't require live SAP data.
/**
* TC-002 <title>
* Source: tests/<PROGRAM>/test-cases/TC-002.md
* No .data.md — this case needs no dynamic test data.
*/
import { test } from "@playwright/test";
import { SapSession } from "@sap-testing/runtime";
test("TC-002 <title>", async ({ page }, testInfo) => {
const sap = new SapSession(
page,
{ tcId: "TC-002", title: "<title>" },
testInfo,
);
try {
await sap.openTx("Z_MY_TCODE");
await sap.selectRadio("Some Mode");
await sap.execute();
await sap.expectAlert(/required/i);
await sap.finish("pass");
} catch (err) {
await sap.finish("fail", (err as Error).message);
throw err;
}
});
Say before continuing: "Step 3 completed. Evidence: the correct with-data or no-data template was selected for each case. Next: Step 4 — translate the case."
Step 4 — Rules for translating steps
Say before acting: "Starting Step 4: translate every case state, action, and expected result into runtime calls."
Step 4a — Selection screen state table — translate first
Say before acting: "Starting Step 4a: translate every selection-screen state row."
Read the ## Selection screen state at Execute table in the .md. For every row, emit ONE helper call according to Vs default:
Vs default value | Emit |
|---|
changed from empty / changed from X | The action to set the new value (setField / check / selectRadio) |
RESET from default X | An explicit clear/uncheck/select-other action — do NOT omit |
at default | Nothing (no action) — unless the row also says confirm (see below) |
at default (confirm) | An ASSERTION verifying the control is at the expected default before Execute |
Cross-check every row against _screens.md — the initial: value in the map must match Vs default. On mismatch, STOP and follow explore-ui to re-explore.
Two common mistranslations — do NOT make them:
{ group: "..." } is NOT unconditional. Emit it ONLY when _screens.md records that control inside an ARIA group role. Most SAP ITS selection screens render as an HTML <table> and expose NO role="group" container — passing { group: "Selection" } there makes every helper timeout because the group scope never matches. If _screens.md doesn't explicitly show a group role wrapping the control, OMIT the option entirely. Dynpro screens (ME21N, MIGO, VA01) that DO expose real groups/regions are the exception, not the rule.
- A SELECT-OPTIONS row =
sap.setRange, never sap.setField. If _screens.md records a control as a from/to pair (two textboxes with the same accessible name — the systematic ITS behaviour for SELECT-OPTIONS, per sap-webgui), you MUST emit sap.setRange(name, from, to) — even when the case only fills the low end (sap.setRange("F", data.k, "")). Emitting sap.setField("F", data.k) for a range field triggers a Playwright strict-mode violation ("2 elements match") because both from and to inputs have the same name. This applies to date, number, and character range fields alike.
Examples:
| State-table row | Emit |
|---|
Article | Article Selection | <data-key: sample_article> | changed from empty | await sap.setField("Article", data.sample_article, { group: "Article Selection" }) |
Article Type | Article Selection | (leave empty) | RESET from default \FERT`` | await sap.setField("Article Type", "", { group: "Article Selection" }) |
Include archived | Article Selection | unchecked | RESET from default CHECKED | await sap.check("Include archived", { group: "Article Selection", value: false }) |
ALV Output | Output Mode | selected | at default | (no action) |
Excel Download | Output Mode | not selected | at default (confirm) | Use a runtime assertion when available; otherwise flag the helper gap and use one narrow expect(sap.raw().frameLocator("iframe#ITSFRAME1").getByRole(...)) assertion |
Say before continuing: "Step 4a completed. Evidence: every state-table row maps to an action, assertion, or documented default. Next: Step 4b — translate the Steps section."
Step 4b — Translate the Steps section
Say before acting: "Starting Step 4b: translate navigation, actions, and expected results."
Once state-table actions are emitted, Steps become mostly nav + Execute + follow-ups.
| .md sentence | helper call |
|---|
| "Open transaction X" | sap.openTx("X") |
| "Start program X" | sap.runReport("X") OR sap.openTx("SE38") + sap.setField("Program", "X") + sap.pressKey("F8") |
| "Set field F in section S to <data-key: k>" | sap.setField("F", data.k, { group: "S" }) |
| "Set field F to a literal constant" | sap.setField("F", "LITERAL") — literal is OK for static values like radio labels, not transactional data |
| "Set field F (duplicate label; ABAP field N) to V" | sap.setField("F", V, { technicalName: "N" }) — last resort; _screens.md must record the duplicate and the verified technicalName |
| "CLEAR field F in section S" | sap.setField("F", "", { group: "S" }) |
| "Range F from A to B" | sap.setRange("F", data.a, data.b, { group: "S" }) |
| "Check C in section S" | sap.check("C", { group: "S" }) |
| "UNCHECK C in section S" | sap.check("C", { group: "S", value: false }) |
| "Select radio R in section S" | sap.selectRadio("R", { group: "S" }) |
| "Switch to tab T" / "On the T tab, …" | sap.clickTab("T") — tab labels are case- and punctuation-sensitive; match _screens.md exactly |
| "Set column C in row R of the item overview to V" | sap.setGridCell("C", R, V) — R is 1-based; use for editable ALV cells (ME21N items, MIGO items, VF01 items). Do NOT use setField on grid cells |
| "Set column C in row R to <data-key: k>" | sap.setGridCell("C", R, data.k) |
| "Click Execute" | sap.execute() |
| "Click button B in dialog D" | sap.clickButton("B", { dialog: "D" }) |
| "On Exit Document dialog, discard changes" / "Leave without saving" | sap.clickButton("No", { dialog: "Exit Document" }) — NOT auto-dismissed; must be explicit |
When _screens.md cites a recording, use it only to clarify validated interaction order and control behavior. Apply the recording classification from sap-webgui; never copy a raw generated locator. Map recorded business literals to declared data.<key> values, remove recorder noise such as a redundant click before fill, preserve intentional Enter/server-roundtrip behavior through helpers, and author assertions from the TC because codegen records actions only. If a required interaction still has no stable semantic contract, STOP, load the sap-webgui-recording skill, and request focused exploration; do not manufacture a locator.
Say before continuing: "Step 4b completed. Evidence: every case step and expected result maps to an allowed runtime operation or post-test verification check. Next: finish Step 4."
Say before continuing: "Step 4 completed. Evidence: each selected case was translated without omitted controls or invented helpers. Next: Step 5 — validate data keys."
Step 5 — Data-key naming
Say before acting: "Starting Step 5: validate every data.<key> reference against its data specification."
Data keys in data.k MUST match keys in .data.md. If .md references a data key that isn't in .data.md, STOP and follow define-data to update .data.md first.
Don't just eyeball this — run the deterministic check after writing the spec (see Step 7).
Say before continuing: "Step 5 completed. Evidence: every referenced data key exists in the matching .data.md. Next: Step 6 — anti-pattern review."
Step 6 — Anti-pattern review
Say before acting: "Starting Step 6: review generated scripts for prohibited patterns."
- ❌
await page.click(...) — always via sap.*
- ❌
await page.waitForTimeout(...) — helpers wait; if a step needs extra wait, that's a helper bug
- ❌
await page.locator('.something').click() — no CSS selectors
- ❌ Manual
await page.screenshot() — evidence is automatic
- ❌ Hardcoded material/plant/user/order numbers — always via
data.<key>
- ❌ Multiple test cases in one spec — one file per TC-ID
- ❌ Skipping any row of the state table. Every row must produce either an action, an assertion, or nothing (documented via
at default). Silent omission = hidden assumption.
- ❌ Trusting Steps alone without cross-checking the state table. If Steps say "Set Article to X and Execute" but the state table says another field must be RESET from a default, the state table wins.
- ❌ Emitting an action for a control that isn't in
_screens.md — STOP and follow explore-ui to re-explore it.
- ❌ Using
sap.clickButton(tabName) for a header tab — tabs have no role="button" and no title, so the button fallback silently finds a lookalike element or throws. Use sap.clickTab(tabName).
- ❌ Using
sap.setField(columnTitle, ..., ...) for an editable ALV grid cell — the cell input has no accessible name and no title, so the lookup fails. Use sap.setGridCell(columnTitle, rowIndex, value).
- ❌ Hand-rolling a grid-cell fill via
sap.raw() (e.g. frameLocator(...).locator('input[id$="#R,C#if"]').fill(...)) — a raw fill() sets the DOM value but skips the change/blur event ITS's ALV grid listens for, so the cell reverts and the case passes green on empty data. Use sap.setGridCell (it commits the value); if it can't drive the grid, report a runtime gap, don't hand-roll.
- ❌ Hardcoding
tbl<N> prefixes, [row,col] / #R,C# coordinates, or M0:... IDs in a sap.raw() block — those change across sessions AND across tab switches. If setGridCell doesn't fit the case, stop and re-explore.
- ❌ Using
expectGridHasRow to assert a ROW COUNT or match a short numeric literal — it is an UNANCHORED, case-insensitive substring match over cell text, so asserting "4" and "5" appear also matches "14", "45", or an amount of "400". Assert on a business-unique value or pass an anchored regex; count checks belong in as .
Say before continuing: "Step 6 completed. Evidence: generated scripts contain no prohibited selectors, waits, screenshots, hardcoded business data, or omitted state rows. Next: Step 7 — verify written specs."
Step 7 — After writing
Say before acting: "Starting Step 7: run deterministic verification for every written spec."
Call abapfs_verify_test_data_usage before handing off — mandatory, not optional.
This fails loudly if the spec references a data.<key> that <TC-XXX>.data.md never declared (the exact mistake that lets resolveTestData throw at run time), and warns about any declared key the spec never ended up using.
Then tell the user it is ready to run with abapfs_run_playwright_tests (headed: true is recommended the first time). Do not execute it during Phase 6; execution follows the run-scripts workflow in Phase 7.
Say before continuing: "Step 7 completed. Evidence: abapfs_verify_test_data_usage results recorded for every new or changed spec. Next: Step 8 — write the handoff."
Step 8 — Write the next-chat handoff
Say before acting: "Starting Step 8: write the Phase 6 handoff."
Before ending Phase 6, make the disk state sufficient for a brand-new run-scripts chat:
- One
.spec.ts exists per selected TC-ID under <TEST_FOLDER>/tests/<PROGRAM>/test-scripts/.
abapfs_verify_test_data_usage has passed for each new/changed spec, or every failure is reported explicitly.
- Data readiness gaps discovered in Step 0 are listed; do not hide them by hardcoding values.
- Your final response names
<TEST_FOLDER>, <PROGRAM>, primary connectionId, built TC-IDs, verification results, and cases not yet runnable.
Your final message MUST tell the user the exact next step: "Next: Phase 7 — start a new chat, load the run-scripts skill, and say: Run <PROGRAM> on <connectionId> using the specs on disk." Naming the skill matters — without it the next chat tends to skip loading run-scripts and improvise.
Say after the handoff is complete: "Step 8 completed. Evidence: built cases, verification results, and readiness blockers were handed off. Next phase: Phase 7 — in a new chat, load the run-scripts skill and follow it."