@keepsnappy/appium-driver 0.1.0-alpha.16 → 0.1.0-alpha.18
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.
- package/README.md +3 -3
- package/dist/src/browserstack.d.ts +6 -1
- package/dist/src/browserstack.js +7 -1
- package/dist/src/browserstack.js.map +1 -1
- package/dist/src/gestures.d.ts +1 -22
- package/dist/src/gestures.js +1 -100
- package/dist/src/gestures.js.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/lists.d.ts +5 -33
- package/dist/src/lists.js +26 -96
- package/dist/src/lists.js.map +1 -1
- package/dist/src/run.d.ts +5 -1
- package/dist/src/run.js +18 -7
- package/dist/src/run.js.map +1 -1
- package/dist/src/scroll.d.ts +120 -0
- package/dist/src/scroll.js +330 -0
- package/dist/src/scroll.js.map +1 -0
- package/dist/src/session.d.ts +31 -0
- package/dist/src/session.js +31 -2
- package/dist/src/session.js.map +1 -1
- package/dist/src/webdriver.d.ts +23 -1
- package/dist/src/webdriver.js +46 -1
- package/dist/src/webdriver.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @keepsnappy/appium-driver
|
|
2
2
|
|
|
3
|
-
KeepSnappy's built-in Appium driver for `keepsnappy farm run --driver appium --journey-module <file>` (BrowserStack App Automate, iOS and Android; `--platform ios|android`, default ios). A thin step vocabulary over W3C WebDriver: you write the journey, the driver owns the session, run tokens, step markers, the readiness wait (`expectNext`) after a touch and the result files. Design: `docs/plans/54b-appium-driver.md`.
|
|
3
|
+
KeepSnappy's built-in Appium driver for `keepsnappy farm run --driver appium --journey-module <file>` (BrowserStack App Automate, iOS and Android; `--platform ios|android`, default ios). A thin step vocabulary over W3C WebDriver: you write the journey, the driver owns the session, run tokens, step markers, the readiness wait (`expectNext`) after a touch and the result files. Design: `docs/plans/archive/54b-appium-driver.md`.
|
|
4
4
|
|
|
5
5
|
A journey module default-exports `defineJourney({ app, home, options?, setup?, journeys })`; see the plan for the full shape. A journey needs no import from this package: selectors are plain objects (`{ id }`, `{ text }`, `{ ...sel, childOf }`, or a bare string) and everything else is on the `t` it is given (`t.acceptAlert`, `t.openLink`, ...). `defineJourney` is only an identity helper for editor types. The CLI runs its own bundled copy, so do not `instanceof` driver errors (`WdError`) from your own copy; test `e.wdError`.
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ How to write journeys that run fast on Android, with measured costs: `docs/writi
|
|
|
18
18
|
|
|
19
19
|
## GC before the action (`--action-gc on|off`, default off)
|
|
20
20
|
|
|
21
|
-
With `--action-gc on` the driver asks the app for a full garbage collection before every declared step, after the step's readiness wait and before its start marker, and once more before the run's flush. Design: `docs/plans/gc-before-action.md`; device checks: `docs/plans/gc-before-action-measurement.md`.
|
|
21
|
+
With `--action-gc on` the driver asks the app for a full garbage collection before every declared step, after the step's readiness wait and before its start marker, and once more before the run's flush. Design: `docs/plans/archive/gc-before-action.md`; device checks: `docs/plans/archive/gc-before-action-measurement.md`.
|
|
22
22
|
|
|
23
23
|
- Each command has a number `n`, unique in the session. Android sends it as a broadcast (`mobile: broadcast`, action `<bundleId>.keepsnappy.COMMAND`, extras `cmd=gc`, `n`) and reads the answer `gc:<n> ...` from the broadcast's result. iOS opens `<scheme>devtools/keepsnappy?cmd=gc&n=<n>` and waits up to 10 s for the element `keepsnappy.ack` whose label starts with `gc:<n> `.
|
|
24
24
|
- The step's start marker carries `gc_n`, so the backend (derivation 18) pairs the step with its GC record without mapping clocks.
|
|
@@ -63,7 +63,7 @@ await scrollCycles(t, {
|
|
|
63
63
|
});
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`
|
|
66
|
+
`scrollList(t, { list, row, field, screen, above }, toward, { cache, hold })` is the one way the driver scrolls a list; every helper below sends its gesture through it. With `mustMove` (a to-top pass, the first fling of an end pass) the gesture is checked: one the device did not take (Android: no view-scrolled event; iOS: the held row's frame unchanged) is confirmed by the row ids inside the list's frame, sent once more and counted in the driver log's `defects`; a second one throws `GestureNotTaken`. `longScroll` is its list gesture on a given rectangle: Android native flings at 12000 dp/s (density cached per driver) or iOS 150 ms swipes. `flingUntilShown` stops when any part of the target shows: three gestures maximum, five toward the top on iOS. `scrollUntilShown` exposes the same capped look/move loop for custom checks. `waitCoasted` waits for two matching row frames 300 ms apart (up to 3000 ms); `holdBeforeScroll` captures a row before an iOS gesture so coast checks can reuse its handle.
|
|
67
67
|
|
|
68
68
|
`scrollToEnd` performs three long scrolls. Each page marker starts a 3000 ms load wait after coast stops (6000 ms Android, 10000 ms iOS); scrolls without a page marker pause 500 ms. Supply `beforePages` to permit exactly one additional fling when the loaded end was missed. `scrollCycles` repeats three end/top pairs, waiting for page markers only in cycle one and checking `lastPage` loaded in every cycle. `pageNumbers` preserves numeric page labels in command summaries; otherwise summaries use selector descriptions. Page sizes and seed data stay in the journey's page selectors. Options expose the counts and timeouts; their defaults are exported constants. `frameTimeoutMs` (20000) bounds the wait for the list frame to stop moving, `checkTimeoutMs` (20000) bounds a step check (`passChecked`, `endChecked`; in `scrollCycles` the top check reads `fling.checkTimeoutMs`), and `lookWindowMs` (1000) is the window in which a look after a gesture is retried. The `lookMs` field in the log stays the cumulative time spent looking.
|
|
69
69
|
|
|
@@ -14,8 +14,13 @@ export interface CapsInput {
|
|
|
14
14
|
localIdentifier?: string | undefined;
|
|
15
15
|
/** Default iOS. */
|
|
16
16
|
platform?: Platform;
|
|
17
|
+
/** Video and device logs for a debugging run; off by default because recording and log streaming load the measured phone. */
|
|
18
|
+
capture?: boolean;
|
|
17
19
|
}
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* `local` is on only when a tunnel exists: without one BrowserStack waits for a Local binary that never comes.
|
|
22
|
+
* Network logs stay off even with `capture`: they route the app's traffic through BrowserStack's proxy.
|
|
23
|
+
*/
|
|
19
24
|
export declare function sessionCaps(i: CapsInput): Record<string, unknown>;
|
|
20
25
|
export declare function sessionDetail(sessionId: string, auth: string, fetchImpl: FetchLike): Promise<{
|
|
21
26
|
build_hashed_id?: string;
|
package/dist/src/browserstack.js
CHANGED
|
@@ -29,7 +29,10 @@ export async function resolveApp(app, customId, auth, fetchImpl, platform = IOS)
|
|
|
29
29
|
throw new Error(`app upload failed: HTTP ${res.status} ${JSON.stringify(body)}`);
|
|
30
30
|
return body.app_url;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* `local` is on only when a tunnel exists: without one BrowserStack waits for a Local binary that never comes.
|
|
34
|
+
* Network logs stay off even with `capture`: they route the app's traffic through BrowserStack's proxy.
|
|
35
|
+
*/
|
|
33
36
|
export function sessionCaps(i) {
|
|
34
37
|
const tunnel = i.localIdentifier ? { local: true, localIdentifier: i.localIdentifier } : {};
|
|
35
38
|
return {
|
|
@@ -42,6 +45,9 @@ export function sessionCaps(i) {
|
|
|
42
45
|
...tunnel,
|
|
43
46
|
idleTimeout: 300,
|
|
44
47
|
appiumVersion: i.appiumVersion,
|
|
48
|
+
video: i.capture ?? false,
|
|
49
|
+
deviceLogs: i.capture ?? false,
|
|
50
|
+
networkLogs: false,
|
|
45
51
|
},
|
|
46
52
|
};
|
|
47
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browserstack.js","sourceRoot":"","sources":["../../src/browserstack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,GAAG,EAAiB,MAAM,eAAe,CAAC;AAGnD,qGAAqG;AACrG,MAAM,CAAC,MAAM,GAAG,GAAG,2CAA2C,CAAC;AAC/D,MAAM,CAAC,MAAM,MAAM,GAAG,iDAAiD,CAAC;AAExE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,GAAW,EAAU,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AAE5H,kHAAkH;AAClH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,QAAgB,EAAE,IAAY,EAAE,SAAoB,EAAE,WAAqB,GAAG;IAC1H,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,GAAG,CAAC;IACxE,MAAM,OAAO,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,MAAM,gBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAClG,MAAM,IAAI,GAAY,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,CAAC,CAAsC,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QACrG,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACtB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,UAAU,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,IAAI,QAAQ;QAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACjD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,MAAM,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACzF,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAyB,CAAC;IAC1E,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpG,OAAO,IAAI,CAAC,OAAO,CAAC;AACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"browserstack.js","sourceRoot":"","sources":["../../src/browserstack.ts"],"names":[],"mappings":"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"}
|
package/dist/src/gestures.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Session } from './session.js';
|
|
2
|
-
import {
|
|
3
|
-
import type { Rect } from './webdriver.js';
|
|
2
|
+
import type { SelectorLike } from './selectors.js';
|
|
4
3
|
export type ScrollDirection = 'end' | 'top';
|
|
5
4
|
export interface Point {
|
|
6
5
|
x: number;
|
|
@@ -35,16 +34,6 @@ export declare const BURST_GAP_MS = 45;
|
|
|
35
34
|
export declare const ROW_TOP_SWIPE_MS = 400;
|
|
36
35
|
export declare const ROW_TOP_SCREEN_FRACTION = 0.98;
|
|
37
36
|
export declare const ROW_END_SCREEN_FRACTION = 0.1;
|
|
38
|
-
/**
|
|
39
|
-
* One long scroll inside window rectangle `r`, after readiness. Android sends a native fling at 12000 dp/s over
|
|
40
|
-
* 30–85% of the list; iOS drags 85→10% to the end or 30→95% to the top in 150 ms. Logs WebDriver commands only.
|
|
41
|
-
*/
|
|
42
|
-
export declare function longScroll(t: Session, r: Rect, toward: ScrollDirection): Promise<void>;
|
|
43
|
-
/**
|
|
44
|
-
* Android inverted-list scroll extending from `list` to the window at 3000 dp/s. Top: 10% of the list to 95% of the
|
|
45
|
-
* window, direction up; end: 90% to 10%, direction down. Reads the list frame after readiness; logs commands only.
|
|
46
|
-
*/
|
|
47
|
-
export declare function androidScreenScroll(t: Session, list: SelectorLike, toward: ScrollDirection): Promise<void>;
|
|
48
37
|
/**
|
|
49
38
|
* Closes a sheet between window points. Android uses native dragGesture; iOS uses a 400 ms drag. Logs commands only.
|
|
50
39
|
* Does not consume readiness itself; use closeSwipeAt/From when readiness is pending.
|
|
@@ -63,13 +52,3 @@ export declare function tapAllAt(t: Session, points: readonly Point[], { holdMs,
|
|
|
63
52
|
holdMs?: number;
|
|
64
53
|
gapMs?: number;
|
|
65
54
|
}): Promise<void>;
|
|
66
|
-
/**
|
|
67
|
-
* Scrolls an inverted list using drawn `row` elements. Android delegates to androidScreenScroll. iOS top starts on the
|
|
68
|
-
* lowest row whose visible attribute is 'true' and that ends above `above` (or the lowest drawn row when none does or no
|
|
69
|
-
* `above` is given), chosen by frame, not tree order, dragging to 98% of the screen in 400 ms. End selects the bottommost visible
|
|
70
|
-
* row, clamps its centre inside `list` when found, and drags to 10% in 150 ms. Fails without a drawn row using missingMessage (default 'no drawn row to scroll from'). Logs commands only.
|
|
71
|
-
*/
|
|
72
|
-
export declare function scrollFromDrawnRow(t: Session, list: SelectorLike, row: SelectorLike, toward: ScrollDirection, { missingMessage, above }?: {
|
|
73
|
-
missingMessage?: string;
|
|
74
|
-
above?: SelectorLike;
|
|
75
|
-
}): Promise<void>;
|
package/dist/src/gestures.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { toSelector } from './selectors.js';
|
|
2
1
|
/** Default scroll budget; the third gesture is logged as extra. */
|
|
3
2
|
export const LONG_SCROLL_CAP = 3;
|
|
4
3
|
export const LONG_SCROLL_PLANNED = 2;
|
|
@@ -16,60 +15,13 @@ export const BURST_GAP_MS = 45;
|
|
|
16
15
|
export const ROW_TOP_SWIPE_MS = 400;
|
|
17
16
|
export const ROW_TOP_SCREEN_FRACTION = 0.98;
|
|
18
17
|
export const ROW_END_SCREEN_FRACTION = 0.1;
|
|
19
|
-
const densities = new WeakMap();
|
|
20
|
-
/** Reads pixels per dp once per driver; logs the native density command and rejects invalid dpi. */
|
|
21
|
-
async function density(t) {
|
|
22
|
-
if (!densities.has(t.driver)) {
|
|
23
|
-
const dpi = Number(await t.driver.execute('mobile: getDisplayDensity'));
|
|
24
|
-
if (!(dpi > 0))
|
|
25
|
-
throw new Error(`mobile: getDisplayDensity answered ${dpi}, not a density`);
|
|
26
|
-
densities.set(t.driver, dpi / 160);
|
|
27
|
-
}
|
|
28
|
-
return densities.get(t.driver);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* One long scroll inside window rectangle `r`, after readiness. Android sends a native fling at 12000 dp/s over
|
|
32
|
-
* 30–85% of the list; iOS drags 85→10% to the end or 30→95% to the top in 150 ms. Logs WebDriver commands only.
|
|
33
|
-
*/
|
|
34
|
-
export async function longScroll(t, r, toward) {
|
|
35
|
-
await t.ready();
|
|
36
|
-
if (t.driver.platform.name === 'android') {
|
|
37
|
-
const { from, to, dpPerSecond } = LONG_SCROLL.android;
|
|
38
|
-
const top = Math.round(r.y + r.height * from);
|
|
39
|
-
await t.driver.execute('mobile: flingGesture', {
|
|
40
|
-
left: r.x, top, width: r.width, height: Math.round(r.y + r.height * to) - top,
|
|
41
|
-
direction: toward === 'end' ? 'down' : 'up', speed: Math.round(dpPerSecond * (await density(t))),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
const [from, to] = LONG_SCROLL.ios[toward];
|
|
46
|
-
const x = Math.round(r.x + r.width / 2);
|
|
47
|
-
await t.driver.drag({ x, y: Math.round(r.y + r.height * from) }, { x, y: Math.round(r.y + r.height * to) }, LONG_SCROLL.ios.ms);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Android inverted-list scroll extending from `list` to the window at 3000 dp/s. Top: 10% of the list to 95% of the
|
|
52
|
-
* window, direction up; end: 90% to 10%, direction down. Reads the list frame after readiness; logs commands only.
|
|
53
|
-
*/
|
|
54
|
-
export async function androidScreenScroll(t, list, toward) {
|
|
55
|
-
await t.ready();
|
|
56
|
-
const r = (await t.frameOf(list, await t.waitVisible(list))).rect;
|
|
57
|
-
const w = await t.driver.screen();
|
|
58
|
-
const [inList, onScreen] = LONG_SCROLL.androidScreen[toward];
|
|
59
|
-
const start = Math.round(r.y + r.height * inList);
|
|
60
|
-
const end = Math.round(w.height * onScreen);
|
|
61
|
-
await t.driver.execute('mobile: flingGesture', {
|
|
62
|
-
left: r.x, top: Math.min(start, end), width: r.width, height: Math.abs(end - start),
|
|
63
|
-
direction: toward === 'top' ? 'up' : 'down', speed: Math.round(LONG_SCROLL.androidScreen.dpPerSecond * (await density(t))),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
18
|
/**
|
|
67
19
|
* Closes a sheet between window points. Android uses native dragGesture; iOS uses a 400 ms drag. Logs commands only.
|
|
68
20
|
* Does not consume readiness itself; use closeSwipeAt/From when readiness is pending.
|
|
69
21
|
*/
|
|
70
22
|
export async function closeSwipe(t, from, to) {
|
|
71
23
|
if (t.driver.platform.name === 'android') {
|
|
72
|
-
await t.driver.execute('mobile: dragGesture', { startX: from.x, startY: from.y, endX: to.x, endY: to.y });
|
|
24
|
+
await t.driver.execute('mobile: dragGesture', { startX: from.x, startY: from.y, endX: to.x, endY: Math.min(to.y, await t.driver.safeBottomY()) });
|
|
73
25
|
}
|
|
74
26
|
else {
|
|
75
27
|
await t.driver.drag(from, to, CLOSE_SWIPE_MS);
|
|
@@ -103,55 +55,4 @@ export async function tapAllAt(t, points, { holdMs = TAP_HOLD_MS, gapMs = BURST_
|
|
|
103
55
|
]) }] });
|
|
104
56
|
await t.driver.cmd('DELETE', '/actions').catch(() => null);
|
|
105
57
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Scrolls an inverted list using drawn `row` elements. Android delegates to androidScreenScroll. iOS top starts on the
|
|
108
|
-
* lowest row whose visible attribute is 'true' and that ends above `above` (or the lowest drawn row when none does or no
|
|
109
|
-
* `above` is given), chosen by frame, not tree order, dragging to 98% of the screen in 400 ms. End selects the bottommost visible
|
|
110
|
-
* row, clamps its centre inside `list` when found, and drags to 10% in 150 ms. Fails without a drawn row using missingMessage (default 'no drawn row to scroll from'). Logs commands only.
|
|
111
|
-
*/
|
|
112
|
-
export async function scrollFromDrawnRow(t, list, row, toward, { missingMessage = 'no drawn row to scroll from', above } = {}) {
|
|
113
|
-
if (t.driver.platform.name !== 'ios') {
|
|
114
|
-
await androidScreenScroll(t, list, toward);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
await t.ready();
|
|
118
|
-
const { using, value } = t.driver.platform.locators(toSelector(row))[0];
|
|
119
|
-
let start = null;
|
|
120
|
-
if (toward === 'top') {
|
|
121
|
-
// iOS lists rows outside the visible area too, and a drawn row in the list's faded top zone passes touches through:
|
|
122
|
-
// start on the lowest drawn row that ends above `above` (or the lowest drawn row), chosen by frame, never by tree order.
|
|
123
|
-
const limit = above ? (await t.driver.cmd('GET', `/element/${await t.waitVisible(toSelector(above))}/rect`)).y : Infinity;
|
|
124
|
-
let lowest = null;
|
|
125
|
-
for (const el of await t.driver.findAll(using, value)) {
|
|
126
|
-
if ((await t.driver.cmd('GET', `/element/${el}/attribute/visible`)) !== 'true')
|
|
127
|
-
continue;
|
|
128
|
-
const r = await t.driver.cmd('GET', `/element/${el}/rect`);
|
|
129
|
-
if (!lowest || r.y > lowest.y)
|
|
130
|
-
lowest = r;
|
|
131
|
-
if (r.y + r.height <= limit && (!start || r.y > start.y))
|
|
132
|
-
start = r;
|
|
133
|
-
}
|
|
134
|
-
start ??= lowest;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
for (const el of await t.driver.findAll(using, `${value} AND visible == 1`)) {
|
|
138
|
-
const r = await t.driver.cmd('GET', `/element/${el}/rect`);
|
|
139
|
-
if (!start || r.y > start.y)
|
|
140
|
-
start = r;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (!start)
|
|
144
|
-
throw new Error(missingMessage);
|
|
145
|
-
const w = await t.driver.screen();
|
|
146
|
-
const x = Math.round(start.x + start.width / 2);
|
|
147
|
-
let y = Math.round(start.y + start.height / 2);
|
|
148
|
-
if (toward === 'end') {
|
|
149
|
-
const listEl = await t.driver.findShown(toSelector(list));
|
|
150
|
-
if (listEl) {
|
|
151
|
-
const l = await t.driver.cmd('GET', `/element/${listEl}/rect`);
|
|
152
|
-
y = Math.min(Math.max(y, l.y + 1), l.y + l.height - 1);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
await t.driver.drag({ x, y }, { x, y: Math.round(w.height * (toward === 'top' ? ROW_TOP_SCREEN_FRACTION : ROW_END_SCREEN_FRACTION)) }, toward === 'top' ? ROW_TOP_SWIPE_MS : LONG_SCROLL.ios.ms);
|
|
156
|
-
}
|
|
157
58
|
//# sourceMappingURL=gestures.js.map
|
package/dist/src/gestures.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gestures.js","sourceRoot":"","sources":["../../src/gestures.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gestures.js","sourceRoot":"","sources":["../../src/gestures.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ export { END_GC_DEFAULT, RUN_DEFAULTS, acceptAlert, activationLink, flushLink, g
|
|
|
3
3
|
export { defineJourney, type JourneyContext, type JourneyFn, type JourneyModule } from './journey.js';
|
|
4
4
|
export { ANDROID, IOS, PLATFORMS, platformOf, type Platform, type PlatformName } from './platform.js';
|
|
5
5
|
export { androidLocators, childOf, describeSelector, id, idText, locator, text, toSelector, type Selector, type SelectorLike } from './selectors.js';
|
|
6
|
-
export { FLUSH_END_RUN_FAILED, FLUSH_ERROR, FLUSH_OFF, FLUSH_NOT_ACKNOWLEDGED, FLUSH_RESEND_MS, FLUSH_UNREADABLE, FLUSH_WAIT_MS, type FlushEvidence, HOST_CLOCK_TRIPS, Session, sendMarkers, type ActionGc, type ClockTrip, type Condition, type Marker } from './session.js';
|
|
6
|
+
export { FLUSH_END_RUN_FAILED, FLUSH_ERROR, FLUSH_OFF, FLUSH_NOT_ACKNOWLEDGED, FLUSH_RESEND_MS, FLUSH_UNREADABLE, FLUSH_WAIT_MS, type FlushEvidence, HOST_CLOCK_TRIPS, Session, sendMarkers, type ActionGc, type GestureDefect, type ClockTrip, type Condition, type Marker } from './session.js';
|
|
7
7
|
export { loadElements, parseTokensJs, plan, type PlannedRun } from './tokens.js';
|
|
8
8
|
export { runDriver, DRIVER_OPTIONS, type IterationRecord, type RunDeps } from './run.js';
|
|
9
9
|
export { loadJourneyModule, main } from './load.js';
|
|
10
10
|
export { DRIVER_NAME, driverVersion, journeyVersion } from './version.js';
|
|
11
|
-
export {
|
|
12
|
-
export { rowAlternatives, rowAtOrBelow, scrollUntilShown,
|
|
11
|
+
export { closeSwipe, closeSwipeAt, closeSwipeFrom, tapAllAt, LONG_SCROLL, LONG_SCROLL_CAP, LONG_SCROLL_PLANNED, IOS_TOP_EXTRA, CLOSE_SWIPE_MS, TAP_HOLD_MS, BURST_GAP_MS, ROW_TOP_SWIPE_MS, ROW_TOP_SCREEN_FRACTION, ROW_END_SCREEN_FRACTION, type ScrollDirection, type Point } from './gestures.js';
|
|
12
|
+
export { rowAlternatives, rowAtOrBelow, scrollUntilShown, scrollCap, flingUntilShown, passChecked, scrollToEnd, endChecked, scrollCycles, scrollToTarget, END_SCROLLS, SCROLL_CYCLES, PAGE_PAUSE_MS, PAGE_WAIT_MS, PAGE_POLL_MS, PAGE_COAST_MAX_MS, PAGE_COAST_MAX_MS_IOS, LOOK_MS, LOOK_POLL_MS, CHECK_TIMEOUT_MS, TARGET_SETTLE_MS, type UntilShownOptions, type FlingUntilShownOptions, type ScrollToEndOptions, type EndCheckOptions, type PassCheckOptions, type ScrollCyclesOptions, type ScrollToTargetOptions } from './lists.js';
|
|
13
|
+
export { scrollList, GestureNotTaken, longScroll, androidScreenScroll, scrollFromDrawnRow, waitCoasted, holdBeforeScroll, FLING_UNMOVED_MS, COAST_POLL_MS, COAST_MAX_MS, type ScrollSpec, type ScrollOptions, type ScrollResult, type RowField, type ListHandle, type RowHold, type ListCache } from './scroll.js';
|
package/dist/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { loadElements, parseTokensJs, plan } from './tokens.js';
|
|
|
8
8
|
export { runDriver, DRIVER_OPTIONS } from './run.js';
|
|
9
9
|
export { loadJourneyModule, main } from './load.js';
|
|
10
10
|
export { DRIVER_NAME, driverVersion, journeyVersion } from './version.js';
|
|
11
|
-
export {
|
|
12
|
-
export { rowAlternatives, rowAtOrBelow, scrollUntilShown,
|
|
11
|
+
export { closeSwipe, closeSwipeAt, closeSwipeFrom, tapAllAt, LONG_SCROLL, LONG_SCROLL_CAP, LONG_SCROLL_PLANNED, IOS_TOP_EXTRA, CLOSE_SWIPE_MS, TAP_HOLD_MS, BURST_GAP_MS, ROW_TOP_SWIPE_MS, ROW_TOP_SCREEN_FRACTION, ROW_END_SCREEN_FRACTION } from './gestures.js';
|
|
12
|
+
export { rowAlternatives, rowAtOrBelow, scrollUntilShown, scrollCap, flingUntilShown, passChecked, scrollToEnd, endChecked, scrollCycles, scrollToTarget, END_SCROLLS, SCROLL_CYCLES, PAGE_PAUSE_MS, PAGE_WAIT_MS, PAGE_POLL_MS, PAGE_COAST_MAX_MS, PAGE_COAST_MAX_MS_IOS, LOOK_MS, LOOK_POLL_MS, CHECK_TIMEOUT_MS, TARGET_SETTLE_MS } from './lists.js';
|
|
13
|
+
export { scrollList, GestureNotTaken, longScroll, androidScreenScroll, scrollFromDrawnRow, waitCoasted, holdBeforeScroll, FLING_UNMOVED_MS, COAST_POLL_MS, COAST_MAX_MS } from './scroll.js';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAmD,MAAM,gBAAgB,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAC5J,OAAO,EAAE,aAAa,EAA2D,MAAM,cAAc,CAAC;AACtG,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAoC,MAAM,eAAe,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAoC,MAAM,gBAAgB,CAAC;AACrJ,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,SAAS,EAAE,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAsB,gBAAgB,EAAE,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAmD,MAAM,gBAAgB,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAC5J,OAAO,EAAE,aAAa,EAA2D,MAAM,cAAc,CAAC;AACtG,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAoC,MAAM,eAAe,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAoC,MAAM,gBAAgB,CAAC;AACrJ,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,SAAS,EAAE,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAsB,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAkF,MAAM,cAAc,CAAC;AAClS,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,IAAI,EAAmB,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAsC,MAAM,UAAU,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,uBAAuB,EAAoC,MAAM,eAAe,CAAC;AACtS,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAmL,MAAM,YAAY,CAAC;AAC1gB,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAwH,MAAM,aAAa,CAAC"}
|
package/dist/src/lists.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Session } from './session.js';
|
|
2
2
|
import { type SelectorLike } from './selectors.js';
|
|
3
|
-
import type { Rect } from './webdriver.js';
|
|
4
3
|
import { type ScrollDirection } from './gestures.js';
|
|
4
|
+
import { type ListCache, type RowField } from './scroll.js';
|
|
5
5
|
/** Default fixed end-pass gesture count. */
|
|
6
6
|
export declare const END_SCROLLS = 3;
|
|
7
7
|
/** Default end/top cycle count. */
|
|
@@ -15,32 +15,12 @@ export declare const PAGE_POLL_MS = 300;
|
|
|
15
15
|
export declare const PAGE_COAST_MAX_MS = 6000;
|
|
16
16
|
/** iOS page-loading coast bound. */
|
|
17
17
|
export declare const PAGE_COAST_MAX_MS_IOS = 10000;
|
|
18
|
-
export declare const COAST_POLL_MS = 300;
|
|
19
|
-
/** Default non-page coast bound; a timeout returns false rather than failing. */
|
|
20
|
-
export declare const COAST_MAX_MS = 3000;
|
|
21
18
|
export declare const LOOK_MS = 1000;
|
|
22
19
|
export declare const LOOK_POLL_MS = 100;
|
|
23
20
|
/** Default settled-frame and pass-check timeout. */
|
|
24
21
|
export declare const CHECK_TIMEOUT_MS = 20000;
|
|
25
22
|
/** Initial visibility wait before trying an indicator or fallback scroll. */
|
|
26
23
|
export declare const TARGET_SETTLE_MS = 10000;
|
|
27
|
-
export type RowField = 'text' | 'description';
|
|
28
|
-
export interface ListHandle {
|
|
29
|
-
sel: SelectorLike;
|
|
30
|
-
el?: string;
|
|
31
|
-
}
|
|
32
|
-
export interface RowHold {
|
|
33
|
-
el?: string | null;
|
|
34
|
-
}
|
|
35
|
-
/** Mutable frame/row cache for one visit to a list; share it with pass checks to share the gesture budget. */
|
|
36
|
-
export interface ListCache {
|
|
37
|
-
at?: {
|
|
38
|
-
el: string;
|
|
39
|
-
rect: Rect;
|
|
40
|
-
};
|
|
41
|
-
hold?: RowHold;
|
|
42
|
-
used?: number;
|
|
43
|
-
}
|
|
44
24
|
export interface UntilShownOptions {
|
|
45
25
|
cmd: string;
|
|
46
26
|
field: string;
|
|
@@ -119,23 +99,13 @@ export interface ScrollToTargetOptions {
|
|
|
119
99
|
* Logs cmd with the gesture count under field, blind/cap, looks/lookMs, status, duration, optional extra and planned overrun.
|
|
120
100
|
*/
|
|
121
101
|
export declare function scrollUntilShown(t: Session, { cmd, field, cap, blind, planned, settle, look: once, move, missing, extra, lookWindowMs, lookPollMs }: UntilShownOptions): Promise<number>;
|
|
122
|
-
/**
|
|
123
|
-
* Captures a drawn row before a scroll when hold is provided and empty; lookup failures clear the hold. Use for iOS
|
|
124
|
-
* where visible-row queries during momentum can consume the coast budget. Logs lookup commands only.
|
|
125
|
-
*/
|
|
126
|
-
export declare function holdBeforeScroll(t: Session, row: SelectorLike, list?: ListHandle, field?: RowField, hold?: RowHold): Promise<void>;
|
|
127
|
-
/**
|
|
128
|
-
* Waits for two matching row frames 300 ms apart, up to maxMs (3000), always attempting at least two reads.
|
|
129
|
-
* Optional list scopes the row and is re-found at most twice on missing/stale reads; field selects an Android text
|
|
130
|
-
* attribute. An optional hold reuses one row across calls until stale. Returns false when still moving. Logs commands only.
|
|
131
|
-
*/
|
|
132
|
-
export declare function waitCoasted(t: Session, row: SelectorLike, list?: ListHandle, field?: RowField, maxMs?: number, hold?: RowHold): Promise<boolean>;
|
|
133
102
|
/** Platform gesture budget: cap (3), plus iosTopExtra (2) only when returning to the top on iOS. No commands or logs. */
|
|
134
103
|
export declare const scrollCap: (t: Session, toward: ScrollDirection, cap?: number, iosTopExtra?: number) => number;
|
|
135
104
|
/**
|
|
136
105
|
* Long-scrolls list toward a partly visible target; scoped defaults true. Refetches missing/stale list handles before
|
|
137
106
|
* moving and rereads the frame before every gesture. A shared cache stores the settled frame, held iOS row and moves used.
|
|
138
107
|
* Defaults: 3 gestures (5 to top on iOS), 2 planned, 20 s frameTimeoutMs, 3 s coast. used subtracts the prior pass budget.
|
|
108
|
+
* A to-top gesture is checked (scrollList `mustMove`); its retry is a logged defect and does not use the budget.
|
|
139
109
|
* Logs fling-to-end/top with flings, blind/cap, looks/lookMs, extra, relists, settled, status and duration, including failures.
|
|
140
110
|
*/
|
|
141
111
|
export declare function flingUntilShown(t: Session, list: SelectorLike, target: SelectorLike, toward: ScrollDirection, { scoped, blind, cache, row, used, cap, planned, iosTopExtra, frameTimeoutMs, coastMaxMs, lookWindowMs, lookPollMs }?: FlingUntilShownOptions): Promise<void>;
|
|
@@ -149,7 +119,9 @@ export declare function passChecked(t: Session, list: SelectorLike, target: Sele
|
|
|
149
119
|
* Performs scrolls (3) end gestures. A gesture with no page marker pauses pauseMs (500). Otherwise it waits for coast
|
|
150
120
|
* (Android 6000 ms, iOS 10000), optionally adds one gesture if beforePages was not reached, then waits pageWaitMs (3000)
|
|
151
121
|
* for the page's first loaded row, polling every pagePollMs (300). scoped defaults true; field narrows Android lookups.
|
|
152
|
-
*
|
|
122
|
+
* The pass is taken to start at the top, so its first gesture is checked (scrollList `mustMove`): one that moved nothing is
|
|
123
|
+
* sent once more and counted in `t.defects`; `unmoved` is 1 then, and 2 when the retry moved nothing either (GestureNotTaken).
|
|
124
|
+
* Logs fling-to-end: flings, supplied pageNumbers (or descriptions), coastMs, waitMs, per-page extra, unmoved, pauseMs, settled,
|
|
153
125
|
* status and duration. Page checks do not require visibility. All limits and frameTimeoutMs (20000) are configurable.
|
|
154
126
|
*/
|
|
155
127
|
export declare function scrollToEnd(t: Session, list: SelectorLike, cache: ListCache, { pages, pageNumbers, beforePages, scoped, field, any, scrolls, pauseMs, pageWaitMs, pagePollMs, coastMaxMs, coastMaxMsIos, frameTimeoutMs }: ScrollToEndOptions): Promise<void>;
|
package/dist/src/lists.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describeSelector, toSelector } from './selectors.js';
|
|
2
|
-
import {
|
|
2
|
+
import { LONG_SCROLL_CAP, LONG_SCROLL_PLANNED, IOS_TOP_EXTRA } from './gestures.js';
|
|
3
|
+
import { COAST_MAX_MS, GestureNotTaken, scrollList, waitCoasted } from './scroll.js';
|
|
3
4
|
/** Default fixed end-pass gesture count. */
|
|
4
5
|
export const END_SCROLLS = 3;
|
|
5
6
|
/** Default end/top cycle count. */
|
|
@@ -13,9 +14,6 @@ export const PAGE_POLL_MS = 300;
|
|
|
13
14
|
export const PAGE_COAST_MAX_MS = 6_000;
|
|
14
15
|
/** iOS page-loading coast bound. */
|
|
15
16
|
export const PAGE_COAST_MAX_MS_IOS = 10_000;
|
|
16
|
-
export const COAST_POLL_MS = 300;
|
|
17
|
-
/** Default non-page coast bound; a timeout returns false rather than failing. */
|
|
18
|
-
export const COAST_MAX_MS = 3_000;
|
|
19
17
|
export const LOOK_MS = 1_000;
|
|
20
18
|
export const LOOK_POLL_MS = 100;
|
|
21
19
|
/** Default settled-frame and pass-check timeout. */
|
|
@@ -85,88 +83,6 @@ export async function scrollUntilShown(t, { cmd, field, cap, blind = 0, planned,
|
|
|
85
83
|
t.driver.log.push({ ...t.driver.context, cmd, t: t.now(), ms: t.now() - start, status, [field]: moves, blind, cap, looks, lookMs, ...over, ...extra() });
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
|
-
/** First drawn row; Android can restrict its text attribute, iOS predicates ask visible == 1. Logs lookups only. */
|
|
89
|
-
async function firstDrawn(t, row, listEl, field, scopedOrField) {
|
|
90
|
-
if (!scopedOrField) {
|
|
91
|
-
return t.driver.findShown(toSelector(row));
|
|
92
|
-
}
|
|
93
|
-
const { childOf: _list, ...inList } = toSelector(row);
|
|
94
|
-
const all = t.driver.platform.locators(inList);
|
|
95
|
-
const call = field === 'text' ? /textMatches/ : /descriptionMatches/;
|
|
96
|
-
const own = t.driver.platform.name === 'android' && field ? all.filter(l => call.test(l.value)) : all;
|
|
97
|
-
for (const { using, value } of own.length ? own : all) {
|
|
98
|
-
const drawn = t.driver.platform.name === 'ios' && using === '-ios predicate string' ? `${value} AND visible == 1` : value;
|
|
99
|
-
const found = await t.driver.find(using, drawn, listEl);
|
|
100
|
-
if (found) {
|
|
101
|
-
return found;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Captures a drawn row before a scroll when hold is provided and empty; lookup failures clear the hold. Use for iOS
|
|
108
|
-
* where visible-row queries during momentum can consume the coast budget. Logs lookup commands only.
|
|
109
|
-
*/
|
|
110
|
-
export async function holdBeforeScroll(t, row, list, field, hold) {
|
|
111
|
-
if (!hold || hold.el) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
hold.el = await firstDrawn(t, row, list?.el, field, Boolean(list || field));
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
hold.el = null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Waits for two matching row frames 300 ms apart, up to maxMs (3000), always attempting at least two reads.
|
|
123
|
-
* Optional list scopes the row and is re-found at most twice on missing/stale reads; field selects an Android text
|
|
124
|
-
* attribute. An optional hold reuses one row across calls until stale. Returns false when still moving. Logs commands only.
|
|
125
|
-
*/
|
|
126
|
-
export async function waitCoasted(t, row, list, field, maxMs = COAST_MAX_MS, hold) {
|
|
127
|
-
const until = t.now() + maxMs;
|
|
128
|
-
let el = list?.el;
|
|
129
|
-
let refinds = 0;
|
|
130
|
-
const read = async () => {
|
|
131
|
-
if (hold?.el) {
|
|
132
|
-
try {
|
|
133
|
-
return await t.driver.cmd('GET', `/element/${hold.el}/rect`);
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
hold.el = null;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const found = await firstDrawn(t, row, el, field, Boolean(list || field));
|
|
140
|
-
if (found && hold) {
|
|
141
|
-
hold.el = found;
|
|
142
|
-
}
|
|
143
|
-
return found ? t.driver.cmd('GET', `/element/${found}/rect`) : null;
|
|
144
|
-
};
|
|
145
|
-
let last = null;
|
|
146
|
-
let reads = 0;
|
|
147
|
-
for (;;) {
|
|
148
|
-
let r = null;
|
|
149
|
-
reads++;
|
|
150
|
-
try {
|
|
151
|
-
r = await read();
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
r = null;
|
|
155
|
-
}
|
|
156
|
-
if (r === null && list && refinds < 2) {
|
|
157
|
-
refinds++;
|
|
158
|
-
el = await t.waitVisible(list.sel);
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const frame = r && `${r.x},${r.y},${r.width},${r.height}`;
|
|
162
|
-
if (frame && frame === last)
|
|
163
|
-
return true;
|
|
164
|
-
if (t.now() >= until && reads >= 2)
|
|
165
|
-
return false;
|
|
166
|
-
last = frame;
|
|
167
|
-
await t.driver.sleep(COAST_POLL_MS);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
86
|
/** Platform gesture budget: cap (3), plus iosTopExtra (2) only when returning to the top on iOS. No commands or logs. */
|
|
171
87
|
export const scrollCap = (t, toward, cap = LONG_SCROLL_CAP, iosTopExtra = IOS_TOP_EXTRA) => cap + (t.driver.platform.name === 'ios' && toward === 'top' ? iosTopExtra : 0);
|
|
172
88
|
const iosHold = (t, cache) => t.driver.platform.name === 'ios' ? (cache.hold ??= {}) : undefined;
|
|
@@ -174,6 +90,7 @@ const iosHold = (t, cache) => t.driver.platform.name === 'ios' ? (cache.hold ??=
|
|
|
174
90
|
* Long-scrolls list toward a partly visible target; scoped defaults true. Refetches missing/stale list handles before
|
|
175
91
|
* moving and rereads the frame before every gesture. A shared cache stores the settled frame, held iOS row and moves used.
|
|
176
92
|
* Defaults: 3 gestures (5 to top on iOS), 2 planned, 20 s frameTimeoutMs, 3 s coast. used subtracts the prior pass budget.
|
|
93
|
+
* A to-top gesture is checked (scrollList `mustMove`); its retry is a logged defect and does not use the budget.
|
|
177
94
|
* Logs fling-to-end/top with flings, blind/cap, looks/lookMs, extra, relists, settled, status and duration, including failures.
|
|
178
95
|
*/
|
|
179
96
|
export async function flingUntilShown(t, list, target, toward, { scoped = true, blind = 0, cache = {}, row, used = 0, cap = LONG_SCROLL_CAP, planned = LONG_SCROLL_PLANNED, iosTopExtra = IOS_TOP_EXTRA, frameTimeoutMs = CHECK_TIMEOUT_MS, coastMaxMs = COAST_MAX_MS, lookWindowMs, lookPollMs } = {}) {
|
|
@@ -223,8 +140,9 @@ export async function flingUntilShown(t, list, target, toward, { scoped = true,
|
|
|
223
140
|
}
|
|
224
141
|
},
|
|
225
142
|
async move() {
|
|
226
|
-
|
|
227
|
-
|
|
143
|
+
await frame();
|
|
144
|
+
// Before a to-top gesture the first row was not shown, so the list has room: the gesture is checked.
|
|
145
|
+
await scrollList(t, { list, row: row ?? target, scoped }, toward, toward === 'top' ? { cache, mustMove: true, hold: iosHold(t, cache), coastMaxMs } : { cache });
|
|
228
146
|
},
|
|
229
147
|
missing: () => `${selectorLabel(target)} not on screen after ${scrollCap(t, toward, cap, iosTopExtra)} long scrolls to the ${toward} of ${selectorLabel(list)}`,
|
|
230
148
|
});
|
|
@@ -283,7 +201,9 @@ async function rowLoaded(t, list, row, scoped, listEl, field) {
|
|
|
283
201
|
* Performs scrolls (3) end gestures. A gesture with no page marker pauses pauseMs (500). Otherwise it waits for coast
|
|
284
202
|
* (Android 6000 ms, iOS 10000), optionally adds one gesture if beforePages was not reached, then waits pageWaitMs (3000)
|
|
285
203
|
* for the page's first loaded row, polling every pagePollMs (300). scoped defaults true; field narrows Android lookups.
|
|
286
|
-
*
|
|
204
|
+
* The pass is taken to start at the top, so its first gesture is checked (scrollList `mustMove`): one that moved nothing is
|
|
205
|
+
* sent once more and counted in `t.defects`; `unmoved` is 1 then, and 2 when the retry moved nothing either (GestureNotTaken).
|
|
206
|
+
* Logs fling-to-end: flings, supplied pageNumbers (or descriptions), coastMs, waitMs, per-page extra, unmoved, pauseMs, settled,
|
|
287
207
|
* status and duration. Page checks do not require visibility. All limits and frameTimeoutMs (20000) are configurable.
|
|
288
208
|
*/
|
|
289
209
|
export async function scrollToEnd(t, list, cache, { pages, pageNumbers, beforePages = [], scoped = true, field, any, scrolls = END_SCROLLS, pauseMs = PAGE_PAUSE_MS, pageWaitMs = PAGE_WAIT_MS, pagePollMs = PAGE_POLL_MS, coastMaxMs = PAGE_COAST_MAX_MS, coastMaxMsIos = PAGE_COAST_MAX_MS_IOS, frameTimeoutMs = CHECK_TIMEOUT_MS }) {
|
|
@@ -294,18 +214,29 @@ export async function scrollToEnd(t, list, cache, { pages, pageNumbers, beforePa
|
|
|
294
214
|
const coastMs = [];
|
|
295
215
|
const waitMs = [];
|
|
296
216
|
const extra = [];
|
|
217
|
+
let unmoved = 0;
|
|
218
|
+
const spec = { list, row: any, field, scoped };
|
|
297
219
|
try {
|
|
298
220
|
if (!cache.at) {
|
|
299
221
|
cache.at = await t.settledFrame(list, frameTimeoutMs);
|
|
300
222
|
settled = true;
|
|
301
223
|
}
|
|
302
224
|
for (let i = 0; i < scrolls; i++) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
225
|
+
// A pass starts at the top, so its first fling has room and is checked.
|
|
226
|
+
let sent;
|
|
227
|
+
try {
|
|
228
|
+
sent = await scrollList(t, spec, 'end', { cache, hold: iosHold(t, cache), mustMove: i === 0, coastMaxMs: t.driver.platform.name === 'ios' ? coastMaxMsIos : coastMaxMs });
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
if (e instanceof GestureNotTaken)
|
|
232
|
+
unmoved = 2;
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
if (sent.retried)
|
|
236
|
+
unmoved = 1;
|
|
306
237
|
const page = pages[i];
|
|
307
238
|
if (!page) {
|
|
308
|
-
await t.driver.sleep(pauseMs);
|
|
239
|
+
await t.driver.sleep(Math.max(0, pauseMs - (t.now() - sent.sentAt)));
|
|
309
240
|
continue;
|
|
310
241
|
}
|
|
311
242
|
const coastFrom = t.now();
|
|
@@ -329,8 +260,7 @@ export async function scrollToEnd(t, list, cache, { pages, pageNumbers, beforePa
|
|
|
329
260
|
}
|
|
330
261
|
if (!reached) {
|
|
331
262
|
extra[i] = 1;
|
|
332
|
-
|
|
333
|
-
await longScroll(t, cache.at.rect, 'end');
|
|
263
|
+
await scrollList(t, spec, 'end', { cache });
|
|
334
264
|
if (!(await waitCoasted(t, any, scoped ? { sel: list, el: cache.at.el } : undefined, field, (t.driver.platform.name === 'ios' ? coastMaxMsIos : coastMaxMs), iosHold(t, cache)))) {
|
|
335
265
|
throw new Error(`${selectorLabel(list)} still moving ${(t.driver.platform.name === 'ios' ? coastMaxMsIos : coastMaxMs)} ms after a long scroll to the end`);
|
|
336
266
|
}
|
|
@@ -361,7 +291,7 @@ export async function scrollToEnd(t, list, cache, { pages, pageNumbers, beforePa
|
|
|
361
291
|
}
|
|
362
292
|
finally {
|
|
363
293
|
const numbers = pageNumbers ?? pages.map(p => describeSelector(toSelector(p)));
|
|
364
|
-
t.driver.log.push({ ...t.driver.context, cmd: 'fling-to-end', t: t.now(), ms: t.now() - start, status, flings: scrolls, pages: numbers, coastMs, waitMs, extra, pauseMs, settled });
|
|
294
|
+
t.driver.log.push({ ...t.driver.context, cmd: 'fling-to-end', t: t.now(), ms: t.now() - start, status, flings: scrolls, pages: numbers, coastMs, waitMs, extra, unmoved, pauseMs, settled });
|
|
365
295
|
}
|
|
366
296
|
}
|
|
367
297
|
/**
|