robotframework-harness 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. rf_harness/__init__.py +3 -0
  2. rf_harness/_assets/.claude-plugin/plugin.json +10 -0
  3. rf_harness/_assets/bin/rfh-launch +60 -0
  4. rf_harness/_assets/bin/rfh-launch.cmd +19 -0
  5. rf_harness/_assets/bin/rfh_launch.py +719 -0
  6. rf_harness/_assets/hooks/hooks.json +84 -0
  7. rf_harness/_assets/skills/rf-harness/SKILL.md +78 -0
  8. rf_harness/_assets/skills/rf-harness/references/browser.md +157 -0
  9. rf_harness/_assets/skills/rf-harness/references/debug.md +39 -0
  10. rf_harness/_assets/skills/rf-harness/references/explore.md +52 -0
  11. rf_harness/_assets/skills/rf-harness/references/requests.md +37 -0
  12. rf_harness/_assets/skills/rf-harness/references/results.md +34 -0
  13. rf_harness/_assets/skills/rf-harness/references/selenium.md +40 -0
  14. rf_harness/_assets/skills/rf-harness/references/waits.md +28 -0
  15. rf_harness/_assets/variants/codex/hooks.json +72 -0
  16. rf_harness/_assets/variants/copilot/hooks.json +53 -0
  17. rf_harness/_assets/variants/opencode/plugins/rf-harness.js +130 -0
  18. rf_harness/config.py +255 -0
  19. rf_harness/installer/__init__.py +1 -0
  20. rf_harness/installer/_assets.py +73 -0
  21. rf_harness/installer/adapters/__init__.py +37 -0
  22. rf_harness/installer/adapters/_base.py +189 -0
  23. rf_harness/installer/adapters/claude_code.py +88 -0
  24. rf_harness/installer/adapters/codex.py +108 -0
  25. rf_harness/installer/adapters/copilot_cli.py +89 -0
  26. rf_harness/installer/adapters/opencode.py +62 -0
  27. rf_harness/installer/adapters/vscode.py +56 -0
  28. rf_harness/installer/cli.py +490 -0
  29. rf_harness/installer/codex_trust.py +190 -0
  30. rf_harness/installer/coexist.py +211 -0
  31. rf_harness/installer/demo_site/index.html +28 -0
  32. rf_harness/installer/doctor.py +343 -0
  33. rf_harness/installer/envfind.py +354 -0
  34. rf_harness/installer/executor.py +288 -0
  35. rf_harness/installer/homes.py +102 -0
  36. rf_harness/installer/manifest.py +204 -0
  37. rf_harness/installer/prompt.py +58 -0
  38. rf_harness/installer/transforms.py +483 -0
  39. rf_harness/runtime/__init__.py +1 -0
  40. rf_harness/runtime/analyzer.py +132 -0
  41. rf_harness/runtime/data/failure_rules.json +77 -0
  42. rf_harness/runtime/data/kwnames_cache.json +1 -0
  43. rf_harness/runtime/data/repl_format_rules.json +455 -0
  44. rf_harness/runtime/data/robocop-agent.toml +58 -0
  45. rf_harness/runtime/desktop/__init__.py +87 -0
  46. rf_harness/runtime/desktop/_env.py +134 -0
  47. rf_harness/runtime/desktop/decision.py +616 -0
  48. rf_harness/runtime/desktop/focus.py +1094 -0
  49. rf_harness/runtime/desktop/guidance.py +91 -0
  50. rf_harness/runtime/desktop/launch_env.py +562 -0
  51. rf_harness/runtime/desktop/safety.py +509 -0
  52. rf_harness/runtime/desktop/signals.py +321 -0
  53. rf_harness/runtime/docs.py +190 -0
  54. rf_harness/runtime/docsgen.py +174 -0
  55. rf_harness/runtime/engine.py +1000 -0
  56. rf_harness/runtime/execd.py +1131 -0
  57. rf_harness/runtime/guards.py +294 -0
  58. rf_harness/runtime/hints.py +1523 -0
  59. rf_harness/runtime/hook.py +1897 -0
  60. rf_harness/runtime/hookcore.py +832 -0
  61. rf_harness/runtime/kwdoc.py +285 -0
  62. rf_harness/runtime/kwdocs.py +286 -0
  63. rf_harness/runtime/leanfmt.py +451 -0
  64. rf_harness/runtime/lintd.py +423 -0
  65. rf_harness/runtime/livetmo.py +385 -0
  66. rf_harness/runtime/pagestate.py +489 -0
  67. rf_harness/runtime/portable.py +191 -0
  68. rf_harness/runtime/procs.py +106 -0
  69. rf_harness/runtime/profiles.py +144 -0
  70. rf_harness/runtime/proto.py +120 -0
  71. rf_harness/runtime/pwlog.py +89 -0
  72. rf_harness/runtime/replfmt.py +556 -0
  73. rf_harness/runtime/rfh_repl_listener.py +244 -0
  74. rf_harness/runtime/richfmt.py +301 -0
  75. rf_harness/runtime/robotcode_adapter.py +157 -0
  76. rf_harness/runtime/rthooks.py +623 -0
  77. rf_harness/runtime/smoke.py +113 -0
  78. robotframework_harness-1.0.0.dist-info/METADATA +85 -0
  79. robotframework_harness-1.0.0.dist-info/RECORD +82 -0
  80. robotframework_harness-1.0.0.dist-info/WHEEL +4 -0
  81. robotframework_harness-1.0.0.dist-info/entry_points.txt +3 -0
  82. robotframework_harness-1.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,84 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" SessionStart --agent auto",
