Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions libs/cua-driver-fixtures/README.md
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
```
Comment on lines +30 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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
-```
+```text
 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
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-fixtures/README.md` around lines 30 - 43, The fenced code
block in libs/cua-driver-fixtures/README.md (the tree diagram showing
libs/cua-driver/Tests/integration and libs/cua-driver-rs/tests/integration) is
missing a language tag; update the opening fence from ``` to ```text so the
block is recognized as plain text (addressing MD040) and keep the block content
unchanged.


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`).
164 changes: 164 additions & 0 deletions libs/cua-driver-fixtures/form_all_inputs.html
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>&nbsp;&nbsp;
<input type="radio" id="r-banana" name="radio_field" value="banana">
<label for="r-banana">Banana</label>&nbsp;&nbsp;
<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">&#10003; 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>
Loading
Loading