@keepsnappy/appium-driver 0.0.0-stage → 0.1.0-alpha.12

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.
Files changed (44) hide show
  1. package/README.md +37 -2
  2. package/dist/src/browserstack.d.ts +24 -0
  3. package/dist/src/browserstack.js +54 -0
  4. package/dist/src/browserstack.js.map +1 -0
  5. package/dist/src/cli.d.ts +2 -0
  6. package/dist/src/cli.js +8 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/index.d.ts +10 -0
  9. package/dist/src/index.js +11 -0
  10. package/dist/src/index.js.map +1 -0
  11. package/dist/src/journey.d.ts +43 -0
  12. package/dist/src/journey.js +2 -0
  13. package/dist/src/journey.js.map +1 -0
  14. package/dist/src/links.d.ts +35 -0
  15. package/dist/src/links.js +87 -0
  16. package/dist/src/links.js.map +1 -0
  17. package/dist/src/load.d.ts +4 -0
  18. package/dist/src/load.js +23 -0
  19. package/dist/src/load.js.map +1 -0
  20. package/dist/src/platform.d.ts +63 -0
  21. package/dist/src/platform.js +145 -0
  22. package/dist/src/platform.js.map +1 -0
  23. package/dist/src/run.d.ts +92 -0
  24. package/dist/src/run.js +157 -0
  25. package/dist/src/run.js.map +1 -0
  26. package/dist/src/selectors.d.ts +25 -0
  27. package/dist/src/selectors.js +57 -0
  28. package/dist/src/selectors.js.map +1 -0
  29. package/dist/src/session.d.ts +199 -0
  30. package/dist/src/session.js +602 -0
  31. package/dist/src/session.js.map +1 -0
  32. package/dist/src/snapshot.d.ts +64 -0
  33. package/dist/src/snapshot.js +197 -0
  34. package/dist/src/snapshot.js.map +1 -0
  35. package/dist/src/tokens.d.ts +14 -0
  36. package/dist/src/tokens.js +28 -0
  37. package/dist/src/tokens.js.map +1 -0
  38. package/dist/src/version.d.ts +14 -0
  39. package/dist/src/version.js +69 -0
  40. package/dist/src/version.js.map +1 -0
  41. package/dist/src/webdriver.d.ts +88 -0
  42. package/dist/src/webdriver.js +237 -0
  43. package/dist/src/webdriver.js.map +1 -0
  44. package/package.json +36 -4
