Skip to content

Commit 5108428

Browse files
committed
feat(ui5): Add OPA5 guidelines as Skills
1 parent 7acd832 commit 5108428

5 files changed

Lines changed: 190 additions & 0 deletions

File tree

plugins/ui5/skills/opa5/SKILL.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: opa5
3+
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.
4+
---
5+
6+
# OPA5 guidelines and tools
7+
8+
## Setup the test environment **before executing the OPA5 test**
9+
**Purpose:** Efficient inspection of test failures with minimal steps.
10+
**Prerequisites:** A tool to load the OPA5 test in the browser and evaluate javascript in the browser window (e.g. MCP Playwright)
11+
12+
### 1. Setup TestRecorder tooling (UI5 version ≥ 1.147 only)
13+
**Purpose:**
14+
- Diagnose issues by inspecting the live control tree in the browser, including private/internal controls the test needs to find;
15+
- Collect reliable OPA5 snippets for non-trivial actions and assertions.
16+
**Setup:** Follow `references/enable-testrecorder-tooling.md` for detailed instructions.
17+
18+
### 2. **ALWAYS** enable pause-on-failure mode (all UI5 versions)
19+
**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.
20+
**Setup:** Add the following line to your test setup (e.g. before the first `opaTest` of your Journey under test):
21+
```javascript
22+
sap.ui.test.qunitPause.pauseRule = "assert,timeout"; // enables pause on assertion failures and timeouts
23+
```
24+
### 3. Isolate the journey under test (all UI5 versions)
25+
**Purpose:** Avoid waiting for unrelated journeys on each iteration
26+
**Isolation strategy:** If the setup does not allow to run individual journeys, comment out unrelated journey imports in the test entry point.
27+
28+
## Verification workflow
29+
1. Enable the test environment setup above and load the test in the browser.
30+
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.
31+
3. Once each new/updated journey succeeds in isolation, restore all journey imports for final validation.
32+
4. Once all journeys pass with imports restored, remove the `sap.ui.testrecorder` library from the app and disable pause-on-failure.
33+
34+
## Handling special cases
35+
- **Initial setup for OPA5 test** → follow `references/initial-setup.md`
36+
- **If the test-case spans multiple views** → follow `references/handle-multiple-views.md`
37+
- **Teardown the app** → follow `references/handle-teardown.md`
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# TestRecorder tooling
2+
3+
The `sap.ui.testrecorder` library provides module `sap.ui.testrecorder.ControlTree` that allows to:
4+
- inspect the live control tree in the browser
5+
- retrieve reliable OPA5 snippets for interacting with any part of the control tree
6+
7+
## Prerequisites to use `sap.ui.testrecorder.ControlTree`
8+
9+
- **UI5 version ≥ 1.147**
10+
- Tool to load the OPA5 test in the browser and evaluate javascript in the browser window (e.g. MCP Playwright)
11+
- **`sap.ui.testrecorder` library loaded** — temporarily add to the app's library declarations in the places listed below (**ORDERED BY PRIORITY**):
12+
1. `ui5.yaml``framework.libraries`: `- name: sap.ui.testrecorder`
13+
2. `manifest.json``sap.ui5.dependencies.libs`: `"sap.ui.testrecorder": {}`
14+
3. `index.html``data-sap-ui-libs` bootstrap attribute: append `,sap.ui.testrecorder`
15+
16+
> After adding to `ui5.yaml` ensure the server is serving the added library before proceeding:
17+
> ```bash
18+
> curl -s -o /dev/null -w "%{http_code}" \
19+
> http://localhost:8080/resources/sap/ui/testrecorder/ControlTree.js
20+
> ```
21+
> If 404, **ALWAYS** start a fresh server on the next free port (8081, 8082, …) and use that port
22+
> for all subsequent browser navigation
23+
24+
> Remove `sap.ui.testrecorder` after use — not needed at runtime.
25+
> Kill after use any started fresh server instance.
26+
27+
## `sap.ui.testrecorder.ControlTree` API
28+
29+
**`ControlTree.search(query)`** — Search the live UI5 control tree.
30+
- Returns `Promise<string>` — a tree snapshot with matching controls and their parents
31+
- `query=""` returns the full tree; `query="anchorBar"` returns filtered results
32+
- Matches against control type short names, non-default property values, and accessibility attributes
33+
- Each node carries a `nodeId="N_M"` (snapshot N, node M) — use these in `ControlTree` methods that require a `nodeId` parameter
34+
35+
**`ControlTree.getControlData(nodeId)`** — Get selector and full control state.
36+
- Returns `Promise<{ selectorSnippet, properties, aggregations, associations, bindings }>`
37+
- `selectorSnippet` — OPA5 `waitFor` code to locate the control (use as the base selector)
38+
- Other fields provide live control state for customizing assertions
39+
40+
**`ControlTree.press(nodeId, settings?)`** — Press a control and get its OPA5 action snippet.
41+
- Returns `Promise<string>` — an OPA5 `waitFor` snippet with `actions: new Press()`
42+
- Also **replays the press** on the running app, advancing the UI state for the next search
43+
- Optional `settings`: `altKey`, `ctrlKey`, `shiftKey`, `xPercentage`, `yPercentage`
44+
45+
**`ControlTree.enterText(nodeId, settings)`** — Type into a control and get its OPA5 action snippet.
46+
- Returns `Promise<string>` — an OPA5 `waitFor` snippet with `actions: new EnterText()`
47+
- Also **replays the text entry** on the running app
48+
- `settings`: `text`, `clearTextFirst` (default `true`), `submitText` (default `true`)
49+
50+
## Example Usage
51+
52+
```javascript
53+
sap.ui.require(["sap/ui/testrecorder/ControlTree"], function(ControlTree) {
54+
// Navigate to the state where the anchor bar is visible, then:
55+
ControlTree.search("anchorBar").then(function(tree) {
56+
// Parse: Button nodeId="1_8" text="Methods"
57+
return ControlTree.press("1_8");
58+
}).then(function(actionSnippet) {
59+
// actionSnippet is the OPA5 snippet — save it; UI has now navigated
60+
return ControlTree.search("selectedSection");
61+
}).then(function(tree) {
62+
// Parse: ObjectPageLayout nodeId="2_3"
63+
return ControlTree.getControlData("2_3");
64+
}).then(function(result) {
65+
// result.selectorSnippet + result.associations → build assertion
66+
});
67+
});
68+
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Organization of actions/assertions
2+
3+
**ALWAYS** add the new actions/assertions to the semantically corresponding page object
4+
5+
Example 1
6+
❌ Anti-Pattern:
7+
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`):
8+
```javascript
9+
// integration/pages/Detail.js
10+
iShouldSeeTheAppInFullScreenMode: function () {
11+
return this.waitFor({
12+
id: "layout",
13+
viewName: "App",
14+
success: function () { ... }
15+
});
16+
},
17+
```
18+
✅ Correct Pattern:
19+
Place the assertion for the `App.view.xml` in page object file `integration/pages/App.js`
20+
21+
Example 2:
22+
❌ Anti-Pattern:
23+
View-specific page object file containing selector for cross-view navigation:
24+
```javascript
25+
// integration/pages/Detail.js
26+
iShouldSeeTheHash: function (sExpectedHash) {
27+
return this.waitFor({
28+
success: function () {
29+
Opa5.assert.strictEqual(Opa5.getHashChanger().getHash(), sExpectedHash, "The Hash not correct");
30+
}
31+
});
32+
},
33+
```
34+
✅ Correct Pattern:
35+
Place the actions/assertions for cross-view navigation into page object `integration/pages/Browser.js`
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Teardown the app
2+
3+
QUnit requires assertions to validate tests. Teardown methods are NOT assertions.
4+
5+
❌ Incorrect:
6+
```javascript
7+
opaTest("Should clean up", function(Given, When, Then) {
8+
Then.iTeardownMyApp(); // ❌ missing assertion (because teardown is not an assertion)
9+
});
10+
```
11+
12+
❌ Incorrect:
13+
```javascript
14+
opaTest("Should assert state and clean up", function(Given, When, Then) {
15+
Then.onTheWorklistPage.iShouldSeeTheTable()
16+
.and.onTheWorklistPage.iTeardownMyApp(); // ❌ chaining on wrong object
17+
});
18+
```
19+
20+
✅ Correct:
21+
```javascript
22+
opaTest("Should assert state and clean up", function(Given, When, Then) {
23+
Then.onTheWorklistPage.iShouldSeeTheTable() // ✅ assertion before teardown
24+
.and.iTeardownMyApp(); // ✅ correct chaining
25+
});
26+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Initial Setup
2+
3+
1. **ALWAYS** use this folder layout:
4+
```
5+
test/integration/
6+
├── opaTests.qunit.js ← single entry point
7+
├── pages/
8+
│ ├── Welcome.js ← one page object per view (name matches the view)
9+
│ ├── Items.js
10+
│ └── Browser.js ← cross-view actions (navigation, hash)
11+
├── WelcomeJourney.js ← one journey per feature/functionality
12+
└── FilterItemsJourney.js
13+
```
14+
15+
2. **ALWAYS** enable `autoWait` and define `viewNamespace` globally in `opaTests.qunit.js`.
16+
```javascript
17+
// opaTests.qunit.js
18+
sap.ui.define(["sap/ui/test/Opa5"], function (Opa5) {
19+
"use strict";
20+
Opa5.extendConfig({
21+
autoWait: true,
22+
viewNamespace: "com.myorg.myapp.view."
23+
});
24+
```

0 commit comments

Comments
 (0)