From 057ffc2237274324885996cead8f7d22a0983f85 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Jun 2026 10:21:48 +0300 Subject: [PATCH 1/5] feat(ui5): Add OPA5 guidelines as Skills --- plugins/ui5/skills/opa5/SKILL.md | 37 ++++++++++ .../references/enable-testrecorder-tooling.md | 68 +++++++++++++++++++ .../opa5/references/handle-multiple-views.md | 35 ++++++++++ .../skills/opa5/references/handle-teardown.md | 26 +++++++ .../skills/opa5/references/initial-setup.md | 24 +++++++ 5 files changed, 190 insertions(+) create mode 100644 plugins/ui5/skills/opa5/SKILL.md create mode 100644 plugins/ui5/skills/opa5/references/enable-testrecorder-tooling.md create mode 100644 plugins/ui5/skills/opa5/references/handle-multiple-views.md create mode 100644 plugins/ui5/skills/opa5/references/handle-teardown.md create mode 100644 plugins/ui5/skills/opa5/references/initial-setup.md diff --git a/plugins/ui5/skills/opa5/SKILL.md b/plugins/ui5/skills/opa5/SKILL.md new file mode 100644 index 0000000..c5dda03 --- /dev/null +++ b/plugins/ui5/skills/opa5/SKILL.md @@ -0,0 +1,37 @@ +--- +name: opa5 +description: ALWAYS load before any OPA5 task — implementing, updating, or verifying a test. Provides best practices for structuring the test and tools for efficient diagnosis of test failures. +--- + +# OPA5 guidelines and tools + +## Setup the test environment **before executing the OPA5 test** +**Purpose:** Efficient inspection of test failures with minimal steps. +**Prerequisites:** A tool to load the OPA5 test in the browser and evaluate javascript in the browser window (e.g. MCP Playwright) + +### 1. Setup TestRecorder tooling (UI5 version ≥ 1.147 only) +**Purpose:** +- Diagnose issues by inspecting the live control tree in the browser, including private/internal controls the test needs to find; +- Collect reliable OPA5 snippets for non-trivial actions and assertions. +**Setup:** Follow `references/enable-testrecorder-tooling.md` for detailed instructions. + +### 2. **ALWAYS** enable pause-on-failure mode (all UI5 versions) +**Purpose:** When enabled, execution pauses on the first test failure and the app remains live in the browser exactly as it was at the point of failure — no teardown, no reload happens automatically. The paused state persists until you explicitly navigate away, so you can inspect the actual UI directly (without reloading) in the browser to compare it against what the test expected. +**Setup:** Add the following line to your test setup (e.g. before the first `opaTest` of your Journey under test): +```javascript +sap.ui.test.qunitPause.pauseRule = "assert,timeout"; // enables pause on assertion failures and timeouts +``` +### 3. Isolate the journey under test (all UI5 versions) +**Purpose:** Avoid waiting for unrelated journeys on each iteration +**Isolation strategy:** If the setup does not allow to run individual journeys, comment out unrelated journey imports in the test entry point. + +## Verification workflow +1. Enable the test environment setup above and load the test in the browser. +2. When the test pauses on failure, inspect the app first — verify the full causal chain with no gaps before changing any code. **ALWAYS** rule out app-side issues before assuming the test is wrong. +3. Once each new/updated journey succeeds in isolation, restore all journey imports for final validation. +4. Once all journeys pass with imports restored, remove the `sap.ui.testrecorder` library from the app and disable pause-on-failure. + +## Handling special cases +- **Initial setup for OPA5 test** → follow `references/initial-setup.md` +- **If the test-case spans multiple views** → follow `references/handle-multiple-views.md` +- **Teardown the app** → follow `references/handle-teardown.md` \ No newline at end of file diff --git a/plugins/ui5/skills/opa5/references/enable-testrecorder-tooling.md b/plugins/ui5/skills/opa5/references/enable-testrecorder-tooling.md new file mode 100644 index 0000000..5cfd6d2 --- /dev/null +++ b/plugins/ui5/skills/opa5/references/enable-testrecorder-tooling.md @@ -0,0 +1,68 @@ +# TestRecorder tooling + +The `sap.ui.testrecorder` library provides module `sap.ui.testrecorder.ControlTree` that allows to: +- inspect the live control tree in the browser +- retrieve reliable OPA5 snippets for interacting with any part of the control tree + +## Prerequisites to use `sap.ui.testrecorder.ControlTree` + +- **UI5 version ≥ 1.147** +- Tool to load the OPA5 test in the browser and evaluate javascript in the browser window (e.g. MCP Playwright) +- **`sap.ui.testrecorder` library loaded** — temporarily add to the app's library declarations in the places listed below (**ORDERED BY PRIORITY**): + 1. `ui5.yaml` → `framework.libraries`: `- name: sap.ui.testrecorder` + 2. `manifest.json` → `sap.ui5.dependencies.libs`: `"sap.ui.testrecorder": {}` + 3. `index.html` → `data-sap-ui-libs` bootstrap attribute: append `,sap.ui.testrecorder` + + > After adding to `ui5.yaml` ensure the server is serving the added library before proceeding: + > ```bash + > curl -s -o /dev/null -w "%{http_code}" \ + > http://localhost:8080/resources/sap/ui/testrecorder/ControlTree.js + > ``` + > If 404, **ALWAYS** start a fresh server on the next free port (8081, 8082, …) and use that port + > for all subsequent browser navigation + + > Remove `sap.ui.testrecorder` after use — not needed at runtime. + > Kill after use any started fresh server instance. + +## `sap.ui.testrecorder.ControlTree` API + +**`ControlTree.search(query)`** — Search the live UI5 control tree. +- Returns `Promise` — a tree snapshot with matching controls and their parents +- `query=""` returns the full tree; `query="anchorBar"` returns filtered results +- Matches against control type short names, non-default property values, and accessibility attributes +- Each node carries a `nodeId="N_M"` (snapshot N, node M) — use these in `ControlTree` methods that require a `nodeId` parameter + +**`ControlTree.getControlData(nodeId)`** — Get selector and full control state. +- Returns `Promise<{ selectorSnippet, properties, aggregations, associations, bindings }>` +- `selectorSnippet` — OPA5 `waitFor` code to locate the control (use as the base selector) +- Other fields provide live control state for customizing assertions + +**`ControlTree.press(nodeId, settings?)`** — Press a control and get its OPA5 action snippet. +- Returns `Promise` — an OPA5 `waitFor` snippet with `actions: new Press()` +- Also **replays the press** on the running app, advancing the UI state for the next search +- Optional `settings`: `altKey`, `ctrlKey`, `shiftKey`, `xPercentage`, `yPercentage` + +**`ControlTree.enterText(nodeId, settings)`** — Type into a control and get its OPA5 action snippet. +- Returns `Promise` — an OPA5 `waitFor` snippet with `actions: new EnterText()` +- Also **replays the text entry** on the running app +- `settings`: `text`, `clearTextFirst` (default `true`), `submitText` (default `true`) + +## Example Usage + +```javascript +sap.ui.require(["sap/ui/testrecorder/ControlTree"], function(ControlTree) { + // Navigate to the state where the anchor bar is visible, then: + ControlTree.search("anchorBar").then(function(tree) { + // Parse: Button nodeId="1_8" text="Methods" + return ControlTree.press("1_8"); + }).then(function(actionSnippet) { + // actionSnippet is the OPA5 snippet — save it; UI has now navigated + return ControlTree.search("selectedSection"); + }).then(function(tree) { + // Parse: ObjectPageLayout nodeId="2_3" + return ControlTree.getControlData("2_3"); + }).then(function(result) { + // result.selectorSnippet + result.associations → build assertion + }); +}); +``` \ No newline at end of file diff --git a/plugins/ui5/skills/opa5/references/handle-multiple-views.md b/plugins/ui5/skills/opa5/references/handle-multiple-views.md new file mode 100644 index 0000000..84af26a --- /dev/null +++ b/plugins/ui5/skills/opa5/references/handle-multiple-views.md @@ -0,0 +1,35 @@ +# Organization of actions/assertions + +**ALWAYS** add the new actions/assertions to the semantically corresponding page object + +Example 1 +❌ Anti-Pattern: +Adding selector for a control from `App.view.xml` into the page object for its **nested** view (e.g. into `integration/pages/Detail.js` for `Detail.view.xml`): +```javascript +// integration/pages/Detail.js +iShouldSeeTheAppInFullScreenMode: function () { + return this.waitFor({ + id: "layout", + viewName: "App", + success: function () { ... } + }); +}, +``` +✅ Correct Pattern: +Place the assertion for the `App.view.xml` in page object file `integration/pages/App.js` + +Example 2: +❌ Anti-Pattern: +View-specific page object file containing selector for cross-view navigation: +```javascript +// integration/pages/Detail.js +iShouldSeeTheHash: function (sExpectedHash) { + return this.waitFor({ + success: function () { + Opa5.assert.strictEqual(Opa5.getHashChanger().getHash(), sExpectedHash, "The Hash not correct"); + } + }); +}, +``` +✅ Correct Pattern: +Place the actions/assertions for cross-view navigation into page object `integration/pages/Browser.js` diff --git a/plugins/ui5/skills/opa5/references/handle-teardown.md b/plugins/ui5/skills/opa5/references/handle-teardown.md new file mode 100644 index 0000000..75f9128 --- /dev/null +++ b/plugins/ui5/skills/opa5/references/handle-teardown.md @@ -0,0 +1,26 @@ +# Teardown the app + +QUnit requires assertions to validate tests. Teardown methods are NOT assertions. + +❌ Incorrect: +```javascript +opaTest("Should clean up", function(Given, When, Then) { + Then.iTeardownMyApp(); // ❌ missing assertion (because teardown is not an assertion) +}); +``` + +❌ Incorrect: +```javascript +opaTest("Should assert state and clean up", function(Given, When, Then) { + Then.onTheWorklistPage.iShouldSeeTheTable() + .and.onTheWorklistPage.iTeardownMyApp(); // ❌ chaining on wrong object +}); +``` + +✅ Correct: +```javascript +opaTest("Should assert state and clean up", function(Given, When, Then) { + Then.onTheWorklistPage.iShouldSeeTheTable() // ✅ assertion before teardown + .and.iTeardownMyApp(); // ✅ correct chaining +}); +``` \ No newline at end of file diff --git a/plugins/ui5/skills/opa5/references/initial-setup.md b/plugins/ui5/skills/opa5/references/initial-setup.md new file mode 100644 index 0000000..4ea4052 --- /dev/null +++ b/plugins/ui5/skills/opa5/references/initial-setup.md @@ -0,0 +1,24 @@ +# Initial Setup + +1. **ALWAYS** use this folder layout: +``` +test/integration/ +├── opaTests.qunit.js ← single entry point +├── pages/ +│ ├── Welcome.js ← one page object per view (name matches the view) +│ ├── Items.js +│ └── Browser.js ← cross-view actions (navigation, hash) +├── WelcomeJourney.js ← one journey per feature/functionality +└── FilterItemsJourney.js +``` + +2. **ALWAYS** enable `autoWait` and define `viewNamespace` globally in `opaTests.qunit.js`. +```javascript +// opaTests.qunit.js +sap.ui.define(["sap/ui/test/Opa5"], function (Opa5) { + "use strict"; + Opa5.extendConfig({ + autoWait: true, + viewNamespace: "com.myorg.myapp.view." + }); +``` \ No newline at end of file From 934a92be704f8b5c26efa1d8c72c0bf21e5e9f46 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Jun 2026 10:44:07 +0300 Subject: [PATCH 2/5] feat(ui5): Correct formatting --- plugins/ui5/skills/opa5/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ui5/skills/opa5/SKILL.md b/plugins/ui5/skills/opa5/SKILL.md index c5dda03..35842bd 100644 --- a/plugins/ui5/skills/opa5/SKILL.md +++ b/plugins/ui5/skills/opa5/SKILL.md @@ -21,6 +21,7 @@ description: ALWAYS load before any OPA5 task — implementing, updating, or ver ```javascript sap.ui.test.qunitPause.pauseRule = "assert,timeout"; // enables pause on assertion failures and timeouts ``` + ### 3. Isolate the journey under test (all UI5 versions) **Purpose:** Avoid waiting for unrelated journeys on each iteration **Isolation strategy:** If the setup does not allow to run individual journeys, comment out unrelated journey imports in the test entry point. From 6931ec123cf3163004c4297a3c40181d8320887b Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Wed, 3 Jun 2026 18:42:13 +0300 Subject: [PATCH 3/5] feat(ui5): Reflect review feedback on skill content --- plugins/ui5/skills/opa5/SKILL.md | 32 +++++++++---------- .../{initial-setup.md => configuration.md} | 6 ++-- .../opa5/references/handle-multiple-views.md | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) rename plugins/ui5/skills/opa5/references/{initial-setup.md => configuration.md} (92%) diff --git a/plugins/ui5/skills/opa5/SKILL.md b/plugins/ui5/skills/opa5/SKILL.md index 35842bd..e0ddabc 100644 --- a/plugins/ui5/skills/opa5/SKILL.md +++ b/plugins/ui5/skills/opa5/SKILL.md @@ -1,38 +1,36 @@ --- name: opa5 -description: ALWAYS load before any OPA5 task — implementing, updating, or verifying a test. Provides best practices for structuring the test and tools for efficient diagnosis of test failures. +description: This skill should be used in any OPA5 task - creating, modifying, extending, debugging, fixing or reviewing an integration test. Use when the user asks to "write an OPA5 test", "add an OPA5 journey", "fix the OPA5 test failure" or mentions OPA5 or its components - opaTest, page object, journey, waitFor. --- # OPA5 guidelines and tools -## Setup the test environment **before executing the OPA5 test** +## Set up inspection tools **before executing the OPA5 test** **Purpose:** Efficient inspection of test failures with minimal steps. **Prerequisites:** A tool to load the OPA5 test in the browser and evaluate javascript in the browser window (e.g. MCP Playwright) -### 1. Setup TestRecorder tooling (UI5 version ≥ 1.147 only) +### 1. Set up TestRecorder tooling (UI5 version ≥ 1.147 only) **Purpose:** - Diagnose issues by inspecting the live control tree in the browser, including private/internal controls the test needs to find; - Collect reliable OPA5 snippets for non-trivial actions and assertions. **Setup:** Follow `references/enable-testrecorder-tooling.md` for detailed instructions. -### 2. **ALWAYS** enable pause-on-failure mode (all UI5 versions) -**Purpose:** When enabled, execution pauses on the first test failure and the app remains live in the browser exactly as it was at the point of failure — no teardown, no reload happens automatically. The paused state persists until you explicitly navigate away, so you can inspect the actual UI directly (without reloading) in the browser to compare it against what the test expected. -**Setup:** Add the following line to your test setup (e.g. before the first `opaTest` of your Journey under test): +### 2. Enable pause-on-failure mode (all UI5 versions) +**Purpose:** When enabled, execution pauses on the first test failure and the app remains live in the browser exactly as it was at the point of failure — no teardown, no reload happens automatically. The paused state persists until you explicitly navigate away, so you can inspect the actual UI directly (without reloading) in the browser to see why it differs from what the test expected. +**Setup:** Add the following line to your test entry point (right before `Opa5.extendConfig`): ```javascript +// Inside the existing sap.ui.define callback in your test entry point sap.ui.test.qunitPause.pauseRule = "assert,timeout"; // enables pause on assertion failures and timeouts +// Opa5.extendConfig({...}); ``` -### 3. Isolate the journey under test (all UI5 versions) -**Purpose:** Avoid waiting for unrelated journeys on each iteration -**Isolation strategy:** If the setup does not allow to run individual journeys, comment out unrelated journey imports in the test entry point. - ## Verification workflow -1. Enable the test environment setup above and load the test in the browser. -2. When the test pauses on failure, inspect the app first — verify the full causal chain with no gaps before changing any code. **ALWAYS** rule out app-side issues before assuming the test is wrong. -3. Once each new/updated journey succeeds in isolation, restore all journey imports for final validation. -4. Once all journeys pass with imports restored, remove the `sap.ui.testrecorder` library from the app and disable pause-on-failure. +1. Enable the inspection tools above and load the test in the browser. +2. When the test pauses on failure, inspect the app first — verify the full causal chain with no gaps before changing any code. Rule out app-side issues before assuming the test is wrong. +3. Iterate on the test until all journeys pass. +4. Once all journeys pass, remove the `sap.ui.testrecorder` library from the app and the pause-on-failure rule `sap.ui.test.qunitPause.pauseRule`. -## Handling special cases -- **Initial setup for OPA5 test** → follow `references/initial-setup.md` +## Handle special cases +- **Initial configuration for OPA5 test** → follow `references/configuration.md` - **If the test-case spans multiple views** → follow `references/handle-multiple-views.md` -- **Teardown the app** → follow `references/handle-teardown.md` \ No newline at end of file +- **Teardown the app** → follow `references/handle-teardown.md` diff --git a/plugins/ui5/skills/opa5/references/initial-setup.md b/plugins/ui5/skills/opa5/references/configuration.md similarity index 92% rename from plugins/ui5/skills/opa5/references/initial-setup.md rename to plugins/ui5/skills/opa5/references/configuration.md index 4ea4052..e45d6dd 100644 --- a/plugins/ui5/skills/opa5/references/initial-setup.md +++ b/plugins/ui5/skills/opa5/references/configuration.md @@ -1,6 +1,6 @@ -# Initial Setup +# Configuration -1. **ALWAYS** use this folder layout: +1. Use this folder layout: ``` test/integration/ ├── opaTests.qunit.js ← single entry point @@ -21,4 +21,6 @@ sap.ui.define(["sap/ui/test/Opa5"], function (Opa5) { autoWait: true, viewNamespace: "com.myorg.myapp.view." }); + // ... +}); ``` \ No newline at end of file diff --git a/plugins/ui5/skills/opa5/references/handle-multiple-views.md b/plugins/ui5/skills/opa5/references/handle-multiple-views.md index 84af26a..e0ceeb2 100644 --- a/plugins/ui5/skills/opa5/references/handle-multiple-views.md +++ b/plugins/ui5/skills/opa5/references/handle-multiple-views.md @@ -1,8 +1,8 @@ -# Organization of actions/assertions +# Page Object Organization Across Multiple Views **ALWAYS** add the new actions/assertions to the semantically corresponding page object -Example 1 +Example 1: ❌ Anti-Pattern: Adding selector for a control from `App.view.xml` into the page object for its **nested** view (e.g. into `integration/pages/Detail.js` for `Detail.view.xml`): ```javascript From daea1947a67081c39dce438009a060d1cf985983 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Wed, 3 Jun 2026 19:20:17 +0300 Subject: [PATCH 4/5] feat(ui5): Add readme entry for opa5 skill --- plugins/ui5/README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/ui5/README.md b/plugins/ui5/README.md index 3d221be..e8f1034 100644 --- a/plugins/ui5/README.md +++ b/plugins/ui5/README.md @@ -1,6 +1,6 @@ # UI5 Plugin for Coding Agents -Complete SAPUI5 / OpenUI5 plugin for coding agents with MCP tools, API documentation access, linting capabilities, and development guidelines. +Complete SAPUI5 / OpenUI5 plugin for coding agents with MCP tools, API documentation access, linting capabilities, development and integration testing guidelines. --- @@ -42,6 +42,15 @@ Development guidelines for UI Integration Cards (also known as UI5 Integration C - **i18n** - Bind all user-facing strings to the i18n model; never hardcode - **Actions** - Use the `actions` property for links and interactions; never inline `` tags or hand-roll URL handlers +#### opa5 + +Guidelines and debugging workflow for OPA5 integration tests: + +- **Failure inspection** - Pause-on-failure mode (`sap.ui.test.qunitPause.pauseRule`) keeps the app live at the failure point for browser inspection +- **TestRecorder tooling** - Temporary `sap.ui.testrecorder.ControlTree` integration to inspect the live control tree and generate reliable OPA5 snippets (UI5 ≥ 1.147) +- **Page object organization** - Placement of actions and assertions across views +- **App teardown** - Cleanup patterns in OPA5 journey tests + --- ## Installation From f3666fef12609cf9c63aa2e70d7408ffc29ab796 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Wed, 3 Jun 2026 19:30:06 +0300 Subject: [PATCH 5/5] feat(ui5): Add keyword entry for opa5 skill in plugin.json --- plugins/ui5/.github/plugin/plugin.json | 1 + plugins/ui5/plugin.json | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/ui5/.github/plugin/plugin.json b/plugins/ui5/.github/plugin/plugin.json index f4f2d63..f3ef4ee 100644 --- a/plugins/ui5/.github/plugin/plugin.json +++ b/plugins/ui5/.github/plugin/plugin.json @@ -13,6 +13,7 @@ "ui5", "sapui5", "openui5", + "opa5", "plugin", "linter", "api-documentation", diff --git a/plugins/ui5/plugin.json b/plugins/ui5/plugin.json index f4f2d63..f3ef4ee 100644 --- a/plugins/ui5/plugin.json +++ b/plugins/ui5/plugin.json @@ -13,6 +13,7 @@ "ui5", "sapui5", "openui5", + "opa5", "plugin", "linter", "api-documentation",