@@ -0,0 +1,92 @@
1
+ import type { JourneyModule } from './journey.js';
2
+ import { type FetchLike } from './webdriver.js';
3
+ export interface IterationRecord {
4
+ journey: string;
5
+ iteration: number;
6
+ token: boolean;
7
+ alert?: string | null;
8
+ steps?: number;
9
+ ready?: {
10
+ n: number;
11
+ ms: number;
12
+ };
13
+ flushed?: string | null;
14
+ t_start?: number;
15
+ begin_ms?: number;
16
+ journey_ms?: number;
17
+ end_ms?: number;
18
+ total_ms?: number;
19
+ }
20
+ export interface RunDeps {
21
+ env: Readonly<Record<string, string | undefined>>;
22
+ fetch: FetchLike;
23
+ now: () => number;
24
+ sleep: (ms: number) => Promise<void>;
25
+ print: (line: string) => void;
26
+ error: (line: string) => void;
27
+ }
28
+ export declare const realRunDeps: () => RunDeps;
29
+ /** The flags `perflab farm run` passes (docs/plans/54a section 4); a journey module adds its own through `options`. */
30
+ export declare const DRIVER_OPTIONS: {
31
+ readonly 'journey-module': {
32
+ readonly type: "string";
33
+ };
34
+ readonly app: {
35
+ readonly type: "string";
36
+ };
37
+ readonly 'app-custom-id': {
38
+ readonly type: "string";
39
+ readonly default: "";
40
+ };
41
+ readonly 'tokens-js': {
42
+ readonly type: "string";
43
+ };
44
+ readonly journeys: {
45
+ readonly type: "string";
46
+ };
47
+ readonly repeat: {
48
+ readonly type: "string";
49
+ readonly default: "1";
50
+ };
51
+ readonly device: {
52
+ readonly type: "string";
53
+ };
54
+ readonly 'local-identifier': {
55
+ readonly type: "string";
56
+ };
57
+ readonly 'build-name': {
58
+ readonly type: "string";
59
+ readonly default: "keepsnappy-appium";
60
+ };
61
+ readonly 'appium-version': {
62
+ readonly type: "string";
63
+ readonly default: "2.19.0";
64
+ };
65
+ readonly out: {
66
+ readonly type: "string";
67
+ readonly default: "farm-run";
68
+ };
69
+ readonly platform: {
70
+ readonly type: "string";
71
+ readonly default: "ios";
72
+ };
73
+ readonly 'action-gc': {
74
+ readonly type: "string";
75
+ readonly default: "off";
76
+ };
77
+ readonly 'end-gc': {
78
+ readonly type: "string";
79
+ readonly default: "off" | "on";
80
+ };
81
+ };
82
+ type Args = Record<string, string | boolean | undefined>;
83
+ export declare function parseDriverArgs(argv: readonly string[], module: JourneyModule): Args;
84
+ /** The module path out of the command line, before the module's own options are known. */
85
+ export declare function journeyModulePath(argv: readonly string[]): string | undefined;
86
+ /**
87
+ * One BrowserStack App Automate session that runs every iteration of the module's journeys, then a final flush, and
88
+ * writes <out>/driver-log.json and <out>/browserstack.<platform>.json in the shape `perflab farm close --bs-build`
89
+ * reads. Returns the exit code: 0, or 1 when the session or an iteration failed (the result files are written anyway).
90
+ */
91
+ export declare function runDriver(argv: readonly string[], module: JourneyModule, deps?: RunDeps): Promise<number>;
92
+ export {};
@@ -0,0 +1,157 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseArgs } from 'node:util';
4
+ import { basicAuth, HUB, resolveApp, sessionCaps, sessionDetail } from './browserstack.js';
5
+ import { END_GC_DEFAULT } from './links.js';
6
+ import { platformOf } from './platform.js';
7
+ import { Session } from './session.js';
8
+ import { parseTokensJs, plan } from './tokens.js';
9
+ import { Driver, realSleep } from './webdriver.js';
10
+ export const realRunDeps = () => ({
11
+ env: process.env,
12
+ fetch: fetch,
13
+ now: Date.now,
14
+ sleep: realSleep,
15
+ print: (l) => console.log(l),
16
+ error: (l) => console.error(l),
17
+ });
18
+ /** The flags `perflab farm run` passes (docs/plans/54a section 4); a journey module adds its own through `options`. */
19
+ export const DRIVER_OPTIONS = {
20
+ 'journey-module': { type: 'string' },
21
+ app: { type: 'string' },
22
+ 'app-custom-id': { type: 'string', default: '' },
23
+ 'tokens-js': { type: 'string' },
24
+ journeys: { type: 'string' },
25
+ repeat: { type: 'string', default: '1' },
26
+ device: { type: 'string' },
27
+ 'local-identifier': { type: 'string' },
28
+ 'build-name': { type: 'string', default: 'keepsnappy-appium' },
29
+ 'appium-version': { type: 'string', default: '2.19.0' },
30
+ out: { type: 'string', default: 'farm-run' },
31
+ platform: { type: 'string', default: 'ios' },
32
+ 'action-gc': { type: 'string', default: 'off' },
33
+ 'end-gc': { type: 'string', default: END_GC_DEFAULT ? 'on' : 'off' },
34
+ };
35
+ export function parseDriverArgs(argv, module) {
36
+ const values = parseArgs({ args: [...argv], options: { ...(module.options ?? {}), ...DRIVER_OPTIONS } }).values;
37
+ // The journey reads `ctx.args.device`: it is the resolved device, not unset.
38
+ values.device ??= platformOf(String(values.platform)).defaultDevice;
39
+ return values;
40
+ }
41
+ /** The module path out of the command line, before the module's own options are known. */
42
+ export function journeyModulePath(argv) {
43
+ return parseArgs({ args: [...argv], options: { 'journey-module': { type: 'string' } }, strict: false }).values['journey-module'];
44
+ }
45
+ /**
46
+ * One BrowserStack App Automate session that runs every iteration of the module's journeys, then a final flush, and
47
+ * writes <out>/driver-log.json and <out>/browserstack.<platform>.json in the shape `perflab farm close --bs-build`
48
+ * reads. Returns the exit code: 0, or 1 when the session or an iteration failed (the result files are written anyway).
49
+ */
50
+ export async function runDriver(argv, module, deps = realRunDeps()) {
51
+ const a = parseDriverArgs(argv, module);
52
+ const user = deps.env.BROWSERSTACK_USERNAME;
53
+ const key = deps.env.BROWSERSTACK_ACCESS_KEY;
54
+ if (!user || !key)
55
+ throw new Error('BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY are not set');
56
+ if (!a.app || !a['tokens-js'] || !a.journeys)
57
+ throw new Error('--app, --tokens-js and --journeys are required');
58
+ const platform = platformOf(String(a.platform));
59
+ const journeys = String(a.journeys).split(',').map((s) => s.trim()).filter(Boolean);
60
+ const unknown = journeys.filter((j) => !Object.hasOwn(module.journeys, j));
61
+ if (unknown.length)
62
+ throw new Error(`the journey module has no journey ${unknown.map((j) => `"${j}"`).join(', ')} (it has ${Object.keys(module.journeys).join(', ') || 'none'})`);
63
+ const repeat = Number(a.repeat);
64
+ if (!Number.isInteger(repeat) || repeat < 1)
65
+ throw new Error('--repeat must be a whole number >= 1');
66
+ if (a['action-gc'] !== 'on' && a['action-gc'] !== 'off')
67
+ throw new Error('--action-gc must be on or off');
68
+ if (a['end-gc'] !== 'on' && a['end-gc'] !== 'off')
69
+ throw new Error('--end-gc must be on or off');
70
+ const out = String(a.out);
71
+ const auth = basicAuth(user, key);
72
+ mkdirSync(out, { recursive: true });
73
+ const { api, tokens } = parseTokensJs(readFileSync(String(a['tokens-js']), 'utf8'));
74
+ const appUrl = await resolveApp(String(a.app), String(a['app-custom-id'] ?? ''), auth, deps.fetch, platform);
75
+ deps.print(`app: ${appUrl}`);
76
+ const log = [];
77
+ const driver = new Driver({ hub: HUB, log, headers: { Authorization: auth }, bundleId: module.app.bundleId, platform, fetch: deps.fetch, now: deps.now, sleep: deps.sleep });
78
+ const t = new Session({ driver, scheme: module.app.scheme, fetch: deps.fetch, api, actionGc: a['action-gc'] === 'on', endGc: a['end-gc'] === 'on' });
79
+ const sessionName = module.sessionName ?? 'perflab (appium)';
80
+ const device = String(a.device);
81
+ const ctx = { fetch: deps.fetch, tokens, journeys, repeat, args: a };
82
+ const caps = sessionCaps({ appUrl, device, buildName: String(a['build-name']), sessionName, appiumVersion: String(a['appium-version']), platform, localIdentifier: a['local-identifier'] });
83
+ const startedAt = deps.now();
84
+ let error = null;
85
+ const iterations = [];
86
+ try {
87
+ await driver.start(caps);
88
+ deps.print(`session ${driver.sessionId}`);
89
+ await driver
90
+ .cmd('POST', '/appium/settings', { settings: platform.settings })
91
+ .catch(() => null);
92
+ await module.setup?.(t, ctx);
93
+ await runIterations(t, module, ctx, iterations);
94
+ }
95
+ catch (e) {
96
+ error = e instanceof Error ? e : new Error(String(e));
97
+ deps.error(`::error::${error.message}`);
98
+ if (driver.sessionId) {
99
+ const source = await driver.cmd('GET', '/source').catch(() => null);
100
+ if (typeof source === 'string')
101
+ writeFileSync(join(out, 'failure-source.xml'), source);
102
+ const png = await driver.cmd('GET', '/screenshot').catch(() => null);
103
+ if (typeof png === 'string')
104
+ writeFileSync(join(out, 'failure-screen.png'), Buffer.from(png, 'base64'));
105
+ }
106
+ }
107
+ if (driver.sessionId) {
108
+ // A failed iteration still sends what it measured.
109
+ driver.context = { phase: 'final-flush' };
110
+ await t.endRun().catch(() => null);
111
+ const reason = error ? error.message.slice(0, 250) : `${sessionName} done`;
112
+ await driver.execute(`browserstack_executor: ${JSON.stringify({ action: 'setSessionStatus', arguments: { status: error ? 'failed' : 'passed', reason } })}`).catch(() => null);
113
+ }
114
+ const endedAt = deps.now();
115
+ await driver.end();
116
+ const detail = driver.sessionId ? await sessionDetail(driver.sessionId, auth, deps.fetch) : {};
117
+ const buildId = detail.build_hashed_id ?? null;
118
+ writeFileSync(join(out, `browserstack.${String(a.platform)}.json`), `${JSON.stringify({
119
+ driver: 'appium',
120
+ status: error ? 'failed' : 'passed',
121
+ buildId,
122
+ dashboard: detail.public_url ?? detail.browser_url ?? null,
123
+ devices: [{ device, sessions: [{ id: driver.sessionId, ...(error ? { error: { message: error.message } } : {}) }] }],
124
+ }, null, 2)}\n`);
125
+ writeFileSync(join(out, 'driver-log.json'), `${JSON.stringify({ driver: 'appium', device, appUrl, session: driver.sessionId, buildId, linkMode: t.linkState.linkMode ?? null, actionGc: t.actionGc, endGc: t.endGc, linkError: t.linkState.linkError ?? null, startedAt, endedAt, total_ms: endedAt - startedAt, error: error?.message ?? null, iterations, commands: log }, null, 2)}\n`);
126
+ deps.print(`driver-log.json: ${log.length} commands, ${iterations.length} iterations, ${Math.round((endedAt - startedAt) / 1000)} s`);
127
+ for (const r of iterations)
128
+ deps.print(` ${r.journey} #${r.iteration}: ${r.total_ms ?? '?'} ms, flushed=${r.flushed}`);
129
+ return error ? 1 : 0;
130
+ }
131
+ /** Per iteration: begin-run (relaunch, home, activation link), the journey, `run:complete`, end-run (markers, flush link). */
132
+ async function runIterations(t, module, ctx, iterations) {
133
+ const { driver } = t;
134
+ for (const it of plan(ctx.journeys, ctx.repeat, ctx.tokens)) {
135
+ const rec = { journey: it.journey, iteration: it.iteration, token: it.token !== '' };
136
+ iterations.push(rec);
137
+ driver.context = { journey: it.journey, iteration: it.iteration, phase: 'begin' };
138
+ const t0 = t.now();
139
+ rec.alert = await t.beginRun({ token: it.token, home: module.home, ...(module.sdkArgs ? { args: module.sdkArgs } : {}) });
140
+ const t1 = t.now();
141
+ driver.context = { ...driver.context, phase: 'journey' };
142
+ const waitsBefore = t.readyWaits.length;
143
+ await module.journeys[it.journey](t, { ...ctx, journey: it.journey, iteration: it.iteration });
144
+ // The last action's screen is part of the journey: its wait ends before the completion marker and the flush.
145
+ await t.ready();
146
+ const t2 = t.now();
147
+ t.marker('run:complete', 'end');
148
+ rec.steps = t.markers.filter((m) => m.phase === 'start').length;
149
+ const waits = t.readyWaits.slice(waitsBefore);
150
+ rec.ready = { n: waits.length, ms: waits.reduce((x, y) => x + y, 0) };
151
+ driver.context = { ...driver.context, phase: 'end' };
152
+ rec.flushed = await t.endRun();
153
+ const t3 = t.now();
154
+ Object.assign(rec, { t_start: t0, begin_ms: t1 - t0, journey_ms: t2 - t1, end_ms: t3 - t2, total_ms: t3 - t0 });
155
+ }
156
+ }
157
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /** What a journey points at. A bare string is an accessibility id. */
2
+ export interface Selector {
3
+ id?: string;
4
+ text?: string;
5
+ childOf?: string;
6
+ }
7
+ export type SelectorLike = Selector | string;
8
+ export declare const toSelector: (s: SelectorLike) => Selector;
9
+ export declare const id: (value: string) => Selector;
10
+ export declare const text: (value: string) => Selector;
11
+ /** An id whose text also matches. */
12
+ export declare const idText: (value: string, re: string) => Selector;
13
+ export declare const childOf: (selector: Selector, parentId: string) => Selector;
14
+ export declare const REGEX_CHARS: RegExp;
15
+ export interface Locator {
16
+ using: string;
17
+ value: string;
18
+ }
19
+ /** The WebDriver locator for a selector, without its parent. */
20
+ export declare function locator(sel: Selector): Locator;
21
+ export declare const describeSelector: (sel: Selector) => string;
22
+ /** The UiAutomator2 locators for a selector, without its parent, in the order to try them. */
23
+ export declare function androidLocators(sel: Selector): Locator[];
24
+ /** One UiAutomator2 lookup that finds an element whose resource id is any of `ids` (a plain id is matched literally, a regex id as a whole value). */
25
+ export declare function androidAnyOfIds(ids: readonly string[]): Locator;
@@ -0,0 +1,57 @@
1
+ export const toSelector = (s) => (typeof s === 'string' ? { id: s } : s);
2
+ export const id = (value) => ({ id: value });
3
+ export const text = (value) => ({ text: value });
4
+ /** An id whose text also matches. */
5
+ export const idText = (value, re) => ({ id: value, text: re });
6
+ export const childOf = (selector, parentId) => ({ ...selector, childOf: parentId });
7
+ // `id` and `text` match whole values as regexes; a plain id goes to the exact accessibility-id lookup.
8
+ export const REGEX_CHARS = /[\\()[\]*+?|^$]/;
9
+ const quote = (s) => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
10
+ const matches = (attr, re) => `${attr} MATCHES ${quote(`(?is)${re}`)}`;
11
+ const textMatch = (re) => `(${matches('label', re)} OR ${matches('value', re)})`;
12
+ /** The WebDriver locator for a selector, without its parent. */
13
+ export function locator(sel) {
14
+ if (sel.id !== undefined && sel.text === undefined && !REGEX_CHARS.test(sel.id)) {
15
+ return { using: 'accessibility id', value: sel.id };
16
+ }
17
+ const parts = [];
18
+ if (sel.id !== undefined)
19
+ parts.push(REGEX_CHARS.test(sel.id) ? matches('name', sel.id) : `name == ${quote(sel.id)}`);
20
+ if (sel.text !== undefined)
21
+ parts.push(textMatch(sel.text));
22
+ return { using: '-ios predicate string', value: parts.join(' AND ') };
23
+ }
24
+ export const describeSelector = (sel) => [sel.id !== undefined ? `id=${sel.id}` : null, sel.text !== undefined ? `text=${sel.text}` : null, sel.childOf !== undefined ? `childOf=${sel.childOf}` : null]
25
+ .filter(Boolean)
26
+ .join(' ');
27
+ // UiAutomator2: an RN testID is the `resource-id` (bare, without the package prefix); `accessibilityLabel` is `content-desc`.
28
+ // UiSelector has no OR, so a text selector is two lookups (`text`, then `content-desc`) and the driver tries them in turn.
29
+ // UiAutomator2's selector parser reads a backslash literally (measured on a Galaxy S23: "Tap \\(1\\)" matched, the Java-doubled form did not), only `\"` is an escape.
30
+ const javaString = (s) => `"${s.replace(/"/g, '\\"')}"`;
31
+ // A value ending in a backslash would escape the closing quote: an empty group after an escaped backslash fixes it, an odd count is not a regex.
32
+ const javaRegex = (re) => {
33
+ const trailing = /\\*$/.exec(re)[0].length;
34
+ if (trailing % 2 === 1)
35
+ throw new Error(`selector ${JSON.stringify(re)} ends in a lone backslash`);
36
+ return javaString(`(?is)${re}${trailing > 0 ? '(?:)' : ''}`);
37
+ };
38
+ /** The UiAutomator2 locators for a selector, without its parent, in the order to try them. */
39
+ export function androidLocators(sel) {
40
+ const base = () => {
41
+ if (sel.id === undefined)
42
+ return [];
43
+ return [REGEX_CHARS.test(sel.id) ? `resourceIdMatches(${javaRegex(sel.id)})` : `resourceId(${javaString(sel.id)})`];
44
+ };
45
+ const using = '-android uiautomator';
46
+ const build = (extra) => ({ using, value: `new UiSelector()${[...base(), ...extra].map((c) => `.${c}`).join('')}` });
47
+ if (sel.text === undefined)
48
+ return [build([])];
49
+ return [build([`textMatches(${javaRegex(sel.text)})`]), build([`descriptionMatches(${javaRegex(sel.text)})`])];
50
+ }
51
+ const escapeRegex = (s) => s.replace(/[\\^$.|?*+()[\]{}]/g, '\\$&');
52
+ /** One UiAutomator2 lookup that finds an element whose resource id is any of `ids` (a plain id is matched literally, a regex id as a whole value). */
53
+ export function androidAnyOfIds(ids) {
54
+ const alternatives = ids.map((id) => `(?:${REGEX_CHARS.test(id) ? id : escapeRegex(id)})`).join('|');
55
+ return { using: '-android uiautomator', value: `new UiSelector().resourceIdMatches(${javaRegex(alternatives)})` };
56
+ }
57
+ //# sourceMappingURL=selectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../src/selectors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ import { type LinkState } from './links.js';
2
+ import { type SelectorLike } from './selectors.js';
3
+ import { type Driver, type FetchLike } from './webdriver.js';
4
+ export interface Marker {
5
+ token: string;
6
+ step: string;
7
+ phase: 'start' | 'end';
8
+ t_host: number;
9
+ /** The GC command answered just before this step's start (GC before the action). */
10
+ gc_n?: number;
11
+ }
12
+ /** One request/response round trip with the PerfLab API: t0/t3 this host's send/receive, t1/t2 the server's receive/send. */
13
+ export interface ClockTrip {
14
+ t0: number;
15
+ t1: number;
16
+ t2: number;
17
+ t3: number;
18
+ }
19
+ /** The API takes at most this many markers in one request; more go out in several. */
20
+ export declare const MARKERS_PER_REQUEST = 500;
21
+ /**
22
+ * Small /v1/clock round trips per run after its markers. The marker batch is one slow trip (a large body), and the API takes
23
+ * the shortest trip's half-delay as the host clock's error: one trip left it at 130-200 ms, more than a short step lasts.
24
+ */
25
+ export declare const HOST_CLOCK_TRIPS = 6;
26
+ export interface SendOptions {
27
+ sleep?: (ms: number) => Promise<void>;
28
+ /** This host's clock, the one `t_host` is on (each request is a round trip on it). */
29
+ now?: () => number;
30
+ }
31
+ /**
32
+ * The iteration's markers, posted after it ended so sending never changes the test's pace: one request per run token
33
+ * (per 500 markers), so a long journey does not run into the API's per-run rate limit. An API that predates batches
34
+ * answers 400 without per-item results; its markers then go one by one as before. Each request is a round trip on the
35
+ * clock of `t_host`: its trip rides on the run's next request and the last one goes to /v1/clock, followed by
36
+ * HOST_CLOCK_TRIPS small clock round trips, so the API can put the markers on the device's clock. Returns how many arrived.
37
+ */
38
+ export declare function sendMarkers(fetchImpl: FetchLike, api: string, markers: Marker[], opts?: SendOptions): Promise<number>;
39
+ export declare const FLUSH_WAIT_MS: number;
40
+ type Step = () => Promise<unknown> | unknown;
41
+ /** One thing a step expects of the screen: an element shown, an element gone, or the keyboard up or down. */
42
+ export type Condition = {
43
+ shown: SelectorLike;
44
+ } | {
45
+ gone: SelectorLike;
46
+ } | {
47
+ keyboard: 'up' | 'down';
48
+ };
49
+ /** GC before the action: `off` (the default), `step` (a command before every declared step), or why it stopped. */
50
+ export type ActionGc = 'off' | 'step' | 'unsupported' | 'unavailable' | 'safari';
51
+ export interface SessionOptions {
52
+ driver: Driver;
53
+ scheme: string;
54
+ fetch?: FetchLike;
55
+ api?: string;
56
+ /** Sends a GC command before every declared step and once before the flush. An experiment switch, off by default. */
57
+ actionGc?: boolean;
58
+ /**
59
+ * Sends one GC command at the run end, after the journey's last step and before the flush, also when `actionGc` is off: the end
60
+ * memory reading after a GC (REQ-MEAS-35). On by default (`END_GC_DEFAULT`); the journey should end on its home screen.
61
+ */
62
+ endGc?: boolean;
63
+ }
64
+ /** One measured session: the phone, the run tokens, and the step vocabulary a journey calls. */
65
+ export declare class Session {
66
+ readonly driver: Driver;
67
+ readonly scheme: string;
68
+ readonly fetch: FetchLike;
69
+ readonly api: string;
70
+ markers: Marker[];
71
+ token: string;
72
+ readonly linkState: LinkState;
73
+ /** What the journey said must show before the next action: the previous action's result. */
74
+ pendingReady: {
75
+ sel: SelectorLike;
76
+ timeoutMs: number;
77
+ } | null;
78
+ /** Time each readiness wait took, in ms. */
79
+ readonly readyWaits: number[];
80
+ actionGc: ActionGc;
81
+ readonly endGc: boolean;
82
+ /** Unique for the whole session; the SDK keeps its answers by run token and n. */
83
+ gcN: number;
84
+ /** Steps open right now: a step inside another step's action sends no GC, which would land inside the outer step. */
85
+ stepDepth: number;
86
+ /** Elements looked up again after going stale, over the session (see `frameOf`). */
87
+ private refinds;
88
+ get staleRefinds(): number;
89
+ constructor(o: SessionOptions);
90
+ /**
91
+ * One GC command; returns its number, or null when none was sent or none ran. Any answer but `gc:<n> ok` (none, a native
92
+ * timeout, an error, `unavailable` from an app without a GC) turns the command off for the rest of the session: a measuring
93
+ * aid never fails the customer's test, and the steps then carry no number. `atEnd`: the run end's command, which `endGc` sends
94
+ * also while the per-step command is off.
95
+ */
96
+ gc(atEnd?: boolean): Promise<number | null>;
97
+ now(): number;
98
+ /** After the action just made: the next action waits until `sel` is visible (the screen or element the action leads to). */
99
+ expectNext(sel: SelectorLike, timeoutMs?: number): void;
100
+ /** Before the next action: waits for what `expectNext` named, if anything. Throws when it does not show in time. */
101
+ ready(): Promise<number>;
102
+ /** A declared step marker, kept until the iteration ends. */
103
+ marker(step: string, phase: 'start' | 'end', gcN?: number | null): void;
104
+ /**
105
+ * One declared step: the readiness wait, `start`, the action, its checks and `once` (asserted before `end` only), `end`, then
106
+ * the checks again. `step(name, fn)` is the short form.
107
+ */
108
+ step(name: string, action: Step, checks?: Step, once?: Step): Promise<void>;
109
+ isVisible(sel: SelectorLike): Promise<boolean>;
110
+ waitVisible(sel: SelectorLike, timeoutMs?: number): Promise<string>;
111
+ waitGone(sel: SelectorLike, timeoutMs?: number): Promise<void>;
112
+ /** Waits until the keyboard is up (or down). */
113
+ waitKeyboard(want: 'up' | 'down', timeoutMs?: number): Promise<void>;
114
+ /**
115
+ * Waits until all the conditions hold in one poll. A poll asks them one lookup after another, so "the same poll" means the
116
+ * answers are a few hundred ms apart, not one instant. Where the platform can look for several ids in one request (Android)
117
+ * the ids are asked together; elsewhere each condition is waited for in turn, with its own timeout, as `waitVisible`,
118
+ * `waitGone` and `waitKeyboard` do.
119
+ */
120
+ waitAll(conditions: readonly Condition[], timeoutMs?: number): Promise<void>;
121
+ tap(sel: SelectorLike, timeoutMs?: number): Promise<void>;
122
+ /** Taps again while `target` has not shown and `sel` is still there (a tap right after a launch can land before the app takes touches). Returns how many extra taps it took. */
123
+ tapUntil(sel: SelectorLike, target: SelectorLike, timeoutMs?: number, retapAfterMs?: number): Promise<number>;
124
+ /**
125
+ * Swipe start/end in percent of the window. `target` is what the swipe acts on (the list or screen it starts in): the swipe
126
+ * waits for it to be visible, so it does not fire during the transition of the action before it. It does not wait for a fling to stop: the next tap after a swipe still needs `expectNext` on an element that is stable only once the scroll ended.
127
+ */
128
+ swipe(from: readonly [number, number], to: readonly [number, number], durationMs?: number, target?: SelectorLike): Promise<void>;
129
+ /** Swipe from an element's centre to 90 % of the window height (down) or 10 % (up). */
130
+ swipeFrom(sel: SelectorLike, direction: 'up' | 'down', durationMs?: number): Promise<void>;
131
+ /** Swipes until `sel` shows or `times` runs out. */
132
+ swipeUntilVisible(sel: SelectorLike, from: readonly [number, number], to: readonly [number, number], durationMs: number, times: number): Promise<void>;
133
+ /** Swipes from `from` (an element, as `swipeFrom`) until `target` shows or `times` swipes are used; throws when it never shows. */
134
+ swipeFromUntilVisible(from: SelectorLike, direction: 'up' | 'down', target: SelectorLike, times: number, durationMs?: number): Promise<void>;
135
+ /** Checks for the element fully on screen, then swipes from the centre, for as long as the timeout. speed 40 is a 601 ms swipe. */
136
+ scrollUntil(sel: SelectorLike, { direction, timeoutMs, speed }?: {
137
+ direction?: 'up' | 'down';
138
+ timeoutMs?: number;
139
+ speed?: number;
140
+ }): Promise<void>;
141
+ /**
142
+ * The frame of `sel`'s element. When the element went stale (the app re-mounted it between the lookup and the read), it is
143
+ * looked up again by the same selector, at most STALE_REFINDS times for this read; any other error passes straight through.
144
+ * A re-find is a lookup, not a touch: it adds no gesture, but it adds its lookup time to the window of the touch before it.
145
+ */
146
+ private frameOf;
147
+ /** `sel`'s frame once it reads the same twice in a row (a sheet still sliding in moves it). */
148
+ private settledFrame;
149
+ /** One fling inside `r`: 80 % to 30 % of its height towards the end, 30 % to 80 % back to the top, on its centre line. */
150
+ private flingIn;
151
+ /**
152
+ * Exactly `times` flings of `list` towards its end or back to its top, whatever the list holds: the same touches, so the
153
+ * same windows, in every run (REQ-FARM-33). The list's frame is read (settled) once, before the first fling, and every
154
+ * fling uses it: nothing is looked up or read between flings, so no lookup or re-find lands inside their windows. The
155
+ * caller then checks a condition the flings always reach.
156
+ */
157
+ flingTimes(list: SelectorLike, times: number, toward: 'end' | 'top', { durationMs, timeoutMs }?: {
158
+ durationMs?: number;
159
+ timeoutMs?: number;
160
+ }): Promise<void>;
161
+ /**
162
+ * Flings `list` until `target` is fully on screen. The number of flings depends on the data, so the step's windows can
163
+ * differ between runs: NOT for a judged step (REQ-FARM-33; use `flingTimes`). `blind` flings go first with no lookup.
164
+ * Returns the flings; logs flings, checks, blind and re-finds, also when it fails.
165
+ */
166
+ flingUntil(list: SelectorLike, target: SelectorLike, toward: 'end' | 'top', { blind, durationMs, timeoutMs }?: {
167
+ blind?: number;
168
+ durationMs?: number;
169
+ timeoutMs?: number;
170
+ }): Promise<number>;
171
+ /** The centres of `sels`' frames, once every frame reads the same twice in a row; a stale element is looked up again. For `tapAt`. */
172
+ stableCentres(sels: readonly SelectorLike[], timeoutMs?: number): Promise<{
173
+ x: number;
174
+ y: number;
175
+ }[]>;
176
+ /** A touch at a window point with no lookup (e.g. from `stableCentres`): the same touch as `tap`, after the same `ready()`. */
177
+ tapAt(point: {
178
+ x: number;
179
+ y: number;
180
+ }): Promise<void>;
181
+ type(sel: SelectorLike, value: string): Promise<void>;
182
+ /** Taps the button of a system alert if one shows within `waitMs`; returns the label tapped, or null. */
183
+ acceptAlert(labels: readonly string[], waitMs: number): Promise<string | null>;
184
+ openLink(url: string): Promise<string | null>;
185
+ /** A relaunch ends the screen a pending `expectNext` was waiting for; the wait must not leak into what comes after. */
186
+ relaunch(): Promise<void>;
187
+ activate(): Promise<unknown>;
188
+ /** Launch, wait for `home`, then the activation link with this iteration's token; `args` (`k=v&...`) override the run defaults. */
189
+ beginRun({ token, args, home }: {
190
+ token: string;
191
+ args?: string;
192
+ home: SelectorLike;
193
+ }): Promise<string | null>;
194
+ /** Markers, then the flush link, then the SDK's own `perflab.flushed` (ok | error | off). */
195
+ endRun(): Promise<string | null>;
196
+ /** The heavy lookup, with its setting on only while it runs. */
197
+ private findFallback;
198
+ }
199
+ export {};