lynkeus 0.2.0
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/lib/__tests__/args.test.d.ts +1 -0
- package/lib/__tests__/args.test.js +69 -0
- package/lib/__tests__/check.test.d.ts +1 -0
- package/lib/__tests__/check.test.js +60 -0
- package/lib/__tests__/init.test.d.ts +1 -0
- package/lib/__tests__/init.test.js +42 -0
- package/lib/__tests__/record.test.d.ts +1 -0
- package/lib/__tests__/record.test.js +44 -0
- package/lib/__tests__/snapshot.test.d.ts +1 -0
- package/lib/__tests__/snapshot.test.js +47 -0
- package/lib/__tests__/targets.test.d.ts +1 -0
- package/lib/__tests__/targets.test.js +127 -0
- package/lib/args.d.ts +30 -0
- package/lib/args.js +86 -0
- package/lib/cases/__tests__/format.test.d.ts +1 -0
- package/lib/cases/__tests__/format.test.js +61 -0
- package/lib/cases/__tests__/runner.test.d.ts +1 -0
- package/lib/cases/__tests__/runner.test.js +188 -0
- package/lib/cases/fixtures.d.ts +13 -0
- package/lib/cases/fixtures.js +100 -0
- package/lib/cases/format.d.ts +37 -0
- package/lib/cases/format.js +169 -0
- package/lib/cases/runner.d.ts +61 -0
- package/lib/cases/runner.js +590 -0
- package/lib/cases/templates.d.ts +20 -0
- package/lib/cases/templates.js +156 -0
- package/lib/cli.d.ts +6 -0
- package/lib/cli.js +173 -0
- package/lib/commands/cases.d.ts +1 -0
- package/lib/commands/cases.js +179 -0
- package/lib/commands/check.d.ts +36 -0
- package/lib/commands/check.js +67 -0
- package/lib/commands/crawl/return.d.ts +20 -0
- package/lib/commands/crawl/return.js +125 -0
- package/lib/commands/crawl/state.d.ts +30 -0
- package/lib/commands/crawl/state.js +56 -0
- package/lib/commands/crawl.d.ts +67 -0
- package/lib/commands/crawl.js +213 -0
- package/lib/commands/device.d.ts +10 -0
- package/lib/commands/device.js +122 -0
- package/lib/commands/doctor.d.ts +3 -0
- package/lib/commands/doctor.js +240 -0
- package/lib/commands/drive.d.ts +1 -0
- package/lib/commands/drive.js +410 -0
- package/lib/commands/flake.d.ts +14 -0
- package/lib/commands/flake.js +42 -0
- package/lib/commands/headless.d.ts +1 -0
- package/lib/commands/headless.js +127 -0
- package/lib/commands/init.d.ts +13 -0
- package/lib/commands/init.js +85 -0
- package/lib/commands/map.d.ts +2 -0
- package/lib/commands/map.js +297 -0
- package/lib/commands/os.d.ts +1 -0
- package/lib/commands/os.js +193 -0
- package/lib/commands/overlay.d.ts +9 -0
- package/lib/commands/overlay.js +12 -0
- package/lib/commands/record.d.ts +5 -0
- package/lib/commands/record.js +55 -0
- package/lib/commands/runs.d.ts +1 -0
- package/lib/commands/runs.js +452 -0
- package/lib/commands/smoke.d.ts +28 -0
- package/lib/commands/smoke.js +59 -0
- package/lib/commands/snapshot.d.ts +27 -0
- package/lib/commands/snapshot.js +69 -0
- package/lib/config.d.ts +72 -0
- package/lib/config.js +44 -0
- package/lib/device/devices.d.ts +11 -0
- package/lib/device/devices.js +33 -0
- package/lib/device/driver.d.ts +53 -0
- package/lib/device/driver.js +169 -0
- package/lib/device/platform.d.ts +7 -0
- package/lib/device/platform.js +59 -0
- package/lib/files.d.ts +7 -0
- package/lib/files.js +29 -0
- package/lib/flow/artifacts.d.ts +19 -0
- package/lib/flow/artifacts.js +49 -0
- package/lib/flow/runner.d.ts +13 -0
- package/lib/flow/runner.js +169 -0
- package/lib/flow/types.d.ts +111 -0
- package/lib/flow/types.js +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +11 -0
- package/lib/knowledge/__tests__/edges.test.d.ts +1 -0
- package/lib/knowledge/__tests__/edges.test.js +25 -0
- package/lib/knowledge/__tests__/events.test.d.ts +1 -0
- package/lib/knowledge/__tests__/events.test.js +39 -0
- package/lib/knowledge/__tests__/extract.test.d.ts +1 -0
- package/lib/knowledge/__tests__/extract.test.js +69 -0
- package/lib/knowledge/__tests__/frontier.test.d.ts +1 -0
- package/lib/knowledge/__tests__/frontier.test.js +57 -0
- package/lib/knowledge/__tests__/funnels.test.d.ts +1 -0
- package/lib/knowledge/__tests__/funnels.test.js +40 -0
- package/lib/knowledge/__tests__/lint.test.d.ts +1 -0
- package/lib/knowledge/__tests__/lint.test.js +53 -0
- package/lib/knowledge/__tests__/perf.test.d.ts +1 -0
- package/lib/knowledge/__tests__/perf.test.js +34 -0
- package/lib/knowledge/__tests__/plan.test.d.ts +1 -0
- package/lib/knowledge/__tests__/plan.test.js +35 -0
- package/lib/knowledge/__tests__/query.test.d.ts +1 -0
- package/lib/knowledge/__tests__/query.test.js +55 -0
- package/lib/knowledge/__tests__/screens.test.d.ts +1 -0
- package/lib/knowledge/__tests__/screens.test.js +30 -0
- package/lib/knowledge/__tests__/why.test.d.ts +1 -0
- package/lib/knowledge/__tests__/why.test.js +28 -0
- package/lib/knowledge/edges.d.ts +30 -0
- package/lib/knowledge/edges.js +68 -0
- package/lib/knowledge/errors.d.ts +8 -0
- package/lib/knowledge/errors.js +62 -0
- package/lib/knowledge/events.d.ts +23 -0
- package/lib/knowledge/events.js +51 -0
- package/lib/knowledge/extract.d.ts +26 -0
- package/lib/knowledge/extract.js +365 -0
- package/lib/knowledge/frontier.d.ts +26 -0
- package/lib/knowledge/frontier.js +60 -0
- package/lib/knowledge/funnels.d.ts +46 -0
- package/lib/knowledge/funnels.js +103 -0
- package/lib/knowledge/lint.d.ts +16 -0
- package/lib/knowledge/lint.js +89 -0
- package/lib/knowledge/perf.d.ts +32 -0
- package/lib/knowledge/perf.js +91 -0
- package/lib/knowledge/plan.d.ts +22 -0
- package/lib/knowledge/plan.js +81 -0
- package/lib/knowledge/problems.d.ts +5 -0
- package/lib/knowledge/problems.js +8 -0
- package/lib/knowledge/query.d.ts +36 -0
- package/lib/knowledge/query.js +127 -0
- package/lib/knowledge/route-params.d.ts +6 -0
- package/lib/knowledge/route-params.js +42 -0
- package/lib/knowledge/screens.d.ts +30 -0
- package/lib/knowledge/screens.js +125 -0
- package/lib/knowledge/tree.d.ts +15 -0
- package/lib/knowledge/tree.js +63 -0
- package/lib/knowledge/types.d.ts +62 -0
- package/lib/knowledge/types.js +6 -0
- package/lib/knowledge/why.d.ts +24 -0
- package/lib/knowledge/why.js +153 -0
- package/lib/mcp/server.d.ts +4 -0
- package/lib/mcp/server.js +111 -0
- package/lib/registry.d.ts +78 -0
- package/lib/registry.js +113 -0
- package/lib/runtime.d.ts +12 -0
- package/lib/runtime.js +56 -0
- package/lib/targets.d.ts +17 -0
- package/lib/targets.js +43 -0
- package/package.json +47 -0
- package/src/__tests__/args.test.ts +82 -0
- package/src/__tests__/check.test.ts +71 -0
- package/src/__tests__/init.test.ts +52 -0
- package/src/__tests__/record.test.ts +49 -0
- package/src/__tests__/snapshot.test.ts +51 -0
- package/src/__tests__/targets.test.ts +132 -0
- package/src/args.ts +111 -0
- package/src/cases/__tests__/format.test.ts +76 -0
- package/src/cases/__tests__/runner.test.ts +230 -0
- package/src/cases/fixtures.ts +104 -0
- package/src/cases/format.ts +183 -0
- package/src/cases/runner.ts +575 -0
- package/src/cases/templates.ts +159 -0
- package/src/cli.ts +176 -0
- package/src/commands/cases.ts +170 -0
- package/src/commands/check.ts +83 -0
- package/src/commands/crawl/return.ts +130 -0
- package/src/commands/crawl/state.ts +70 -0
- package/src/commands/crawl.ts +262 -0
- package/src/commands/device.ts +119 -0
- package/src/commands/doctor.ts +237 -0
- package/src/commands/drive.ts +413 -0
- package/src/commands/flake.ts +48 -0
- package/src/commands/headless.ts +131 -0
- package/src/commands/init.ts +98 -0
- package/src/commands/map.ts +298 -0
- package/src/commands/os.ts +195 -0
- package/src/commands/overlay.ts +20 -0
- package/src/commands/record.ts +46 -0
- package/src/commands/runs.ts +475 -0
- package/src/commands/smoke.ts +86 -0
- package/src/commands/snapshot.ts +87 -0
- package/src/config.ts +103 -0
- package/src/device/devices.ts +44 -0
- package/src/device/driver.ts +197 -0
- package/src/device/platform.ts +74 -0
- package/src/files.ts +35 -0
- package/src/flow/artifacts.ts +61 -0
- package/src/flow/runner.ts +182 -0
- package/src/flow/types.ts +55 -0
- package/src/index.ts +18 -0
- package/src/knowledge/__tests__/edges.test.ts +34 -0
- package/src/knowledge/__tests__/events.test.ts +45 -0
- package/src/knowledge/__tests__/extract.test.ts +88 -0
- package/src/knowledge/__tests__/frontier.test.ts +77 -0
- package/src/knowledge/__tests__/funnels.test.ts +58 -0
- package/src/knowledge/__tests__/lint.test.ts +66 -0
- package/src/knowledge/__tests__/perf.test.ts +41 -0
- package/src/knowledge/__tests__/plan.test.ts +52 -0
- package/src/knowledge/__tests__/query.test.ts +74 -0
- package/src/knowledge/__tests__/screens.test.ts +37 -0
- package/src/knowledge/__tests__/why.test.ts +40 -0
- package/src/knowledge/edges.ts +91 -0
- package/src/knowledge/errors.ts +73 -0
- package/src/knowledge/events.ts +60 -0
- package/src/knowledge/extract.ts +379 -0
- package/src/knowledge/frontier.ts +82 -0
- package/src/knowledge/funnels.ts +126 -0
- package/src/knowledge/lint.ts +99 -0
- package/src/knowledge/perf.ts +114 -0
- package/src/knowledge/plan.ts +95 -0
- package/src/knowledge/problems.ts +12 -0
- package/src/knowledge/query.ts +146 -0
- package/src/knowledge/route-params.ts +39 -0
- package/src/knowledge/screens.ts +139 -0
- package/src/knowledge/tree.ts +66 -0
- package/src/knowledge/types.ts +60 -0
- package/src/knowledge/why.ts +189 -0
- package/src/mcp/server.ts +109 -0
- package/src/registry.ts +191 -0
- package/src/runtime.ts +54 -0
- package/src/targets.ts +44 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the outside world hands the app. One vocabulary whether the app is
|
|
3
|
+
* hosted in Node (lynkeus stands in for the OS) or runs on a simulator (simctl
|
|
4
|
+
* and adb do it); each command's summary says which it supports.
|
|
5
|
+
*/
|
|
6
|
+
import { type Ctx, define } from '../registry.js';
|
|
7
|
+
import { simulatorCommand } from './device.js';
|
|
8
|
+
|
|
9
|
+
/** A command the agent answers in place of the OS, with parameters taken from the context. */
|
|
10
|
+
const agent =
|
|
11
|
+
(name: string, params: (ctx: Ctx) => unknown = () => undefined) =>
|
|
12
|
+
async (ctx: Ctx) => ({ json: await (await ctx.device()).command(name, params(ctx)) });
|
|
13
|
+
|
|
14
|
+
const simulator = (sub: string) => async (ctx: Ctx) => {
|
|
15
|
+
const device = await ctx.device();
|
|
16
|
+
const info = await device.resolved();
|
|
17
|
+
const lines = await simulatorCommand(info.platform, info.id, sub, ctx.args, (ctx.flags.app as string | undefined) ?? device.appId ?? undefined);
|
|
18
|
+
return { text: lines.join('\n'), json: { ran: lines } };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const osCommands = [
|
|
22
|
+
define({
|
|
23
|
+
name: 'os deeplink',
|
|
24
|
+
mcp: 'core',
|
|
25
|
+
group: 'os',
|
|
26
|
+
summary: 'Open a URL in the app, as the OS would hand it over',
|
|
27
|
+
positionals: [{ name: 'url', help: 'myapp://pay/abc or https://…', required: true }],
|
|
28
|
+
aliases: ['deeplink'],
|
|
29
|
+
needs: 'app',
|
|
30
|
+
run: async (ctx) => {
|
|
31
|
+
await (await ctx.device()).openUrl(ctx.args[0]!);
|
|
32
|
+
return { json: { opened: ctx.args[0] } };
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
define({
|
|
36
|
+
name: 'os appstate',
|
|
37
|
+
group: 'os',
|
|
38
|
+
summary: 'Headless: AppState listeners see the change (background, then active: what refetches or re-locks)',
|
|
39
|
+
positionals: [{ name: 'state', help: 'active | background | inactive', required: true }],
|
|
40
|
+
aliases: ['appstate'],
|
|
41
|
+
needs: 'app',
|
|
42
|
+
run: agent('appstate', (ctx) => ({ state: ctx.args[0] })),
|
|
43
|
+
}),
|
|
44
|
+
define({
|
|
45
|
+
name: 'os network',
|
|
46
|
+
group: 'os',
|
|
47
|
+
summary: 'Headless: airplane mode (requests fail, open sockets drop, NetInfo listeners hear it)',
|
|
48
|
+
positionals: [{ name: 'state', help: 'off | on', required: true }],
|
|
49
|
+
aliases: ['network'],
|
|
50
|
+
needs: 'app',
|
|
51
|
+
run: agent('network', (ctx) => ({ state: ctx.args[0] })),
|
|
52
|
+
}),
|
|
53
|
+
define({
|
|
54
|
+
name: 'os clock',
|
|
55
|
+
group: 'os',
|
|
56
|
+
summary: 'Headless: what Date answers (timers stay real)',
|
|
57
|
+
flags: {
|
|
58
|
+
now: { type: 'string', help: 'set the time', value: 'iso' },
|
|
59
|
+
advance: { type: 'number', help: 'move the clock forward', value: 'ms' },
|
|
60
|
+
reset: { type: 'boolean', help: 'back to the real clock' },
|
|
61
|
+
},
|
|
62
|
+
aliases: ['clock'],
|
|
63
|
+
needs: 'app',
|
|
64
|
+
run: agent('clock', ({ flags }) => ({ now: flags.now || undefined, advanceMs: flags.advance, reset: flags.reset || undefined })),
|
|
65
|
+
}),
|
|
66
|
+
define({
|
|
67
|
+
name: 'os biometrics',
|
|
68
|
+
group: 'os',
|
|
69
|
+
summary: 'Headless: the sensor and what the next prompts answer',
|
|
70
|
+
flags: {
|
|
71
|
+
available: { type: 'string', help: 'FaceID | TouchID | none', value: 'kind' },
|
|
72
|
+
next: { type: 'string', help: 'outcomes of the next prompts, in order', value: 'success,fail,cancel' },
|
|
73
|
+
fallback: { type: 'string', help: 'what a fallback prompt answers', value: 'outcome' },
|
|
74
|
+
},
|
|
75
|
+
aliases: ['biometrics'],
|
|
76
|
+
needs: 'app',
|
|
77
|
+
run: agent('biometrics', ({ flags }) => ({
|
|
78
|
+
available: flags.available || undefined,
|
|
79
|
+
next: flags.next ? (flags.next as string).split(',') : undefined,
|
|
80
|
+
fallback: flags.fallback || undefined,
|
|
81
|
+
})),
|
|
82
|
+
}),
|
|
83
|
+
define({
|
|
84
|
+
name: 'os scan',
|
|
85
|
+
group: 'os',
|
|
86
|
+
summary: "Headless: the open camera's code scanner receives a value",
|
|
87
|
+
positionals: [{ name: 'value', help: 'what the code carries', required: true }],
|
|
88
|
+
flags: { type: { type: 'string', help: 'code type', default: 'qr', value: 'qr|ean13|…' } },
|
|
89
|
+
aliases: ['scan'],
|
|
90
|
+
needs: 'app',
|
|
91
|
+
run: agent('scan', (ctx) => ({ value: ctx.args[0], type: ctx.flags.type })),
|
|
92
|
+
}),
|
|
93
|
+
define({
|
|
94
|
+
name: 'os gallery',
|
|
95
|
+
group: 'os',
|
|
96
|
+
summary: 'Headless: what the next image picker answers',
|
|
97
|
+
positionals: [{ name: 'uri', help: 'file the picker returns' }],
|
|
98
|
+
flags: {
|
|
99
|
+
cancel: { type: 'boolean', help: 'the user cancelled' },
|
|
100
|
+
name: { type: 'string', help: 'file name reported', value: 'name' },
|
|
101
|
+
mime: { type: 'string', help: 'mime type reported', value: 'type' },
|
|
102
|
+
},
|
|
103
|
+
aliases: ['gallery'],
|
|
104
|
+
needs: 'app',
|
|
105
|
+
run: agent('gallery', ({ args, flags }) => (flags.cancel ? { cancel: true } : { uri: args[0], fileName: flags.name, type: flags.mime })),
|
|
106
|
+
}),
|
|
107
|
+
define({
|
|
108
|
+
name: 'mock',
|
|
109
|
+
group: 'os',
|
|
110
|
+
summary: 'Headless: answer a request before the network (--sticky survives a reset)',
|
|
111
|
+
positionals: [{ name: 'route', help: "'GET /path' (a bare path matches any method)" }],
|
|
112
|
+
flags: {
|
|
113
|
+
status: { type: 'number', help: 'HTTP status to answer', default: 200, value: 'code' },
|
|
114
|
+
body: { type: 'string', help: 'JSON body', value: 'json' },
|
|
115
|
+
delay: { type: 'number', help: 'answer after', value: 'ms' },
|
|
116
|
+
times: { type: 'number', help: 'only this many times', value: 'n' },
|
|
117
|
+
sticky: { type: 'boolean', help: 'keep the rule across an app reset' },
|
|
118
|
+
lost: { type: 'boolean', help: 'let the request reach the server and fail it on the way back' },
|
|
119
|
+
clear: { type: 'boolean', help: 'drop every rule' },
|
|
120
|
+
list: { type: 'boolean', help: 'show the rules' },
|
|
121
|
+
},
|
|
122
|
+
example: "mock 'GET /path' [--status 500] [--body <json>] [--delay <ms>] [--times <n>] [--sticky] [--lost] | --clear | --list",
|
|
123
|
+
needs: 'app',
|
|
124
|
+
run: async (ctx) => {
|
|
125
|
+
const device = await ctx.device();
|
|
126
|
+
if (ctx.flags.clear) return { json: await device.command('netmock', { action: 'clear' }) };
|
|
127
|
+
if (ctx.flags.list) {
|
|
128
|
+
const rules = await device.command('netmock', { action: 'list' });
|
|
129
|
+
return { text: JSON.stringify(rules, null, 2), json: rules };
|
|
130
|
+
}
|
|
131
|
+
if (!ctx.args[0]) throw new Error("mock needs 'METHOD /path' (or --clear, --list)");
|
|
132
|
+
const [method, route] = ctx.args[0].includes(' ') ? ctx.args[0].split(/\s+/, 2) : ['*', ctx.args[0]];
|
|
133
|
+
return {
|
|
134
|
+
json: await device.command('netmock', {
|
|
135
|
+
action: 'add',
|
|
136
|
+
method,
|
|
137
|
+
path: route,
|
|
138
|
+
status: ctx.flags.status,
|
|
139
|
+
body: ctx.flags.body,
|
|
140
|
+
delayMs: ctx.flags.delay,
|
|
141
|
+
times: ctx.flags.times,
|
|
142
|
+
sticky: ctx.flags.sticky || undefined,
|
|
143
|
+
lost: ctx.flags.lost || undefined,
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
define({
|
|
149
|
+
name: 'os permissions',
|
|
150
|
+
group: 'os',
|
|
151
|
+
summary: 'Simulator: grant, revoke or reset a permission for the app',
|
|
152
|
+
positionals: [
|
|
153
|
+
{ name: 'action', help: 'grant | revoke | reset', required: true },
|
|
154
|
+
{ name: 'service', help: 'camera, photos, location, microphone, contacts, …', required: true },
|
|
155
|
+
],
|
|
156
|
+
needs: 'app',
|
|
157
|
+
run: simulator('permissions'),
|
|
158
|
+
}),
|
|
159
|
+
define({
|
|
160
|
+
name: 'os push',
|
|
161
|
+
group: 'os',
|
|
162
|
+
summary: 'Simulator: deliver a push notification to the app',
|
|
163
|
+
positionals: [{ name: 'payload', help: '\'{"alert":"…"}\' or a .json file', required: true }],
|
|
164
|
+
needs: 'app',
|
|
165
|
+
run: simulator('push'),
|
|
166
|
+
}),
|
|
167
|
+
define({
|
|
168
|
+
name: 'os appearance',
|
|
169
|
+
group: 'os',
|
|
170
|
+
summary: 'Simulator: light or dark',
|
|
171
|
+
positionals: [{ name: 'mode', help: 'light | dark', required: true }],
|
|
172
|
+
needs: 'app',
|
|
173
|
+
run: simulator('appearance'),
|
|
174
|
+
}),
|
|
175
|
+
define({
|
|
176
|
+
name: 'os location',
|
|
177
|
+
group: 'os',
|
|
178
|
+
summary: 'Simulator: where the device is',
|
|
179
|
+
positionals: [{ name: 'where', help: '<lat>,<lon> | clear', required: true }],
|
|
180
|
+
needs: 'app',
|
|
181
|
+
run: simulator('location'),
|
|
182
|
+
}),
|
|
183
|
+
define({
|
|
184
|
+
name: 'os language',
|
|
185
|
+
group: 'os',
|
|
186
|
+
summary: 'Simulator: relaunch the app in a locale',
|
|
187
|
+
positionals: [{ name: 'tag', help: 'de-DE, en-US, … (default: the locale in lynkeus.config.json)' }],
|
|
188
|
+
needs: 'app',
|
|
189
|
+
run: async (ctx) => {
|
|
190
|
+
const tag = ctx.args[0] ?? ctx.config.locale?.languageTag ?? ctx.config.language;
|
|
191
|
+
if (!tag) throw new Error('os language needs a tag, or `locale` in lynkeus.config.json');
|
|
192
|
+
return simulator('language')({ ...ctx, args: [tag] });
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
195
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Target } from 'lynkeus-protocol';
|
|
2
|
+
|
|
3
|
+
import type { Device } from '../device/driver.js';
|
|
4
|
+
import { runFlow } from '../flow/runner.js';
|
|
5
|
+
import type { Flow } from '../flow/types.js';
|
|
6
|
+
|
|
7
|
+
/** An overlay the app raises on its own (a lock screen, a rating prompt, a forced-update wall) and the flow that clears it. */
|
|
8
|
+
export type Overlay = { when: Target; flow: Flow };
|
|
9
|
+
|
|
10
|
+
const matches = (e: { testId?: string; text?: string; accessibilityLabel?: string }, t: Target): boolean =>
|
|
11
|
+
('testId' in t && t.testId !== undefined && e.testId === t.testId) ||
|
|
12
|
+
('text' in t && t.text !== undefined && (e.text?.includes(t.text) || e.accessibilityLabel?.includes(t.text)) === true);
|
|
13
|
+
|
|
14
|
+
export const clearOverlayIfShown = async (device: Device, overlay: Overlay | undefined): Promise<boolean> => {
|
|
15
|
+
if (!overlay) return false;
|
|
16
|
+
const screen = await device.screen();
|
|
17
|
+
if (!screen.elements.some((e) => matches(e, overlay.when))) return false;
|
|
18
|
+
await runFlow(overlay.flow, { device, vars: {} });
|
|
19
|
+
return true;
|
|
20
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TraceEvent } from 'lynkeus-protocol';
|
|
2
|
+
|
|
3
|
+
import type { Flow, FlowStep } from '../flow/types.js';
|
|
4
|
+
|
|
5
|
+
import { pathOf } from '../knowledge/problems.js';
|
|
6
|
+
|
|
7
|
+
/** One press per touch and, after each, what it caused: the route it led to, the requests and the stores it changed. */
|
|
8
|
+
export const draftFlow = (events: TraceEvent[], name = 'recorded'): Flow => {
|
|
9
|
+
const steps: FlowStep[] = [];
|
|
10
|
+
let lastRoute: string | undefined;
|
|
11
|
+
let pending: { requests: string[]; stores: string[]; route?: string } | null = null;
|
|
12
|
+
const flush = () => {
|
|
13
|
+
if (!pending) return;
|
|
14
|
+
if (pending.route) steps.push({ waitFor: { route: pending.route, timeoutMs: 10000 } });
|
|
15
|
+
if (pending.requests.length) steps.push({ note: `requests: ${[...new Set(pending.requests)].join(', ')}` });
|
|
16
|
+
if (pending.stores.length) steps.push({ note: `stores changed: ${[...new Set(pending.stores)].join(', ')}` });
|
|
17
|
+
pending = null;
|
|
18
|
+
};
|
|
19
|
+
for (const e of events) {
|
|
20
|
+
if (e.kind === 'touch') {
|
|
21
|
+
flush();
|
|
22
|
+
if (e.testId) steps.push({ press: { testId: e.testId } });
|
|
23
|
+
else if (e.text) steps.push({ press: { text: e.text } });
|
|
24
|
+
else steps.push({ note: `touch at ${e.x},${e.y} on ${e.route ?? '?'} hit nothing lynkeus could name` });
|
|
25
|
+
pending = { requests: [], stores: [] };
|
|
26
|
+
lastRoute = e.route ?? lastRoute;
|
|
27
|
+
} else if (e.kind === 'route') {
|
|
28
|
+
if (pending && e.route && e.route !== lastRoute) pending.route = e.route;
|
|
29
|
+
lastRoute = e.route ?? lastRoute;
|
|
30
|
+
} else if (e.kind === 'request' && pending) {
|
|
31
|
+
pending.requests.push(`${e.method} ${pathOf(e.url)}${e.status ? ` ${e.status}` : ''}`);
|
|
32
|
+
} else if (e.kind === 'store' && pending) {
|
|
33
|
+
pending.stores.push(`${e.store}.${Object.keys(e.changed).join(',')}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
flush();
|
|
37
|
+
return { name, steps };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const describeEvent = (e: TraceEvent): string | undefined => {
|
|
41
|
+
if (e.kind === 'touch') return `touch ${e.testId ? `#${e.testId}` : e.text ? JSON.stringify(e.text) : `${e.x},${e.y}`} on ${e.route ?? '?'}`;
|
|
42
|
+
if (e.kind === 'route') return `route ${e.route ?? '?'}`;
|
|
43
|
+
if (e.kind === 'request') return `req ${e.status ?? '…'} ${e.method} ${pathOf(e.url)}${e.ms ? ` ${e.ms}ms` : ''}`;
|
|
44
|
+
if (e.kind === 'store') return `store ${e.store}: ${Object.keys(e.changed).join(', ')}`;
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|