9
+ "timeout": 30
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "UserPromptSubmit": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" UserPromptSubmit --agent auto",
20
+ "timeout": 10
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "PreToolUse": [
26
+ {
27
+ "matcher": "Write|Edit|MultiEdit|Bash|Read",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" PreToolUse --agent auto",
32
+ "timeout": 15
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "PostToolUse": [
38
+ {
39
+ "matcher": "Write|Edit|MultiEdit|Bash",
40
+ "hooks": [
41
+ {
42
+ "type": "command",
43
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" PostToolUse --agent auto",
44
+ "timeout": 180
45
+ }
46
+ ]
47
+ }
48
+ ],
49
+ "PostToolUseFailure": [
50
+ {
51
+ "matcher": "Bash",
52
+ "hooks": [
53
+ {
54
+ "type": "command",
55
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" PostToolUseFailure --agent auto",
56
+ "timeout": 180
57
+ }
58
+ ]
59
+ }
60
+ ],
61
+ "Stop": [
62
+ {
63
+ "hooks": [
64
+ {
65
+ "type": "command",
66
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" Stop --agent auto",
67
+ "timeout": 90
68
+ }
69
+ ]
70
+ }
71
+ ],
72
+ "SessionEnd": [
73
+ {
74
+ "hooks": [
75
+ {
76
+ "type": "command",
77
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" SessionEnd --agent auto",
78
+ "timeout": 5
79
+ }
80
+ ]
81
+ }
82
+ ]
83
+ }
84
+ }
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: rf-harness
3
+ description: Robot Framework test automation - write, debug and repair .robot/.resource tests (Browser/Playwright web UI incl. iframes, shadow DOM, custom widgets; SeleniumLibrary; RequestsLibrary APIs), explore unknown web pages, fix failing suites, find keywords, analyze output.xml. Hooks execute your test lines live after every edit and attach the results. Use for any Robot Framework task.
4
+ ---
5
+
6
+ # Robot Framework with live hooks
7
+
8
+ No helper command: you write files and run `robot`; hooks do the rest.
9
+ - **Every Write/Edit of a test file** runs its new/changed lines in a live RF session (page, sessions, variables kept)
10
+ and answers briefly: `L10-L16 PASS (7 lines)` + the page, or the failing line, the error, a `verdict:`, the nearest
11
+ controls and a `NEXT:` step (some sessions also get a controls list after each new page, the keyword's signature and
12
+ a `hint:` with every failure). A failing line stays open: fix it (or insert lines before it) and only that
13
+ part re-runs. Changing an executed line restarts the test (fresh browser context). `[Teardown]` runs only in the
14
+ plain run. Only edits that break the file's syntax/structure, delete tests or hide failures are refused; other
15
+ static findings come as a one-line warning and the live run decides.
16
+ - **Verdicts** (live element timeout 2.5 s; navigation/`Wait*` keep the normal one; never in your files):
17
+ `COVERED` (another element, e.g. a styled `<span>` over a radio, intercepts the click -> click its label / the element
18
+ on top; waits and force do not help), `NOT_VISIBLE`, `DISABLED`, `ANIMATING`, `AMBIGUOUS` (several matches),
19
+ `PRESENT` (element exists, not a timing issue), `MISSING` (no match: wrong selector or iframe), `IN_IFRAME`
20
+ (use the given `frame >>> sel`), `SLOW` (was absent, appeared late: the page keeps the normal timeout; a wait is only
21
+ suggested when close to the limit). Value checks show `actual ... vs expected ...`: fix data/steps, not timing.
22
+
23
+ ## Files the hooks write (Read / Grep them; never Read whole huge files)
24
+ - `.rf/state/latest/` after the last executed step of an edit and on failures: `page.txt` (url, title + the page's
25
+ VISIBLE TEXT), `controls.txt` (every control with label/id/name/data-test, hidden?, `=>` suggested selector, iframe
26
+ controls with `frame >>> `), `aria.yaml`, `source.html`, `screenshot.jpg`; history `.rf/state/step-NNN-<keyword>/`.
27
+ - `.rf/docs/<Library>.md`: one line per keyword (`grep -i "select option" .rf/docs/*.md`), `<Library>.full.md`: full
28
+ docs; reading `.rf/docs/kw/<Library>.<Keyword>.md` (path given with a failure) writes that keyword's full doc.
29
+
30
+ ## New test (web or API)
31
+ 1. Write the file with `*** Settings ***` (`Library Browser`, `Suite Setup New Browser chromium headless=True`)
32
+ and the first lines (`New Page ${URL}`); pick selectors from `.rf/state/latest/controls.txt`. In a user journey
33
+ reach pages by clicking like a user (no `Go To` deep links).
34
+ 2. Add a few lines per edit and read each result. Read values from the page instead of guessing.
35
+ 3. Final proof: `robot -d results tests/x.robot`. Failures are summarized and the first one reproduced live (if it
36
+ passes live it is a timing race: wait for the needed state before the failing step); a gate at the end of your turn
37
+ asks for this run if a test file changed since its last green plain run.
38
+
39
+ ## Repair an existing failing suite (references/debug.md)
40
+ 1. `robot -d results path/suite.robot` -> one line per failure + live reproduction stopped at the first failing line.
41
+ 2. Fix that line with your edit tool (state files show the page at the failure) -> it re-runs from there.
42
+ 3. Never delete/rename/skip tests or silence failures (refused) or weaken assertions; no `Sleep`. Finish with a green
43
+ plain run.
44
+ Results only: `robotcode results summary|show --failed -o results/output.xml` (references/results.md).
45
+
46
+ ## REPL / debugger (optional; a test file runs live too and keeps its page)
47
+ ```
48
+ robotcode repl --plain --no-history -d .rf/out 2>&1 <<'RF'
49
+ Library Browser
50
+ New Browser chromium headless=True
51
+ New Page file:///abs/path/page.html
52
+ ${t}= Get Title
53
+ .exit
54
+ RF
55
+ ```
56
+ Quoted heredoc, one statement per line, 4 spaces between cells, no `...` lines, `[ FAIL ]` on stderr, exit code
57
+ always 0, new session per call; `[rfh] COVERED/SLOW/MISSING` lines judge failures. `robotcode robot-debug --plain -d .rf/out
58
+ --break tests/x.robot:12 tests/x.robot 2>&1 <<'RF'` then `.where` / `.pp ${v}` / `.c` (bare `.break` args).
59
+
60
+ ## Rules
61
+ - PORTABLE: plain `robot <file>` must pass. Import project code by path (`Library ${CURDIR}/../lib/MyLib.py`,
62
+ `Resource ${CURDIR}/common.resource`); never -P/PYTHONPATH (warned); pip/playwright installs are refused.
63
+ - Separate keyword and arguments with 4 spaces. A cell starting with `#` is a comment: write `\#id`, `id=x`, `css=#x`.
64
+
65
+ ## Browser facts (verified, Browser 19.12 / 20.5)
66
+ - iframe: `id=frame >>> id=field` (no select-frame keyword). Open shadow DOM: css/id/text pierce it, xpath does not.
67
+ - Stable selectors first: `data-test=x`, `id=x`, `role=button[name="Save"]` (exact name), `text="Save"` (exact;
68
+ `text=Save` = substring). ARIA lines are not selectors. Strict mode: >1 match fails -> unique or `>> nth=0`.
69
+ - Values do not wait: `${t}= Get Text id=x` returns the current value; with an operator it retries only ~1 s.
70
+ Async UI: `Wait For Condition Text id=x contains Done timeout=15s` or
71
+ `Wait For Elements State id=spinner hidden timeout=15s`.
72
+ - Hidden styled radio/checkbox: click its label (`Click label[for="opt"]`), assert `Get Checkbox State id=opt == checked`.
73
+ Native `<select>`: `Select Options By id=x label Oslo`; custom dropdown: click it, then `role=option[name="Oslo"]`.
74
+ Cookie banner/overlay: close it first (force=True would click the overlay). Styled radio/checkbox covered by a
75
+ `<span>`: click the `label[for="id"]`, or `label:has(#id)` when the input sits inside its label (no `for=`);
76
+ never a positional `>> nth=` pick.
77
+
78
+ More: references/{browser,explore,waits,debug,requests,results,selenium}.md.
@@ -0,0 +1,157 @@
1
+ # Browser library (Playwright): locators, iframes, shadow DOM
2
+
3
+ BROWSER LIBRARY (Playwright) -- LOCATORS, IFRAMES, SHADOW DOM
4
+
5
+ VERIFIED CORE (checked live on Browser 19.12 and 20.5):
6
+ - Strategies: css (default, no prefix needed), id=, text=, xpath= (or start with // ), data-test=/data-testid=.
7
+ - IFRAMES: Browser has NO "select frame" keyword. Put the frame selector in front and pierce it with >>>
8
+ Fill Text id=editor-frame >>> id=title Draft
9
+ Click iframe[name="payment"] >>> text=Submit order
10
+ Several steps in one frame: Set Selector Prefix id=editor-frame >>> ... Set Selector Prefix ${EMPTY}
11
+ .rf/state/latest/aria.yaml includes iframe content (Browser < 20.5: one section per iframe headed with the frame
12
+ selector to prefix; >= 20.5: `Get Aria Snapshot css=body mode=ai` inlines it); controls.txt lists iframe
13
+ controls with the full `frame >>> selector`.
14
+ - SHADOW DOM: css=, text= and id= pierce OPEN shadow roots automatically -> write the inner selector as if
15
+ there were no shadow root: Click css=#shadow-btn / Get Text text=Shadow says hi
16
+ xpath does NOT pierce shadow roots; css:light= / text:light= deliberately do not pierce; closed roots unreachable.
17
+ - ROLE selectors work too: role=button[name="Sign in"] (exact accessible name; case-sensitive).
18
+ - STRICT MODE: a selector matching >1 element fails. Make it unique, or pick one: `css=.item >> nth=0` (zero-based).
19
+ - A cell STARTING with # is an RF comment: write `\#submit`, `css=#submit` or `id=submit`.
20
+ - Before acting on an unknown page: read .rf/state/latest/controls.txt / aria.yaml (written after each run that navigated or failed), then build a selector.
21
+ ARIA lines like `button "Sign in"` are NOT selectors -> use text="Sign in" / css=button >> text=Sign in.
22
+ - Element keywords auto-wait up to the browser timeout (default 10 s) for the ELEMENT; values do not
23
+ (getters with an operator retry only 1 s -> references/waits.md). The hook's probe refuses a step early (NOT RUN):
24
+ NOT_FOUND (nothing matched within the short live timeout; inside an iframe -> prints the `frame >>> selector` fix;
25
+ otherwise re-checked with the normal timeout -> SLOW or MISSING),
26
+ NOT_VISIBLE (hidden styled input -> prints the label to click), COVERED (overlay/cookie banner -> close it).
27
+ - Custom widgets, dropdowns, dates, dialogs, new tabs, cookie banners: references/explore.md.
28
+
29
+ --- browser locator reference ---
30
+ locator_strategies:
31
+ css: CSS selector (default strategy) - e.g., 'css=.class > #login_btn' or just '.class > #login_btn'
32
+ xpath: XPath expression - e.g., 'xpath=//input[@id="login_btn"]' or '//input[@id="login_btn"]'
33
+ text: Browser text engine (exact/partial/regex) - e.g., 'text=Login' or "Login"
34
+ id: Element ID attribute - e.g., 'id=login_btn'
35
+ css:light: CSS without shadow DOM piercing - e.g., 'css:light=article div'
36
+ text:light: Text without shadow DOM piercing - e.g., 'text:light=Login'
37
+ data-testid: data-testid attribute - e.g., 'data-testid=submit-button'
38
+ data-test-id: data-test-id attribute - e.g., 'data-test-id=submit-button'
39
+ data-test: data-test attribute - e.g., 'data-test=submit-button'
40
+ id:light: ID without shadow DOM piercing - e.g., 'id:light=login_btn'
41
+ selector_patterns:
42
+ explicit: strategy=value (e.g., 'css=.button', 'xpath=//button')
43
+ implicit_css: Plain selectors default to CSS (e.g., '.button' becomes 'css=.button')
44
+ implicit_xpath: Selectors starting with // or .. become XPath (e.g., '//button')
45
+ implicit_text: Quoted selectors become text (e.g., '"Login"' becomes 'text=Login')
46
+ cascaded: Multiple strategies with >> separator (e.g., 'text=Hello >> ../.. >> .select_button')
47
+ iframe_piercing: Frame piercing with >>> (e.g., 'id=iframe >>> id=btn')
48
+ element_reference: Element reference with element= (e.g., '${ref} >> .child')
49
+ common_examples:
50
+ CSS (default): .button-primary
51
+ CSS explicit: css=.button-primary
52
+ CSS with ID: \#submit-btn
53
+ XPath: //input[@type='submit']
54
+ XPath implicit: //button[contains(text(), 'Login')]
55
+ Text substring (case-insensitive): text=Login ; exact: text="Login"
56
+ Text implicit: "Login"
57
+ Text regex: text=/^Log(in|out)$/i
58
+ ID: id=submit-button
59
+ Cascaded: text=Hello >> ../.. >> .select_button
60
+ iFrame piercing: id=myframe >>> .inner-button
61
+ selector_format_rules:
62
+ Default strategy: CSS - plain selectors are treated as CSS
63
+ Explicit format: strategy=value (spaces around = are ignored)
64
+ XPath detection: Selectors starting with // or .. become XPath automatically
65
+ Text detection: Quoted selectors ("text" or 'text') become text selectors
66
+ Cascading: Use >> to chain selectors (css=div >> text=Login >> .button)
67
+ iFrame access: Use >>> to pierce iFrames (id=frame >>> id=element)
68
+ Element refs: Use element=${ref} >> .child for element references
69
+ strict_mode_info:
70
+ description: Browser Library uses strict mode by default
71
+ strict_true: Keyword fails if selector finds multiple elements
72
+ strict_false: Keyword succeeds even with multiple matches (uses first)
73
+ how_to_change: Use 'Set Strict Mode' keyword or library import parameter
74
+ shadow_dom_support:
75
+ automatic_piercing: CSS and text engines automatically pierce open shadow roots
76
+ light_engines: Use css:light= or text:light= to disable shadow DOM piercing
77
+ closed_shadow_roots: Closed shadow roots cannot be accessed
78
+ tips:
79
+ - Browser Library uses CSS selectors by default (no prefix needed)
80
+ - Use \# instead of # for ID selectors (Robot Framework escaping)
81
+ - XPath: Start with // or .. for automatic detection
82
+ - Text: Use quotes for exact text matching or regex patterns
83
+ - Cascaded selectors: Chain with >> for complex element paths
84
+ - iFrames: Use >>> to access elements inside frames
85
+ - Shadow DOM: CSS pierces automatically, use :light for light DOM only
86
+ - Strict mode: Controls behavior when multiple elements match
87
+ strict_mode_violation:
88
+ issue: Selector matches multiple elements but strict mode is enabled
89
+ solutions:
90
+ - Make selector more specific to match only one element
91
+ - Use 'Set Strict Mode False' to allow multiple matches
92
+ - Add more specific CSS selectors or attributes
93
+ - Use nth-child() or other CSS pseudo-selectors for specific elements
94
+ examples:
95
+ - Instead of '.button' use '.button.primary' or '.button:nth-child(1)'
96
+ - Instead of 'div' use 'div.container > div.content'
97
+ - Add unique attributes like '[data-testid="submit-btn"]'
98
+ element_not_found_suggestions:
99
+ - Verify element exists on current page
100
+ - Check if element loads asynchronously (use Wait For Elements State)
101
+ - Try different selector strategies (CSS, XPath, text, ID)
102
+ - Check if element is inside an iFrame (use >>> syntax)
103
+ - Verify element is not in closed shadow DOM
104
+ - Use browser developer tools to inspect element
105
+ - Check if element appears after user interaction
106
+ timeout_suggestions:
107
+ - Increase timeout with explicit waits
108
+ - Use 'Wait For Elements State' before interaction
109
+ - Check if element loads dynamically
110
+ - Verify selector syntax is correct
111
+ - Use 'Wait For Load State' to ensure page is ready
112
+ shadow_dom_guidance:
113
+ issue: Element may be in shadow DOM
114
+ solutions:
115
+ - Use regular CSS (automatic shadow piercing): 'css=.my-element'
116
+ - Use text selectors (automatic shadow piercing): 'text=Button Text'
117
+ - Avoid css:light= for shadow DOM elements
118
+ - Check if shadow root is closed (not accessible)
119
+ note: Browser Library automatically pierces open shadow roots with CSS and text engines
120
+ iframe_guidance:
121
+ issue: Element may be inside an iFrame
122
+ solutions:
123
+ - Use frame piercing syntax: 'id=myframe >>> .inner-element'
124
+ - There is no 'select frame' step in Browser: put the frame selector in front, `<frame> >>> <inner>`
125
+ - Use 'Set Selector Prefix' for multiple operations in same frame
126
+ examples:
127
+ - Click id=login-frame >>> input[name='username']
128
+ - Set Selector Prefix id=content-frame >>>
129
+ Click .submit-button (prefix MUST end with >>>; reset: Set Selector Prefix ${EMPTY})
130
+
131
+ --- common locator patterns ---
132
+
133
+ Browser library (Playwright-backed):
134
+ id=submit — by id attribute (equivalent to css=[id="submit"])
135
+ css=button.primary — CSS selector
136
+ text=Sign in — substring match, case-insensitive (use text="Sign in" for exact)
137
+ xpath=//input[@type='submit'] — XPath fallback
138
+ id=item >> nth=2 — Playwright cascaded nth filter (zero-based; also via intent_action nth=2)
139
+ // and .. — auto-routed to xpath (no prefix needed)
140
+ "Quoted string" — auto-routed to exact text= (no prefix needed)
141
+ For ARIA role + accessible name, use Browser's dedicated keywords
142
+ (Get Element By Role, Click With Options) — the role engine ALSO works as a selector string: role=button[name="Sign in"] (verified live).
143
+
144
+ SeleniumLibrary (= or : both work; : is the doc-preferred form):
145
+ id=submit — by id attribute
146
+ name=username — by name attribute
147
+ css=button.primary — CSS selector
148
+ xpath=//input[@type='submit'] — XPath
149
+ link=Click here — full link text
150
+ partial link=Click — partial link text (space inside the prefix)
151
+ css=tr:nth-of-type(3) — 1-based CSS nth
152
+
153
+ PICKING A LOCATOR
154
+ - Prefer id= when available (stable, fast).
155
+ - Fall back to css= or text= for elements without stable ids.
156
+ - Use xpath= only when CSS/text cannot express the predicate.
157
+ - When multiple elements match, disambiguate with `>> nth=N` (zero-based).
@@ -0,0 +1,39 @@
1
+ # Debugging an existing failing suite
2
+
3
+ RULES: fix the TEST code (locators, waits, imports, keyword usage, test data handling). Keep every test and the
4
+ intent of every assertion: never delete or skip tests, never loosen/remove assertions (== -> contains, Run Keyword
5
+ And Ignore Error, deleting a check). Change an expected literal only if the app clearly renamed it, and say so.
6
+
7
+ 1. Run it as is: `robot -d results path/suite.robot`. The hook attaches, from output.xml:
8
+ - one line per failed test: name, file:line, failing keyword, message;
9
+ - a LIVE REPRODUCTION of the first failing test: re-run from the top in the hook's session, stopped AT the failing
10
+ line with the page left open; page summary (title, url, controls with suggested selectors); full state in
11
+ .rf/state/latest/{controls.txt, aria.yaml, source.html, screenshot.jpg, page.txt} (history: .rf/state/step-NN-<keyword>/).
12
+ More detail without re-running: `robotcode results show --failed -o results/output.xml`,
13
+ `robotcode results log "*Test Name*" -o results/output.xml` (keyword tree with arguments).
14
+ 2. Classify by message: timeout / "waiting for locator" / NOT_FOUND -> locator (or element hidden/covered: NOT_VISIBLE,
15
+ COVERED with the fix); "should be/contain" with an intermediate value ("Loading...", 0, empty) -> missing wait;
16
+ "No keyword with name" -> import/typo; ModuleNotFoundError / "does not exist" on an import -> import path.
17
+ 3. Fix the failing line with your edit tool (or the .resource keyword it calls). The hook re-runs it right away, in the
18
+ page that is still open, and continues to the end of the test or the next failure. A failure inside a user keyword
19
+ is reported on the calling test line (`failed inside: <keyword>`): fix the keyword, the failed line re-runs.
20
+ 4. Editing a line of another failing test runs that test (from its setup). Changing a line that already passed
21
+ restarts that test from the top.
22
+ 5. A live re-run of a WAIT fix is weak evidence (the page has finished loading meanwhile): the plain run decides.
23
+ 6. Fix every failing test, then the proof: `robot -d results path/suite.robot` (fresh browser, all tests). After a
24
+ green run the hook records the suite as verified; at the end of your turn a gate asks for this run if a test file
25
+ changed since its last green plain run.
26
+
27
+ Typical fixes (Browser library):
28
+ - stale locator -> pick a stable one from .rf/state/latest/controls.txt: data-test=..., id=..., role=button[name="Save"],
29
+ text="Save"; avoid long CSS chains / nth-child / generated ids.
30
+ - missing wait -> a getter without operator does not wait, with operator it retries only 1 s:
31
+ Wait For Condition Text id=status contains Saved timeout=15s
32
+ Wait For Elements State id=spinner hidden timeout=15s (never Sleep; references/waits.md)
33
+ - element covered (cookie banner, modal): close it first; force=True would click the overlay instead.
34
+ - hidden styled radio/checkbox, custom dropdown, dialogs, new tabs, dates: references/explore.md
35
+ - element inside an iframe: `id=frame >>> id=field` (references/browser.md)
36
+ - PORTABLE: the suite must pass with plain `robot <file>`: Library ${CURDIR}/../lib/MyLib.py (by path, never
37
+ -P / PYTHONPATH / robot.toml).
38
+ API suites: the reproduction stops at the failing request; the message shows status/body. Log values with
39
+ `Log ${resp.json()}` lines while debugging (references/requests.md).
@@ -0,0 +1,52 @@
1
+ # Unknown pages and custom widgets
2
+
3
+ EXPLORING AN UNKNOWN LIVE PAGE + CUSTOM WIDGETS (verified on Browser 19.12 and 20.5)
4
+
5
+ Open it and LOOK before writing any selector: write the Settings + `New Page ${URL}` first; the hook runs it
6
+ and writes the page state (rewritten after every run that navigated or failed):
7
+ .rf/state/latest/controls.txt # every field/button/link: label, id, name, data-test, hidden?, native/custom, => selector
8
+ .rf/state/latest/aria.yaml # texts, roles, accessible names, one section per iframe Read messages from the page (Get Text) instead of guessing.
9
+
10
+ Selectors, most stable first: data-test=x / data-testid=x > id=x (not generated ones like id=r12, ember34) >
11
+ role=textbox[name="E-mail"] / role=button[name="Save"] (name = label / aria-label / text) > text="Save" >
12
+ short attribute CSS input[name="email"] > xpath. Avoid long CSS chains, nth-child, generated class names.
13
+ By label/placeholder: ${e}= Get Element By Label E-mail then Fill Text ${e} me@x.org
14
+
15
+ Cookie banner / overlay: close it first (Click text=Accept all). The hook's probe reports COVERED. A click that
16
+ fails with "intercepts pointer events" is covered too; Click With Options ... force=True does NOT help (the click
17
+ lands on the overlay, passes, and nothing happens).
18
+
19
+ Styled radio / checkbox (the real <input> is hidden, a label/span is drawn instead): Check Checkbox / Click on the
20
+ input waits for visibility and fails (the hook's probe: NOT_VISIBLE + the label to click). Click the visible label:
21
+ Click label[for="opt-yes"] Click label:has(#newsletter) Click text=Round trip
22
+ Assert on the input (works while hidden): Get Checkbox State id=opt-yes == checked
23
+
24
+ Dropdowns:
25
+ native <select> (aria: combobox WITH option children; controls: "native select"):
26
+ Select Options By id=country label Germany (or value de / index 2)
27
+ Get Selected Options id=country label == Germany (attribute BEFORE the operator)
28
+ custom dropdown (button/div, role=combobox/listbox; Select Options By fails "Element is not a <select> element"):
29
+ Click id=country-picker -> .rf/state/latest/aria.yaml -> Click role=option[name="Germany"] (or text=Germany)
30
+ Get Text id=country-picker == Germany
31
+
32
+ Dates (add `Library DateTime`): never hard-code a date that expires.
33
+ ${d}= Get Current Date increment=7 days result_format=%Y-%m-%d # today + 7
34
+ ${r}= Add Time To Date ${d} 14 days result_format=%m/%d/%Y # from another date
35
+ <input type=date>: Fill Text id=start ${d} with %Y-%m-%d (shown localized). Text fields: the format of
36
+ their placeholder/label (%d.%m.%Y, %m/%d/%Y ...). Calendar pop-ups: try Fill Text on the input first; else open it
37
+ and click the day (.rf/state/latest/aria.yaml).
38
+
39
+ JavaScript alert/confirm/prompt: without a handler they are DISMISSED (confirm() -> false, nothing happens).
40
+ Handle Future Dialogs action=accept prompt_input=My text # ONCE per page, BEFORE the click; stays active
41
+ ${p}= Promise To Wait For Alert action=accept text=Delete item? / Click ... / Wait For ${p}
42
+ One handler per page: a 2nd Handle Future Dialogs (Browser 19.x) or mixing it with Wait For Alert -> two handlers
43
+ -> the Playwright process crashes and every later test fails. Different answers on one page: one
44
+ `Promise To Wait For Alert` per dialog (accept/dismiss, prompt_input=...) and no Handle Future Dialogs there.
45
+ In-page modals (role=dialog) are normal elements.
46
+
47
+ New tab / popup: Click text=Help -> ${prev}= Switch Page NEW ... Switch Page ${prev}
48
+
49
+ Async UI: values do not wait; assertion operators retry only 1 s -> Wait For Condition Text id=status
50
+ contains Done timeout=15s / Wait For Elements State id=spinner hidden timeout=15s (references/waits.md).
51
+ Form ignores a filled value ("required" while text is shown): Fill Text fires `input` but not `change`; leave the
52
+ field: Press Keys id=email Tab
@@ -0,0 +1,37 @@
1
+ # RequestsLibrary: API testing
2
+
3
+ REQUESTSLIBRARY -- API TESTING
4
+
5
+ VERIFIED CORE:
6
+ - Create Session api http://127.0.0.1:8000 (alias + base URL)
7
+ - ${resp}= GET On Session api /users.json expected_status=200
8
+ - Status Should Be 200 ${resp}
9
+ - ${data}= Set Variable ${resp.json()} (outside Evaluate: ${resp.json()}; inside Evaluate: $resp.json())
10
+ - Should Be Equal As Strings ${resp.json()}[0][name] Ann
11
+ - ${resp}= POST On Session api /items json=${body} (json= for JSON bodies, NOT data=)
12
+ - ${resp}= GET On Session api /missing expected_status=404 (non-2xx without failing)
13
+ - `grep -i "on session" .rf/docs/RequestsLibrary.md` shows the real parameters (alias, url, data, json, expected_status, msg, **kwargs).
14
+
15
+ --- requests locator reference ---
16
+ tips:
17
+ - SESSION: Create Session with an alias + base URL, then use GET/POST/PUT/PATCH/DELETE On Session with that alias and a RELATIVE path. A bare Get/Post takes a full URL.
18
+ - RESPONSE: On-Session keywords RETURN the response — capture with ${resp}=. Read status via ${resp.status_code}, body via ${resp.json()}, a field via ${resp.json()["bookingid"]}.
19
+ - EVALUATE: Inside Evaluate use the BARE name ($resp, $token); OUTSIDE Evaluate use ${resp.json()}. Do NOT reach for Evaluate to compare status/fields — use the native assertion keywords.
20
+ - STATUS ASSERT: use 'Status Should Be 200 ${resp}' — NOT an Evaluate equality on ${resp.status_code}. This is the biggest Evaluate-call remover.
21
+ - BODY/HEADERS: Send a JSON body with json=<dict> — NOT data=. Set headers with headers=${headers}. A 400/415 usually means data= was used, the body was a string, or Content-Type: application/json is missing.
22
+ - BODY CONSTRUCTION: Build the JSON body dict, do NOT pass a repr-string. Easiest: inline Python eval — json=${{ {"firstname": "Jane", "totalprice": 111, "depositpaid": True, "bookingdates": {"checkin": "2024-01-01"}} }} (a real Python literal, so True/False/None and nested dicts work). Or define it first and pass it: assign ${body}= via Evaluate {"firstname": "Jane", ...} THEN reference json=${body} in the POST — never POST before ${body} exists. Avoid Set Variable/Create Dictionary gymnastics for JSON bodies.
23
+ - AUTH TOKEN: capture the token from ${resp.json()["token"]}, then send it as a cookie header: headers=${{"Cookie": "token=" + $token}} (or a &{headers} dict). Required for PUT/DELETE on restful-booker.
24
+ - NON-2XX: pass expected_status=404 (or expected_status=anything) so an error response is RETURNED instead of raising — needed to assert a deleted resource returns 404.
25
+ - NAMED ARGS: RequestsLibrary named args are positional name=value tokens (expected_status=200, json=${body}, headers=${headers}), not RF &{dict} kwargs.
26
+ warnings:
27
+ - ${resp.json()} works OUTSIDE Evaluate; inside Evaluate it is $resp.json() (bare) — mixing them is the #1 API time sink.
28
+ - Without expected_status=, a non-2xx response RAISES — you cannot then assert a 404; pass expected_status to capture error responses.
29
+ - PUT/DELETE that return 403/Forbidden usually mean the auth token cookie header was not sent.
30
+ examples:
31
+ - Create Session rb https://restful-booker.herokuapp.com # alias + base URL
32
+ - ${resp}= POST On Session rb /booking json=${{ {"firstname": "Jane", "lastname": "Doe", "totalprice": 111, "depositpaid": True, "bookingdates": {"checkin": "2024-01-01", "checkout": "2024-01-05"}} }} # build the JSON body inline with ${{ }} — no Set Variable/Create Dictionary
33
+ - ${resp}= POST On Session rb /auth json=${creds} # or define ${creds}= first, then reference it (never POST before it exists)
34
+ - Status Should Be 200 ${resp} # native status assertion (not Evaluate)
35
+ - ${token}= Set Variable ${resp.json()["token"]} # read a JSON field outside Evaluate
36
+ - ${resp}= GET On Session rb /booking/${id} expected_status=404 # assert a deleted resource → 404 without raising
37
+ - ${resp}= DELETE On Session rb /booking/${id} headers=${{"Cookie": "token=" + $token}} # auth token cookie header
@@ -0,0 +1,34 @@
1
+ # Running robot and reading output.xml
2
+
3
+ RESULTS / output.xml ANALYSIS
4
+
5
+ Run:
6
+ robot -d results tests/ # or: robotcode robot -d results tests/
7
+ robot -d results -t "Login*" -i smoke tests/ # select by test name / tag; --dryrun = syntax/keyword check only
8
+ Exit code = number of failed tests (0 = all passed). Files: results/output.xml, log.html, report.html.
9
+
10
+ Inspect a finished run WITHOUT re-running (robotcode results; -o selects the file, default from robot.toml):
11
+ robotcode results summary -o results/output.xml # counts + overall status
12
+ robotcode results show --failed -o results/output.xml # one line per failed test: name, path:line, message
13
+ robotcode results log "*Login*" -o results/output.xml # keyword tree of matching tests (args, messages)
14
+ robotcode results stats --by tag -o results/output.xml # aggregate by tag/suite/status
15
+ robotcode results diff old/output.xml new/output.xml # status changes between runs
16
+ robotcode --format json results summary -o results/output.xml # machine-readable
17
+ NOTE: `-o` belongs AFTER the subcommand (robotcode results show -o FILE).
18
+
19
+ Custom analysis with Robot Framework's API (no extra packages):
20
+ python - <<'PY'
21
+ from robot.api import ExecutionResult, ResultVisitor
22
+ res = ExecutionResult("results/output.xml")
23
+ s = res.statistics.total
24
+ print("passed", s.passed, "failed", s.failed, "skipped", s.skipped)
25
+ class Failed(ResultVisitor):
26
+ def visit_test(self, test):
27
+ if test.status == "FAIL":
28
+ print(test.full_name, "|", test.message.splitlines()[0] if test.message else "", "|", test.elapsed_time)
29
+ res.visit(Failed())
30
+ for t in res.suite.all_tests: # every test: name, status, tags, elapsed
31
+ print(t.name, t.status, list(t.tags), t.elapsed_time.total_seconds())
32
+ PY
33
+ Keyword level: walk test.body (kw.name, kw.status, kw.args, kw.message, kw.elapsed_time; nested kw.body).
34
+ Merge/re-report: rebot --merge -d merged a/output.xml b/output.xml ; rebot --output NONE --log log2.html output.xml
@@ -0,0 +1,40 @@
1
+ # SeleniumLibrary: locators and frames
2
+
3
+ SELENIUMLIBRARY -- LOCATORS AND FRAMES
4
+
5
+ VERIFIED CORE:
6
+ - Strategies: id:x / name:x / css:x / xpath:x / link:Text / partial link:Te (= works too: id=x).
7
+ A bare value matches id or name only; bare `//...` is xpath.
8
+ - IFRAMES: Select Frame id:frame-id ... Unselect Frame (no >>> in SeleniumLibrary).
9
+ - SHADOW DOM: not pierced by css/xpath; use Browser library, or Execute Javascript returning the element.
10
+ - Selenium is not strict: with several matches it silently uses the FIRST one.
11
+
12
+ --- selenium locator reference ---
13
+ locator_strategies:
14
+ id: Element id (e.g., 'id:example')
15
+ name: name attribute (e.g., 'name:example')
16
+ identifier: Either id or name (e.g., 'identifier:example')
17
+ class: Element class (e.g., 'class:example')
18
+ tag: Tag name (e.g., 'tag:div')
19
+ xpath: XPath expression (e.g., 'xpath://div[@id="example"]')
20
+ css: CSS selector (e.g., 'css:div#example')
21
+ dom: DOM expression (e.g., 'dom:document.images[5]')
22
+ link: Exact text a link has (e.g., 'link:The example')
23
+ partial link: Partial link text (e.g., 'partial link:he ex')
24
+ sizzle: Sizzle selector deprecated (e.g., 'sizzle:div.example')
25
+ data: Element data-* attribute (e.g., 'data:id:my_id')
26
+ jquery: jQuery expression (e.g., 'jquery:div.example')
27
+ default: Keyword specific default behavior (e.g., 'default:example')
28
+ common_examples:
29
+ By ID: id:my-button
30
+ By Name: name:firstname
31
+ By CSS: css:#submit-btn
32
+ By XPath: xpath://input[@type='submit']
33
+ By Class: class:button-primary
34
+ By Link Text: link:Click Here
35
+ tips:
36
+ - For form elements, 'name:fieldname' is often most reliable
37
+ - CSS selectors use 'css:' prefix, not just the selector
38
+ - XPath expressions must start with 'xpath:' prefix
39
+ - Use 'identifier:' to match either id or name attributes
40
+ - For buttons/links, try 'link:' for exact text matching
@@ -0,0 +1,28 @@
1
+ # Waits and timeouts
2
+
3
+ WAITS AND TIMEOUTS (verified on Browser 19.12 and 20.5)
4
+
5
+ Browser library:
6
+ - Element keywords (Click, Fill Text, Get Text ...) auto-wait for the ELEMENT (attached, visible, enabled) up to
7
+ the browser timeout (default 10 s; Set Browser Timeout 20s).
8
+ - VALUES do not wait: a getter WITHOUT an assertion operator returns the current value at once
9
+ ${t}= Get Text id=status -> "Loading..." ${n}= Get Element Count css=.row -> 0
10
+ - A getter WITH an assertion operator retries only `retry_assertions_for` = 1 s by default (not 10 s!):
11
+ Get Text id=status == Done Get Element Count css=.row >= 3
12
+ - Slower async UI (spinner, search results, toast) -> wait with the full timeout:
13
+ Wait For Condition Text id=status contains Done timeout=15s
14
+ Wait For Condition Element Count css=.row == 3
15
+ Wait For Elements State id=spinner hidden timeout=15s
16
+ Wait For Elements State css=.row >> nth=2 visible timeout=15s
17
+ or raise the retry for the suite/test: Set Retry Assertions For 10s (Library Browser retry_assertions_for=10s)
18
+ - After navigation: Get Url contains /home or Wait For Load State networkidle
19
+ - Do NOT add Sleep; use a condition/state wait instead.
20
+ SeleniumLibrary: Wait Until Element Is Visible id:x 10s ; Wait Until Page Contains text ;
21
+ Set Selenium Timeout 10s ; Set Selenium Implicit Wait (avoid mixing with explicit waits).
22
+ BuiltIn: Wait Until Keyword Succeeds 10s 1s <keyword> <args>
23
+ Hook runs (live session only -- never in your files or the plain robot run): element actions/getters use a SHORT
24
+ timeout (2.5 s; navigation and Wait* keywords keep the normal one). A not-found / not-visible / timeout failure is
25
+ re-checked ONCE with the normal timeout: "recheck: SLOW -- appeared after N s" -> the element is just slow: add
26
+ `Wait For Elements State <sel> visible timeout=10s` before the line (the hook already re-ran it);
27
+ "recheck: MISSING" -> wrong selector / iframe / hidden element (.rf/state/latest/controls.txt).
28
+ A line that runs longer than the hook budget is reported as "still running"; its result comes with your next edit.
@@ -0,0 +1,72 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" SessionStart --agent codex",
9
+ "timeout": 30
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "UserPromptSubmit": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" UserPromptSubmit --agent codex",
20
+ "timeout": 10
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "PreToolUse": [
26
+ {
27
+ "matcher": "apply_patch|Edit|Write|Bash",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" PreToolUse --agent codex",
32
+ "timeout": 15
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "PostToolUse": [
38
+ {
39
+ "matcher": "apply_patch|Edit|Write|Bash",
40
+ "hooks": [
41
+ {
42
+ "type": "command",
43
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" PostToolUse --agent codex",
44
+ "timeout": 180
45
+ }
46
+ ]
47
+ }
48
+ ],
49
+ "Stop": [
50
+ {
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" Stop --agent codex",
55
+ "timeout": 90
56
+ }
57
+ ]
58
+ }
59
+ ],
60
+ "SessionEnd": [
61
+ {
62
+ "hooks": [
63
+ {
64
+ "type": "command",
65
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/bin/rfh-launch\" SessionEnd --agent codex",
66
+ "timeout": 3
67
+ }
68
+ ]
69
+ }
70
+ ]
71
+ }
72
+ }