|
| 1 | +# Robot Framework Maintenance Comparison PoC Design |
| 2 | + |
| 3 | +Status: Proposed |
| 4 | +Date: 2026-04-27 |
| 5 | +Scope: Evidence-gathering PoC comparing two Robot Framework maintenance strategies for CumulusCI |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +CumulusCI ships a Robot Framework integration with ~37 Selenium keywords (`Salesforce.py`), a versioned locator dictionary (`locators_*.py`), a page object model, and a nascent Playwright library (`SalesforcePlaywright.py`, 8 keywords). The Selenium locators are tightly coupled to Salesforce Lightning's DOM structure, which changes every API version. Bryan's original architecture (copy-on-write locator files, `locator_manager` with dot-notation, parameterized format strings, multi-tier keyword libraries) was designed for periodic per-release maintenance. That maintenance stopped due to team capacity, not architectural failure. |
| 10 | + |
| 11 | +Two questions now need evidence-backed answers: |
| 12 | + |
| 13 | +1. With agent token budgets unblocked, is refreshing Selenium locators per API version a tractable ongoing cost? |
| 14 | +2. Is porting to Playwright cheaper to maintain per release, and does it justify the migration cost? |
| 15 | + |
| 16 | +The parent v5 roadmap requires an ADR on this topic and explicitly mandates evidence before decision. |
| 17 | + |
| 18 | +### Shadow DOM Finding (from WIP) |
| 19 | + |
| 20 | +API v66 has migrated parts of Lightning Experience from Aura (light DOM) to LWC (shadow DOM). Selenium 3 XPath/CSS cannot pierce shadow DOM. Affected elements include the actions ribbon on filtered list views and the List View Controls button. This is a structural ceiling for Selenium that grows with each API version as Salesforce continues the Aura-to-LWC migration. |
| 21 | + |
| 22 | +## Goals |
| 23 | + |
| 24 | +- Measure agent token cost to refresh CCI's Selenium locators from API v57 to v66. |
| 25 | +- Measure agent token cost to port ~8 high-value Playwright keywords covering the same test surface. |
| 26 | +- Quantify locator durability: what fraction of Selenium locators reference Aura internals (fragile) vs. SLDS/Lightning-stable selectors? |
| 27 | +- Identify structural coverage ceilings (shadow DOM elements unreachable by Selenium 3). |
| 28 | +- Write ADR 0004 backed by the comparison evidence. |
| 29 | +- Draft a local "Robot Locator Refresh" skill spec codifying Bryan's best practices for agent-driven locator updates. |
| 30 | + |
| 31 | +## Non-Goals |
| 32 | + |
| 33 | +- Full Playwright feature parity with `Salesforce.py` (37 keywords). Track B ports ~8. |
| 34 | +- Upgrading Selenium 3 to 4 (that is in the dependency-modernization phase). |
| 35 | +- Fixing pre-existing `[Return]` deprecation warnings or `_tkinter` import noise. |
| 36 | +- Refactoring the `locator_manager` or page object architecture. |
| 37 | +- NPSP/EDA downstream migration planning (noted in ADR as future work). |
| 38 | +- Resolving the `form_handlers.py` Selenium coupling for Playwright (out of scope; Track B uses Playwright-native form filling). |
| 39 | + |
| 40 | +## Decisions |
| 41 | + |
| 42 | +1. **Two-track comparison against the same scratch org (API v66).** |
| 43 | + Track A: Selenium locator refresh. Track B: Playwright keyword port. Same test surface, same org, comparable evidence. |
| 44 | + |
| 45 | +2. **Track A preserves Bryan's architecture.** |
| 46 | + `locators_66.py` deepcopies `locators_57` and overrides only changed locators. No structural changes to `locator_manager`, `Salesforce.py`, or the versioning system. |
| 47 | + |
| 48 | +3. **Track B uses Playwright-native locators (not `locator_manager`).** |
| 49 | + `SalesforcePlaywright.py` already ignores the versioned locator system. Track B continues that pattern, using Playwright's accessibility-first selectors (`role=`, `text=`, CSS). This is a data point on whether `locator_manager` adds value for Playwright. |
| 50 | + |
| 51 | +4. **Inline locators in page objects are in scope for Track A.** |
| 52 | + `BasePageObjects.py` and `ObjectManagerPageObject.py` contain hardcoded locators outside the versioning system. Track A fixes breakage in these files too, since they are part of the real maintenance cost. |
| 53 | + |
| 54 | +5. **ADR format follows `docs/adrs/templates/template.md`.** |
| 55 | + Status starts as `Proposed`. Sections: Context and Problem Statement, Assumptions, Constraints, Decision, Considered Options, Decision Outcome, Consequences, References. |
| 56 | + |
| 57 | +6. **Skill spec is local only.** |
| 58 | + Written to `~/.cursor/skills-cursor/robot-locator-refresh/SKILL.md`, not committed to the repo. |
| 59 | + |
| 60 | +## Current-State Inputs (WIP Baseline) |
| 61 | + |
| 62 | +The following work was completed in a prior session before the formal loop was established. It is preserved as evidence and starting state, not reverted. |
| 63 | + |
| 64 | +### Completed |
| 65 | + |
| 66 | +- Scratch org `robot-poc` created (API v66, expires ~May 4 2026). |
| 67 | +- `locators_66.py` created with 3 locator overrides: |
| 68 | + - `actions`: added `//div[contains(@class, 'slds-page-header')]` as OR branch. |
| 69 | + - `app_launcher.current_app`: broadened to `//*[contains(@class,'appName')][.//text()='{}']`. |
| 70 | + - `list_view_menu.button`: added `aria-label` CSS fallback (not yet verified against shadow DOM). |
| 71 | +- Test file adjustments for API v66 UI changes: |
| 72 | + - `TestLibraryA.py`: `breadcrumbDetail` class replaced with `//a[@role='tab'][.='{}']`. |
| 73 | + - `locators.robot` line 20: `text:Mobile Publisher` changed to `text:Object Manager`. |
| 74 | +- 86 of 88 Selenium tests pass across 10 suites. |
| 75 | +- `forms.robot` radiobutton test needs re-run after `list_view_menu.button` fix. |
| 76 | + |
| 77 | +### Remaining |
| 78 | + |
| 79 | +- Verify `forms.robot` fix or document as shadow DOM limitation. |
| 80 | +- Run full 11-suite battery for final confirmation. |
| 81 | +- Run 4 page object suites and fix inline locator breakage. |
| 82 | +- Locator durability audit. |
| 83 | +- Skill spec draft. |
| 84 | +- All of Track B (Playwright port + test). |
| 85 | +- Comparison analysis. |
| 86 | +- ADR 0004. |
| 87 | +- Parent roadmap update. |
| 88 | + |
| 89 | +## Architecture |
| 90 | + |
| 91 | +### Locator Version Resolution |
| 92 | + |
| 93 | +```mermaid |
| 94 | +flowchart LR |
| 95 | + orgApi["Org API version (e.g. 66)"] --> resolver["get_locator_module_name(66)"] |
| 96 | + resolver --> check{"locators_66.py exists?"} |
| 97 | + check -- yes --> load["import locators_66"] |
| 98 | + check -- no --> fallback["use highest-numbered locators_*.py"] |
| 99 | + load --> update["lex_locators.update(module.lex_locators)"] |
| 100 | + fallback --> update |
| 101 | + update --> register["locator_manager.register_locators('sf', lex_locators)"] |
| 102 | +``` |
| 103 | + |
| 104 | +### Locator Inheritance Chain |
| 105 | + |
| 106 | +``` |
| 107 | +locators_56.py (base dict, ~87 lines, all locators) |
| 108 | + | |
| 109 | + v deepcopy |
| 110 | +locators_57.py (no changes, identity copy) |
| 111 | + | |
| 112 | + v deepcopy + 3 overrides |
| 113 | +locators_66.py (WIP: actions, app_launcher.current_app, list_view_menu.button) |
| 114 | +``` |
| 115 | + |
| 116 | +Versions 58-65 all fall back to 57 via the resolver's highest-numbered fallback. |
| 117 | + |
| 118 | +### Inline Locator Inventory (Outside Versioning System) |
| 119 | + |
| 120 | +| File | Locator Count | Risk | |
| 121 | +| ------------------------------------------------- | ------------- | -------------------------------------------- | |
| 122 | +| `BasePageObjects.py` (`ModalMixin`) | ~8 | `uiModal` class is Aura-internal | |
| 123 | +| `ObjectManagerPageObject.py` | 13 | Standalone dict, fully outside versioning | |
| 124 | +| `Salesforce.py` (modal-container, label strategy) | ~3 | Mixed Aura/SLDS | |
| 125 | +| `form_handlers.py` (combobox/lookup values) | ~2 | Lightning component selectors, moderate risk | |
| 126 | + |
| 127 | +### Track B: SalesforcePlaywright.py |
| 128 | + |
| 129 | +Currently 8 keywords, no connection to `lex_locators` or `locator_manager`. Hardcodes SLDS selectors inline. Track B adds ~8 keywords covering: app launcher navigation, form population, modal handling, related list interaction. |
| 130 | + |
| 131 | +## Comparison Criteria |
| 132 | + |
| 133 | +| Metric | How Measured | |
| 134 | +| ------------------------------- | -------------------------------------------------------------------------- | |
| 135 | +| Agent token cost | Approximate from conversation length per track | |
| 136 | +| Locator durability | Count Aura-internal refs vs. SLDS/Lightning-stable refs in final artifacts | |
| 137 | +| Shadow DOM ceiling | Count elements unreachable by Selenium 3 but accessible via Playwright | |
| 138 | +| Per-release maintenance posture | Estimated effort to repeat locator refresh for next API version | |
| 139 | +| Coverage gap | Keywords ported vs. total `Salesforce.py` surface (37 keywords) | |
| 140 | +| Downstream impact | Qualitative assessment of NPSP/EDA migration cost per path | |
| 141 | + |
| 142 | +## Verification Strategy |
| 143 | + |
| 144 | +- **Track A Selenium battery**: 11 suites run via `uv run cci task run robot --org robot-poc -o suites <list> -o vars BROWSER:headlesschrome` |
| 145 | +- **Track A page objects**: 4 suites under `cumulusci/robotframework/tests/salesforce/pageobjects/` |
| 146 | +- **Track B Playwright**: 1 end-to-end suite via `uv run cci task run robot --org robot-poc -o suites <path> -o vars BROWSER:headlesschrome` |
| 147 | +- **Unit tests**: `pytest cumulusci/robotframework/tests/test_salesforce_locators.py cumulusci/robotframework/tests/test_locator_manager.py` |
| 148 | +- **Known noise to ignore**: `_tkinter` Dialogs import error (cosmetic), `[Return]` deprecation warnings (pre-existing) |
| 149 | + |
| 150 | +## Risks and Mitigations |
| 151 | + |
| 152 | +- **Scratch org expiry (~May 4)**: all work must complete before expiry. Mitigation: org can be recreated if needed. |
| 153 | +- **Shadow DOM expansion**: more elements may be unreachable than currently identified. Mitigation: document all shadow DOM encounters as evidence for the ADR. |
| 154 | +- **Playwright `rfbrowser` installation**: requires `cci robot install_playwright`. Mitigation: already available in the dev environment. |
| 155 | +- **Token cost measurement is approximate**: conversation length is a proxy, not a precise metric. Mitigation: sufficient for directional comparison. |
| 156 | + |
| 157 | +## Acceptance Criteria |
| 158 | + |
| 159 | +- All 11 Selenium test suites pass (or failures documented as shadow DOM limitations). |
| 160 | +- All 4 page object suites pass (or failures documented with root cause). |
| 161 | +- Locator durability audit complete with counts. |
| 162 | +- At least 1 Playwright end-to-end test passes against the same org. |
| 163 | +- ADR 0004 written with evidence from both tracks. |
| 164 | +- Local skill spec drafted. |
| 165 | +- Parent roadmap updated per closeout obligations. |
| 166 | + |
| 167 | +## Deliverables |
| 168 | + |
| 169 | +1. `cumulusci/robotframework/locators_66.py` (committed to repo) |
| 170 | +2. Any keyword/page-object/test fixes (committed to repo) |
| 171 | +3. Expanded `SalesforcePlaywright.py` with ~8 ported keywords (committed to repo) |
| 172 | +4. 1 Playwright end-to-end `.robot` test (committed to repo) |
| 173 | +5. `docs/adrs/0004-robot-framework-selenium-vs-playwright.md` (committed to repo) |
| 174 | +6. `~/.cursor/skills-cursor/robot-locator-refresh/SKILL.md` (local only) |
| 175 | +7. Parent roadmap updates (local plan file) |
0 commit comments