Repository navigation
test-fixtures: consolidate canonical Swift + Rust fixtures into shared dir #1619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
a9b40ea
fix(cua-driver-rs/windows): click prefers ExpandCollapsePattern for m…
f-trycua e28e1d7
feat(test-fixtures): shared cua-driver gesture playground SPA + fixtu…
f-trycua e72cb6b
refactor(test-fixtures): consolidate canonical Swift fixtures into sh…
f-trycua File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # cua-driver-fixtures | ||
|
|
||
| Canonical HTML test fixtures for cua-driver (Swift / macOS) and | ||
| cua-driver-rs (Rust / Windows + Linux + macOS). One source of truth; both | ||
| ports' integration tests reference these files via relative symlinks from | ||
| their existing `Tests/integration/fixtures/` and `assets/` trees. | ||
|
|
||
| ## Files | ||
|
|
||
| | File | Purpose | | ||
| |------|---------| | ||
| | `interactive.html` | 31-line minimal click + type-mirror harness. Stable IDs: `#counter`, `#clicker`, `#textbox`, `#typed`. Used by `test_background_focus.py` (both ports) — opened via `file://`. | | ||
| | `form_all_inputs.html` | Every HTML5 input type (text, password, email, number, tel, textarea, select, checkbox, radio, range, date, color). Submit handler stores result in `window._submitted`; live read via `getFieldValues()`. Used by `test_drag_slider_delivery.py`, `test_hermes_form_fill*.py`. | | ||
| | `test_page.html` | Richer Safari/Chrome/Electron harness — button counter, text input, checkbox, dropdown, textarea, link, canvas. Served via local `html_server` HTTP fixture. Used by `test_chrome.py`, `test_safari.py`, `test_electron.py` (both ports). | | ||
| | `gesture_panels.html` | **New** — extends the same ID-convention style as `test_page.html` with panels for the four gestures not currently covered by the v2 harness: hotkey + modifier-state propagation, pixel-coord pinpoint accuracy, drag-and-drop event sequence, scroll position. See **Why** below. | | ||
|
|
||
| All four files are vanilla HTML — no external deps, no build step. Open | ||
| directly in any browser: | ||
|
|
||
| ```bash | ||
| open libs/cua-driver-fixtures/test_page.html | ||
| ``` | ||
|
|
||
| ## Where each port references the canonical copy | ||
|
|
||
| Existing port paths are preserved as symlinks. Tests that read these | ||
| paths (`os.path.join(_THIS_DIR, "fixtures", "interactive.html")`, | ||
| `f"{html_server}/test_page.html"`) work unchanged. | ||
|
|
||
| ``` | ||
| libs/cua-driver/Tests/integration/ | ||
| ├── fixtures/ | ||
| │ ├── interactive.html → ../../../../cua-driver-fixtures/interactive.html | ||
| │ └── form_all_inputs.html → ../../../../cua-driver-fixtures/form_all_inputs.html | ||
| └── assets/ | ||
| └── test_page.html → ../../../../cua-driver-fixtures/test_page.html | ||
|
|
||
| libs/cua-driver-rs/tests/integration/ | ||
| ├── fixtures/ | ||
| │ └── interactive.html → ../../../../cua-driver-fixtures/interactive.html | ||
| └── v2/assets/ | ||
| └── test_page.html → ../../../../../cua-driver-fixtures/test_page.html | ||
| ``` | ||
|
|
||
| Edits to a fixture now propagate to **both ports** in a single commit. | ||
| Drift is impossible by construction. | ||
|
|
||
| ## Why `gesture_panels.html` was added | ||
|
|
||
| `test_page.html` covers Safari/Chrome/Electron interaction surfaces well | ||
| but doesn't probe four gestures that the Windows cua-driver-rs stress | ||
| test (May 2026) showed need explicit harness coverage: | ||
|
|
||
| 1. **Hotkey + modifier-state propagation** (`#hotkey-focus` + `#hotkey-status`): | ||
| PostMessage-based hotkey paths inject keystrokes without updating the | ||
| OS modifier state, so the page-level `KeyboardEvent.ctrlKey` reads | ||
| `false` even after a routed `ctrl+s`. SendInput updates `GetKeyState`, | ||
| so the same event reads `ctrl=true`. The status div prints | ||
| `ctrl=true|false` directly — single assertion per routing path. | ||
|
|
||
| 2. **Pixel-coord pinpoint** (`#coord-area` + `#coord-status`): a known | ||
| target at viewport `(60, 60)` inside the box; status shows the | ||
| distance from the target in pixels. Asserting `dist < 2` is a clean | ||
| precision test for coordinate-routed clicks. | ||
|
|
||
| 3. **Drag-and-drop sequence** (`#drag-source` + `#drag-target` + | ||
| `#drag-status`): the page records each HTML5 drag event in order so | ||
| tests can assert the full `dragstart → dragover → drop` chain reached | ||
| the page (not just that the driver moved the cursor). | ||
|
|
||
| 4. **Scroll position** (`#scroll-area` + `#scroll-status`): a scrollable | ||
| container whose `scrollTop` is printed live — tests can assert scroll | ||
| direction + amount after a routed scroll gesture. | ||
|
|
||
| Like `test_page.html`, each panel exposes a global accessor: | ||
| `window.getGesturePanelState()` returns a JSON snapshot of all four | ||
| panels' state. Tests can poll it via `browser_eval` (when running with | ||
| Chromium's `--remote-debugging-port`) or read the visible status divs | ||
| via UIA / OCR. | ||
|
|
||
| ### Known browser-coverage gaps (May 2026) | ||
|
|
||
| When driving Chromium-based browsers (Edge / Chrome / Electron) on | ||
| Windows, the integration tests should be aware of: | ||
|
|
||
| - **GPU-composited content blanks PrintWindow.** `cua-driver screenshot` | ||
| captures the browser chrome + a white body. Workaround: launch the | ||
| target browser with `--remote-debugging-port=<port>` and use | ||
| `browser_eval` for both inputs and verification. | ||
| - **Chromium auto-disables full a11y** unless an AT-grade client is | ||
| detected. UIA on the rendered DOM returns empty until the page is | ||
| flagged accessibility-active. Workaround: enable Chromium's | ||
| `--force-renderer-accessibility` flag (or rely on `--remote-debugging-port`). | ||
| - **`hotkey` SendInput injection requires the daemon at UIAccess | ||
| integrity.** Otherwise `SetForegroundWindow` is rejected and the | ||
| injected events land on the wrong window — `SendInput inserted only 0 | ||
| of 4 events`. Workaround: run `cua-driver` via the | ||
| `cua-driver-uia.exe` worker (PR #1593) which carries the manifest. | ||
|
|
||
| These are environmental requirements for any DOM-level verification of | ||
| gesture behavior in a Chromium target on Windows; they don't affect the | ||
| canonical fixtures, only how they're driven. | ||
|
|
||
| ## Adding new fixtures | ||
|
|
||
| 1. Add the new HTML file directly to this dir | ||
| 2. Document it in the table above (purpose + stable IDs + who uses it) | ||
| 3. Add a relative symlink under whichever port(s) need it at the path | ||
| their tests already use | ||
| 4. Commit the new file + symlink(s) in the same PR | ||
|
|
||
| Do **not** create copies under each port's tree — symlink-only. | ||
|
|
||
| ## License | ||
|
|
||
| Same as the parent project (MIT, see `LICENSE.md`). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <title>All Inputs Test Form</title> | ||
| <style> | ||
| body { font-family: -apple-system, sans-serif; padding: 30px; background: #f0f0f0; max-width: 640px; } | ||
| h1 { font-size: 22px; margin-bottom: 20px; } | ||
| .field { margin-bottom: 16px; } | ||
| label { display: block; font-weight: bold; margin-bottom: 4px; font-size: 14px; } | ||
| input[type=text], input[type=password], input[type=email], | ||
| input[type=number], input[type=tel], input[type=url], input[type=search], | ||
| textarea, select { | ||
| width: 100%; padding: 8px; font-size: 16px; box-sizing: border-box; | ||
| border: 1px solid #bbb; border-radius: 4px; | ||
| } | ||
| textarea { height: 80px; resize: vertical; } | ||
| .radio-group label, .checkbox-group label { font-weight: normal; display: inline; margin-left: 6px; } | ||
| input[type=range] { width: 100%; } | ||
| input[type=color] { width: 80px; height: 40px; border: none; cursor: pointer; } | ||
| button#submit-btn { | ||
| background: #007AFF; color: white; border: none; padding: 12px 28px; | ||
| font-size: 16px; border-radius: 6px; cursor: pointer; margin-top: 10px; | ||
| } | ||
| button#submit-btn:hover { background: #0066DD; } | ||
| #result { | ||
| margin-top: 24px; padding: 16px; background: white; border: 1px solid #ccc; | ||
| border-radius: 6px; font-size: 13px; white-space: pre-wrap; display: none; | ||
| } | ||
| #submitted { display: none; font-size: 18px; color: green; font-weight: bold; margin-top: 16px; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>All Inputs Test Form</h1> | ||
| <form id="test-form" onsubmit="handleSubmit(event)" autocomplete="off"> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-text">Text</label> | ||
| <input type="text" id="f-text" name="text_field" placeholder="Enter text..."> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-password">Password</label> | ||
| <input type="password" id="f-password" name="password_field" placeholder="Enter password..."> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-email">Email</label> | ||
| <input type="email" id="f-email" name="email_field" placeholder="you@example.com"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-number">Number</label> | ||
| <input type="number" id="f-number" name="number_field" placeholder="42" min="0" max="999"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-tel">Phone</label> | ||
| <input type="tel" id="f-tel" name="tel_field" placeholder="555-1234"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-textarea">Message (Textarea)</label> | ||
| <textarea id="f-textarea" name="textarea_field" placeholder="Type a message..."></textarea> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-select">Favourite colour (Select)</label> | ||
| <select id="f-select" name="select_field"> | ||
| <option value="">-- pick one --</option> | ||
| <option value="red">Red</option> | ||
| <option value="green">Green</option> | ||
| <option value="blue">Blue</option> | ||
| <option value="yellow">Yellow</option> | ||
| </select> | ||
| </div> | ||
|
|
||
| <div class="field checkbox-group"> | ||
| <label>Checkbox</label><br> | ||
| <input type="checkbox" id="f-checkbox" name="checkbox_field" value="checked"> | ||
| <label for="f-checkbox">I agree to the terms</label> | ||
| </div> | ||
|
|
||
| <div class="field radio-group"> | ||
| <label>Radio buttons — Preferred fruit</label><br> | ||
| <input type="radio" id="r-apple" name="radio_field" value="apple"> | ||
| <label for="r-apple">Apple</label> | ||
| <input type="radio" id="r-banana" name="radio_field" value="banana"> | ||
| <label for="r-banana">Banana</label> | ||
| <input type="radio" id="r-cherry" name="radio_field" value="cherry"> | ||
| <label for="r-cherry">Cherry</label> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-range">Range / Slider (0–100)</label> | ||
| <input type="range" id="f-range" name="range_field" min="0" max="100" value="50"> | ||
| <span id="range-display">50</span> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-date">Date</label> | ||
| <input type="date" id="f-date" name="date_field"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <label for="f-color">Colour picker</label><br> | ||
| <input type="color" id="f-color" name="color_field" value="#4488ff"> | ||
| </div> | ||
|
|
||
| <button type="submit" id="submit-btn">Submit Form</button> | ||
| </form> | ||
|
|
||
| <div id="submitted">✓ Form Submitted!</div> | ||
| <div id="result"></div> | ||
|
|
||
| <script> | ||
| document.getElementById('f-range').addEventListener('input', function() { | ||
| document.getElementById('range-display').textContent = this.value; | ||
| }); | ||
|
|
||
| function handleSubmit(e) { | ||
| e.preventDefault(); | ||
| var form = document.getElementById('test-form'); | ||
| var data = { | ||
| text: document.getElementById('f-text').value, | ||
| password: document.getElementById('f-password').value, | ||
| email: document.getElementById('f-email').value, | ||
| number: document.getElementById('f-number').value, | ||
| tel: document.getElementById('f-tel').value, | ||
| textarea: document.getElementById('f-textarea').value, | ||
| select: document.getElementById('f-select').value, | ||
| checkbox: document.getElementById('f-checkbox').checked, | ||
| radio: (document.querySelector('input[name=radio_field]:checked') || {}).value || '', | ||
| range: document.getElementById('f-range').value, | ||
| date: document.getElementById('f-date').value, | ||
| color: document.getElementById('f-color').value, | ||
| }; | ||
| document.getElementById('submitted').style.display = 'block'; | ||
| document.getElementById('result').style.display = 'block'; | ||
| document.getElementById('result').textContent = JSON.stringify(data, null, 2); | ||
| window._submitted = data; | ||
| } | ||
|
|
||
| // Expose live field state for test assertions (no submit required) | ||
| function getFieldValues() { | ||
| return JSON.stringify({ | ||
| text: document.getElementById('f-text').value, | ||
| password: document.getElementById('f-password').value, | ||
| email: document.getElementById('f-email').value, | ||
| number: document.getElementById('f-number').value, | ||
| tel: document.getElementById('f-tel').value, | ||
| textarea: document.getElementById('f-textarea').value, | ||
| select: document.getElementById('f-select').value, | ||
| checkbox: document.getElementById('f-checkbox').checked, | ||
| radio: (document.querySelector('input[name=radio_field]:checked') || {}).value || '', | ||
| range: document.getElementById('f-range').value, | ||
| date: document.getElementById('f-date').value, | ||
| color: document.getElementById('f-color').value, | ||
| submitted: typeof window._submitted !== 'undefined', | ||
| }); | ||
| } | ||
| </script> | ||
| </body> | ||
| </html> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a language tag to the fenced tree block.
The code fence starting at Line 30 is missing a language identifier (
MD040), which will keep markdown lint noisy.Suggested fix
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 30-30: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents