Skip to content

Commit 36fb9ae

Browse files
committed
feat: restructure motions into motions/ and update README
1 parent afca7ca commit 36fb9ae

29 files changed

Lines changed: 723 additions & 713 deletions

README.md

Lines changed: 146 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,104 @@
1+
# pointerdriver
2+
13
[![test][test-badge]][test]
24

3-
# pointerdriver
5+
Simulate human maneuvers across devices on gesture-heavy web apps.
6+
For usage as an automated testing utility.
7+
8+
Abstracts the idiosyncrasies across many different pointer
9+
devices; e.g: "Apple Pencil", "touchscreen", "mouse" etc...
10+
behind an ergonomic and uniform API.
11+
12+
> [!NOTE]
13+
> LLM agents/assistants **must** read the [skill][skill-md].
14+
15+
## Usage
416

5-
Synthesize pointer, touch, and gesture events on any page.
17+
Start the local server, then import in your target app's
18+
DevTools/Web Inspector/Console:
619

720
```bash
821
npx github:TheProfs/pointerdriver
922
```
1023

11-
> [!NOTE]
12-
> LLM Agents **must** read the `pointerdriver` skill first.
13-
> Ask your LLM to run: `pointerdriver --skill` and study it carefully.
24+
```js
25+
const { DragMotion } = await import('http://127.0.0.1:5619/pointerdriver.js')
26+
27+
await new DragMotion(document.querySelector('#el'), [
28+
[30, 50, 0],
29+
[60, 80, 16],
30+
]).perform()
31+
```
32+
33+
### Programmatic usage
1434

15-
## How it works:
35+
You can also `import` and use it to drive automated tests:
1636

17-
1. Run your project however you want.
18-
We're assuming it's running on `localhost:3000`
19-
2. Run a pointerdriver server with `npx github:TheProfs/pointerdriver`
20-
3. Go in your browser console and run:
37+
```bash
38+
npm i github:TheProfs/pointerdriver
39+
```
2140

2241
```js
23-
const {
24-
DragMotion, GlideMotion, StrokeMotion,
25-
PinchMotion, TwistMotion, SwipeMotion
26-
} = await import('http://127.0.0.1:5619/pointerdriver.js')
42+
import { test } from 'node:test'
43+
import puppeteer from 'puppeteer'
44+
45+
const browser = await puppeteer.launch({ headless: false })
46+
const page = await browser.newPage()
47+
await page.goto('http://localhost:3000')
48+
49+
test('#mousedrag', async t => {
50+
t.beforeEach(() => page.evaluate(async () => {
51+
const { DragMotion } = await import(
52+
'http://127.0.0.1:5619/pointerdriver.js'
53+
)
54+
55+
await new DragMotion(document.querySelector('#el'), [
56+
[30, 50, 0],
57+
[60, 80, 16],
58+
]).perform()
59+
}))
60+
61+
await t.test('creates a PathItem', async t => {
62+
// assertions...
63+
})
64+
65+
await t.test('closely following the cursor path', async t => {
66+
// assertions...
67+
})
68+
69+
await t.test('with selected attributes', async t => {
70+
// assertions...
71+
})
72+
})
2773
```
2874

29-
This loads `pointerdriver` and allows executing any of the following motions:
30-
3175
## Motions
3276

33-
Create a motion by running: `new Motion(arguments).perform()`.
34-
35-
A motion is a *human-maneuver*;
36-
Regardless of the arguments you use for it, a motion eventually
37-
generates the exact sequence of events that would be generated
38-
from a real device.
77+
A `Motion` is a unit that represents a particular maneuver.
78+
For example:
79+
80+
- `"swipe across an element using 1 finger, following this <path>"`
81+
- `"put 2 fingers down and twist <amount> of degrees and lift up"`
3982

83+
The `Motion` is then executed on the passed `element`,
84+
which starts dispatching appropriate events at appropriate timings.
4085

41-
### `DragMotion(el, points)`
86+
Run any motion with `new Motion(args).perform()`.
4287

43-
Mouse drag across a surface.
88+
There are 2 types of Motions; both explained below:
4489

45-
| Param | Description |
46-
|----------|--------------------------------------------|
47-
| `el` | target element |
48-
| `points` | `[x, y, ms][]`; `ms` monotonic and `>= 0` |
90+
### Drawing
91+
92+
Motions used for drawing on a surface.
93+
94+
- `DragMotion` — mouse
95+
- `GlideMotion` — touch
96+
- `StrokeMotion` — pen
97+
98+
All three take `(el, points)`:
99+
100+
- `points` is `[[x, y, ms], ...]`
101+
- `ms` is monotonic and `>= 0`
49102

50103
```js
51104
await new DragMotion(document.querySelector('#el'), [
@@ -54,104 +107,117 @@ await new DragMotion(document.querySelector('#el'), [
54107
]).perform()
55108
```
56109

57-
### `GlideMotion(el, points)`
58-
59-
Finger draw on a surface.
60-
61-
| Param | Description |
62-
|----------|--------------------------------------------|
63-
| `el` | target element |
64-
| `points` | `[x, y, ms][]`; `ms` monotonic and `>= 0` |
65-
66110
```js
67111
await new GlideMotion(document.querySelector('#el'), [
68112
[30, 50, 0],
69113
[60, 80, 16],
70114
]).perform()
71115
```
72116

73-
### `StrokeMotion(el, points)`
74-
75-
Pen stylus stroke on a surface.
76-
77-
| Param | Description |
78-
|----------|--------------------------------------------|
79-
| `el` | target element |
80-
| `points` | `[x, y, ms][]`; `ms` monotonic and `>= 0` |
81-
82117
```js
83118
await new StrokeMotion(document.querySelector('#el'), [
84119
[30, 50, 0],
85120
[60, 80, 16],
86121
]).perform()
87122
```
88123

89-
### `PinchMotion(el, scale, { x, y, distance, steps })`
124+
### Gestures
125+
126+
Motions used for interaction (zooming, panning, etc).
127+
128+
#### `PinchMotion(el, scale, { x, y, distance, steps })`
90129

91130
Two-finger pinch together or apart.
92131

93-
| Param | Default | Description |
94-
|------------|---------|--------------------------|
95-
| `el` | | target element |
96-
| `scale` | | target scale factor |
97-
| `x`, `y` | | gesture center |
98-
| `distance` | `100` | initial finger gap in px |
99-
| `steps` | `20` | interpolation frames |
132+
- `scale` — target scale factor
133+
- `x`, `y` — gesture center
134+
- `distance` — initial finger gap in px (default `100`)
135+
- `steps` — interpolation frames (default `20`)
100136

101137
```js
102138
await new PinchMotion(document.querySelector('#el'), 2, {
103139
x: 60, y: 80
104140
}).perform()
105141
```
106142

107-
### `TwistMotion(el, degrees, { x, y, radius, steps })`
143+
#### `TwistMotion(el, degrees, { x, y, radius, steps })`
108144

109145
Two-finger rotation around a center point.
110146

111-
| Param | Default | Description |
112-
|-----------|---------|-----------------------------|
113-
| `el` | | target element |
114-
| `degrees` | `45` | rotation angle |
115-
| `x`, `y` | | gesture center |
116-
| `radius` | `80` | finger distance from center |
117-
| `steps` | `20` | interpolation frames |
147+
- `degrees` — rotation angle (default `45`)
148+
- `x`, `y` — gesture center
149+
- `radius` — finger distance from center (default `80`)
150+
- `steps` — interpolation frames (default `20`)
118151

119152
```js
120153
await new TwistMotion(document.querySelector('#el'), 45, {
121154
x: 60, y: 80
122155
}).perform()
123156
```
124157

125-
### `SwipeMotion(el, distance, { x, y, angle, separation, steps })`
158+
#### `SwipeMotion(el, distance, { x, y, angle, separation, steps })`
126159

127160
Two-finger parallel drag.
128161

129-
| Param | Default | Description |
130-
|--------------|---------|---------------------------|
131-
| `el` | | target element |
132-
| `distance` | | travel distance in px |
133-
| `x`, `y` | | gesture center |
134-
| `angle` | `0` | direction in degrees |
135-
| `separation` | `40` | gap between fingers in px |
136-
| `steps` | `20` | interpolation frames |
162+
- `distance` — travel distance in px
163+
- `x`, `y` — gesture center
164+
- `angle` — direction in degrees (default `0`)
165+
- `separation` — gap between fingers in px (default `40`)
166+
- `steps` — interpolation frames (default `20`)
137167

138168
```js
139169
await new SwipeMotion(document.querySelector('#el'), 200, {
140170
x: 60, y: 80
141171
}).perform()
142172
```
143173

144-
## Notes:
174+
## Add a motion
175+
176+
Motions are extensible, allowing support for more
177+
devices and interaction types.
178+
179+
```
180+
Motion (base)
181+
├── PathMotion (drawing)
182+
│ ├── DragMotion
183+
│ ├── GlideMotion
184+
│ └── StrokeMotion
185+
└── GestureMotion (gestures)
186+
├── PinchMotion
187+
├── TwistMotion
188+
└── SwipeMotion
189+
```
190+
191+
1. Create `motions/<name>/index.js`
192+
2. Extend `PathMotion` or `GestureMotion`
193+
3. Export from `motions/index.js`
194+
195+
## Local server
196+
197+
For local development:
198+
199+
```bash
200+
npx github:TheProfs/pointerdriver
201+
```
202+
203+
then in your target app Web Console:
145204

146-
- The module server only serves `/pointerdriver.js` and `/src/*/index.js`.
147-
- It sets permissive CORS headers so you can import it from any page.
205+
```js
206+
const { DragMotion } = await import('http://127.0.0.1:5619/pointerdriver.js')
207+
208+
// execute motion ...
209+
```
148210

149-
## HTTPS pages
211+
### Use HTTPS tunnels for https local dev
150212

151-
If the target page is HTTPS,
152-
importing pointerdriver from HTTP is blocked as mixed content.
153-
Serve pointerdriver over HTTPS (see `bin/skill.md` for one approach).
213+
If the target page is HTTPS, the HTTP import is blocked as mixed content.
214+
Use a Cloudflare Tunnel to wrap it in HTTPS:
215+
216+
```bash
217+
cloudflared tunnel --url http://127.0.0.1:5619
218+
```
154219

220+
Install via [Cloudflare Tunnel downloads][cftunnel].
155221

156222
## Run tests
157223

@@ -166,3 +232,5 @@ npm test
166232
[test-badge]: https://github.com/TheProfs/pointerdriver/actions/workflows/test.yml/badge.svg
167233
[test]: https://github.com/TheProfs/pointerdriver/actions/workflows/test.yml
168234
[license]: https://opensource.org/licenses/MIT
235+
[skill-md]: bin/skill.md
236+
[cftunnel]: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/

0 commit comments

Comments
 (0)