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.
- rf_harness/__init__.py +3 -0
- rf_harness/_assets/.claude-plugin/plugin.json +10 -0
- rf_harness/_assets/bin/rfh-launch +60 -0
- rf_harness/_assets/bin/rfh-launch.cmd +19 -0
- rf_harness/_assets/bin/rfh_launch.py +719 -0
- rf_harness/_assets/hooks/hooks.json +84 -0
- rf_harness/_assets/skills/rf-harness/SKILL.md +78 -0
- rf_harness/_assets/skills/rf-harness/references/browser.md +157 -0
- rf_harness/_assets/skills/rf-harness/references/debug.md +39 -0
- rf_harness/_assets/skills/rf-harness/references/explore.md +52 -0
- rf_harness/_assets/skills/rf-harness/references/requests.md +37 -0
- rf_harness/_assets/skills/rf-harness/references/results.md +34 -0
- rf_harness/_assets/skills/rf-harness/references/selenium.md +40 -0
- rf_harness/_assets/skills/rf-harness/references/waits.md +28 -0
- rf_harness/_assets/variants/codex/hooks.json +72 -0
- rf_harness/_assets/variants/copilot/hooks.json +53 -0
- rf_harness/_assets/variants/opencode/plugins/rf-harness.js +130 -0
- rf_harness/config.py +255 -0
- rf_harness/installer/__init__.py +1 -0
- rf_harness/installer/_assets.py +73 -0
- rf_harness/installer/adapters/__init__.py +37 -0
- rf_harness/installer/adapters/_base.py +189 -0
- rf_harness/installer/adapters/claude_code.py +88 -0
- rf_harness/installer/adapters/codex.py +108 -0
- rf_harness/installer/adapters/copilot_cli.py +89 -0
- rf_harness/installer/adapters/opencode.py +62 -0
- rf_harness/installer/adapters/vscode.py +56 -0
- rf_harness/installer/cli.py +490 -0
- rf_harness/installer/codex_trust.py +190 -0
- rf_harness/installer/coexist.py +211 -0
- rf_harness/installer/demo_site/index.html +28 -0
- rf_harness/installer/doctor.py +343 -0
- rf_harness/installer/envfind.py +354 -0
- rf_harness/installer/executor.py +288 -0
- rf_harness/installer/homes.py +102 -0
- rf_harness/installer/manifest.py +204 -0
- rf_harness/installer/prompt.py +58 -0
- rf_harness/installer/transforms.py +483 -0
- rf_harness/runtime/__init__.py +1 -0
- rf_harness/runtime/analyzer.py +132 -0
- rf_harness/runtime/data/failure_rules.json +77 -0
- rf_harness/runtime/data/kwnames_cache.json +1 -0
- rf_harness/runtime/data/repl_format_rules.json +455 -0
- rf_harness/runtime/data/robocop-agent.toml +58 -0
- rf_harness/runtime/desktop/__init__.py +87 -0
- rf_harness/runtime/desktop/_env.py +134 -0
- rf_harness/runtime/desktop/decision.py +616 -0
- rf_harness/runtime/desktop/focus.py +1094 -0
- rf_harness/runtime/desktop/guidance.py +91 -0
- rf_harness/runtime/desktop/launch_env.py +562 -0
- rf_harness/runtime/desktop/safety.py +509 -0
- rf_harness/runtime/desktop/signals.py +321 -0
- rf_harness/runtime/docs.py +190 -0
- rf_harness/runtime/docsgen.py +174 -0
- rf_harness/runtime/engine.py +1000 -0
- rf_harness/runtime/execd.py +1131 -0
- rf_harness/runtime/guards.py +294 -0
- rf_harness/runtime/hints.py +1523 -0
- rf_harness/runtime/hook.py +1897 -0
- rf_harness/runtime/hookcore.py +832 -0
- rf_harness/runtime/kwdoc.py +285 -0
- rf_harness/runtime/kwdocs.py +286 -0
- rf_harness/runtime/leanfmt.py +451 -0
- rf_harness/runtime/lintd.py +423 -0
- rf_harness/runtime/livetmo.py +385 -0
- rf_harness/runtime/pagestate.py +489 -0
- rf_harness/runtime/portable.py +191 -0
- rf_harness/runtime/procs.py +106 -0
- rf_harness/runtime/profiles.py +144 -0
- rf_harness/runtime/proto.py +120 -0
- rf_harness/runtime/pwlog.py +89 -0
- rf_harness/runtime/replfmt.py +556 -0
- rf_harness/runtime/rfh_repl_listener.py +244 -0
- rf_harness/runtime/richfmt.py +301 -0
- rf_harness/runtime/robotcode_adapter.py +157 -0
- rf_harness/runtime/rthooks.py +623 -0
- rf_harness/runtime/smoke.py +113 -0
- robotframework_harness-1.0.0.dist-info/METADATA +85 -0
- robotframework_harness-1.0.0.dist-info/RECORD +82 -0
- robotframework_harness-1.0.0.dist-info/WHEEL +4 -0
- robotframework_harness-1.0.0.dist-info/entry_points.txt +3 -0
- 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
|
+
}
|