@taqwright/taqwright 0.0.24

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 (132) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +108 -0
  3. package/dist/auto-appium.d.ts +12 -0
  4. package/dist/auto-appium.js +77 -0
  5. package/dist/bin/branding.d.ts +6 -0
  6. package/dist/bin/branding.js +22 -0
  7. package/dist/bin/index.d.ts +2 -0
  8. package/dist/bin/index.js +321 -0
  9. package/dist/bin/init.d.ts +26 -0
  10. package/dist/bin/init.js +902 -0
  11. package/dist/bin/inspect.d.ts +9 -0
  12. package/dist/bin/inspect.js +91 -0
  13. package/dist/bin/report-branding.d.ts +2 -0
  14. package/dist/bin/report-branding.js +42 -0
  15. package/dist/branding-assets.d.ts +1 -0
  16. package/dist/branding-assets.js +1 -0
  17. package/dist/capabilities-helpers.d.ts +7 -0
  18. package/dist/capabilities-helpers.js +14 -0
  19. package/dist/capabilities.d.ts +6 -0
  20. package/dist/capabilities.js +86 -0
  21. package/dist/config.d.ts +17 -0
  22. package/dist/config.js +235 -0
  23. package/dist/discovery-setup.d.ts +1 -0
  24. package/dist/discovery-setup.js +61 -0
  25. package/dist/discovery.d.ts +17 -0
  26. package/dist/discovery.js +55 -0
  27. package/dist/docs/configuration.html +376 -0
  28. package/dist/docs/custom-reporters.html +265 -0
  29. package/dist/docs/docker.html +339 -0
  30. package/dist/docs/docs.js +173 -0
  31. package/dist/docs/generating-tests.html +161 -0
  32. package/dist/docs/images/taqwright-html-report.png +0 -0
  33. package/dist/docs/index.html +13 -0
  34. package/dist/docs/installation.html +686 -0
  35. package/dist/docs/parallel.html +271 -0
  36. package/dist/docs/running-tests.html +385 -0
  37. package/dist/docs/styles.css +460 -0
  38. package/dist/docs/writing-tests.html +565 -0
  39. package/dist/doctor.d.ts +33 -0
  40. package/dist/doctor.js +508 -0
  41. package/dist/expect.d.ts +38 -0
  42. package/dist/expect.js +96 -0
  43. package/dist/fixture/artifact-mode.d.ts +2 -0
  44. package/dist/fixture/artifact-mode.js +7 -0
  45. package/dist/fixture/index.d.ts +15 -0
  46. package/dist/fixture/index.js +324 -0
  47. package/dist/images/taqwright-html-report.png +0 -0
  48. package/dist/images/taqwright_favicon.png +0 -0
  49. package/dist/images/taqwright_logo.png +0 -0
  50. package/dist/index.d.ts +9 -0
  51. package/dist/index.js +7 -0
  52. package/dist/inspector/codegen-appium.d.ts +3 -0
  53. package/dist/inspector/codegen-appium.js +228 -0
  54. package/dist/inspector/devices.d.ts +41 -0
  55. package/dist/inspector/devices.js +422 -0
  56. package/dist/inspector/locator-suggester.d.ts +23 -0
  57. package/dist/inspector/locator-suggester.js +539 -0
  58. package/dist/inspector/recorder.d.ts +128 -0
  59. package/dist/inspector/recorder.js +162 -0
  60. package/dist/inspector/server.d.ts +39 -0
  61. package/dist/inspector/server.js +1210 -0
  62. package/dist/inspector/session.d.ts +84 -0
  63. package/dist/inspector/session.js +262 -0
  64. package/dist/inspector/ui.d.ts +1 -0
  65. package/dist/inspector/ui.js +5508 -0
  66. package/dist/keys.d.ts +3 -0
  67. package/dist/keys.js +28 -0
  68. package/dist/locator/index.d.ts +206 -0
  69. package/dist/locator/index.js +1506 -0
  70. package/dist/logger.d.ts +5 -0
  71. package/dist/logger.js +5 -0
  72. package/dist/mobile/index.d.ts +130 -0
  73. package/dist/mobile/index.js +762 -0
  74. package/dist/network/android.d.ts +5 -0
  75. package/dist/network/android.js +87 -0
  76. package/dist/network/ca.d.ts +10 -0
  77. package/dist/network/ca.js +136 -0
  78. package/dist/network/har.d.ts +90 -0
  79. package/dist/network/har.js +101 -0
  80. package/dist/network/host-proxy.d.ts +16 -0
  81. package/dist/network/host-proxy.js +134 -0
  82. package/dist/network/index.d.ts +26 -0
  83. package/dist/network/index.js +105 -0
  84. package/dist/network/ios-sim.d.ts +3 -0
  85. package/dist/network/ios-sim.js +29 -0
  86. package/dist/network/proxy.d.ts +13 -0
  87. package/dist/network/proxy.js +310 -0
  88. package/dist/providers/appium.d.ts +23 -0
  89. package/dist/providers/appium.js +288 -0
  90. package/dist/providers/browserstack/index.d.ts +5 -0
  91. package/dist/providers/browserstack/index.js +77 -0
  92. package/dist/providers/browserstack/utils.d.ts +1 -0
  93. package/dist/providers/browserstack/utils.js +6 -0
  94. package/dist/providers/cloud.d.ts +53 -0
  95. package/dist/providers/cloud.js +117 -0
  96. package/dist/providers/emulator/index.d.ts +8 -0
  97. package/dist/providers/emulator/index.js +47 -0
  98. package/dist/providers/index.d.ts +10 -0
  99. package/dist/providers/index.js +33 -0
  100. package/dist/providers/lambdatest/index.d.ts +28 -0
  101. package/dist/providers/lambdatest/index.js +99 -0
  102. package/dist/providers/lambdatest/utils.d.ts +1 -0
  103. package/dist/providers/lambdatest/utils.js +6 -0
  104. package/dist/providers/local/index.d.ts +9 -0
  105. package/dist/providers/local/index.js +53 -0
  106. package/dist/providers/local-session.d.ts +16 -0
  107. package/dist/providers/local-session.js +55 -0
  108. package/dist/setup/archive.d.ts +2 -0
  109. package/dist/setup/archive.js +43 -0
  110. package/dist/setup/avd.d.ts +12 -0
  111. package/dist/setup/avd.js +103 -0
  112. package/dist/setup/index.d.ts +6 -0
  113. package/dist/setup/index.js +55 -0
  114. package/dist/setup/install-android.d.ts +2 -0
  115. package/dist/setup/install-android.js +70 -0
  116. package/dist/setup/install-appium.d.ts +1 -0
  117. package/dist/setup/install-appium.js +64 -0
  118. package/dist/setup/install-jdk.d.ts +1 -0
  119. package/dist/setup/install-jdk.js +58 -0
  120. package/dist/setup/paths.d.ts +16 -0
  121. package/dist/setup/paths.js +88 -0
  122. package/dist/setup/spawn-tool.d.ts +3 -0
  123. package/dist/setup/spawn-tool.js +11 -0
  124. package/dist/tracer/index.d.ts +34 -0
  125. package/dist/tracer/index.js +687 -0
  126. package/dist/tracer/proxy.d.ts +3 -0
  127. package/dist/tracer/proxy.js +60 -0
  128. package/dist/types/index.d.ts +189 -0
  129. package/dist/types/index.js +6 -0
  130. package/dist/utils.d.ts +2 -0
  131. package/dist/utils.js +37 -0
  132. package/package.json +79 -0
@@ -0,0 +1,539 @@
1
+ import { Platform } from '../types/index.js';
2
+ export const CATEGORY_ORDER = {
3
+ [Platform.ANDROID]: ['id', 'uiautomator', 'xpath'],
4
+ [Platform.IOS]: ['id', 'predicate', 'classChain', 'xpath'],
5
+ };
6
+ export const WEB_CATEGORY_ORDER = ['css', 'xpath'];
7
+ export function generateCandidates(platform, attrs, _xpath, isWeb = false) {
8
+ if (isWeb) {
9
+ return generateWebCandidates(attrs, _xpath);
10
+ }
11
+ const out = [];
12
+ if (platform === Platform.ANDROID) {
13
+ const resourceId = attrs['resource-id'];
14
+ const contentDesc = attrs['content-desc'];
15
+ const text = attrs['text'];
16
+ const hint = attrs['hint'];
17
+ const cls = attrs['class'];
18
+ const stableAttrs = [];
19
+ if (resourceId)
20
+ stableAttrs.push({ key: 'resource-id', val: resourceId, priority: 700 });
21
+ if (contentDesc)
22
+ stableAttrs.push({ key: 'content-desc', val: contentDesc, priority: 680 });
23
+ if (text)
24
+ stableAttrs.push({ key: 'text', val: text, priority: 660 });
25
+ if (hint)
26
+ stableAttrs.push({ key: 'hint', val: hint, priority: 640 });
27
+ if (resourceId) {
28
+ const shortId = resourceId.includes(':id/') ? resourceId.split(':id/')[1] : resourceId;
29
+ out.push({
30
+ category: 'id',
31
+ subLabel: 'resource-id',
32
+ priority: 1000,
33
+ code: `mobile.getById(${jsString(shortId)})`,
34
+ using: 'id',
35
+ value: resourceId,
36
+ });
37
+ }
38
+ if (resourceId) {
39
+ const ua = `new UiSelector().resourceId(${jsString(resourceId)})`;
40
+ out.push({
41
+ category: 'uiautomator',
42
+ subLabel: 'resourceId',
43
+ priority: 950,
44
+ code: `mobile.getByUiSelector(${jsString(ua)})`,
45
+ using: '-android uiautomator',
46
+ value: ua,
47
+ });
48
+ }
49
+ if (contentDesc) {
50
+ const ua = `new UiSelector().description(${jsString(contentDesc)})`;
51
+ out.push({
52
+ category: 'uiautomator',
53
+ subLabel: 'content-desc',
54
+ priority: 920,
55
+ code: `mobile.getByUiSelector(${jsString(ua)})`,
56
+ using: '-android uiautomator',
57
+ value: ua,
58
+ });
59
+ const cdLine = contentDesc.includes('\n') ? longestLine(contentDesc) : undefined;
60
+ if (cdLine) {
61
+ const uac = `new UiSelector().descriptionContains(${jsString(cdLine)})`;
62
+ out.push({
63
+ category: 'uiautomator',
64
+ subLabel: 'content-desc contains',
65
+ priority: 915,
66
+ code: `mobile.getByUiSelector(${jsString(uac)})`,
67
+ using: '-android uiautomator',
68
+ value: uac,
69
+ });
70
+ }
71
+ }
72
+ if (cls && contentDesc) {
73
+ const ua = `new UiSelector().className(${jsString(cls)}).description(${jsString(contentDesc)})`;
74
+ out.push({
75
+ category: 'uiautomator',
76
+ subLabel: 'class + content-desc',
77
+ priority: 910,
78
+ code: `mobile.getByUiSelector(${jsString(ua)})`,
79
+ using: '-android uiautomator',
80
+ value: ua,
81
+ });
82
+ }
83
+ if (text) {
84
+ const ua = `new UiSelector().text(${jsString(text)})`;
85
+ out.push({
86
+ category: 'uiautomator',
87
+ subLabel: 'text',
88
+ priority: 900,
89
+ code: `mobile.getByUiSelector(${jsString(ua)})`,
90
+ using: '-android uiautomator',
91
+ value: ua,
92
+ });
93
+ const txtLine = text.includes('\n') ? longestLine(text) : undefined;
94
+ if (txtLine) {
95
+ const uac = `new UiSelector().textContains(${jsString(txtLine)})`;
96
+ out.push({
97
+ category: 'uiautomator',
98
+ subLabel: 'text contains',
99
+ priority: 895,
100
+ code: `mobile.getByUiSelector(${jsString(uac)})`,
101
+ using: '-android uiautomator',
102
+ value: uac,
103
+ });
104
+ }
105
+ }
106
+ if (cls && text) {
107
+ const ua = `new UiSelector().className(${jsString(cls)}).text(${jsString(text)})`;
108
+ out.push({
109
+ category: 'uiautomator',
110
+ subLabel: 'class + text',
111
+ priority: 890,
112
+ code: `mobile.getByUiSelector(${jsString(ua)})`,
113
+ using: '-android uiautomator',
114
+ value: ua,
115
+ });
116
+ }
117
+ if (cls) {
118
+ const ua = `new UiSelector().className(${jsString(cls)})`;
119
+ out.push({
120
+ category: 'uiautomator',
121
+ subLabel: 'class',
122
+ priority: 700,
123
+ code: `mobile.getByUiSelector(${jsString(ua)})`,
124
+ using: '-android uiautomator',
125
+ value: ua,
126
+ });
127
+ }
128
+ for (const a of stableAttrs) {
129
+ const xp = `//*[${xpathAttrEq(a.key, a.val)}]`;
130
+ out.push({
131
+ category: 'xpath',
132
+ subLabel: a.key,
133
+ priority: a.priority,
134
+ code: `mobile.getByXpath(${jsString(xp)})`,
135
+ using: 'xpath',
136
+ value: xp,
137
+ });
138
+ }
139
+ if (cls) {
140
+ const shortCls = shortClassLabel(cls);
141
+ for (const a of stableAttrs) {
142
+ const xp = `//${cls}[${xpathAttrEq(a.key, a.val)}]`;
143
+ out.push({
144
+ category: 'xpath',
145
+ subLabel: `${shortCls} + ${a.key}`,
146
+ priority: a.priority - 50,
147
+ code: `mobile.getByXpath(${jsString(xp)})`,
148
+ using: 'xpath',
149
+ value: xp,
150
+ });
151
+ }
152
+ }
153
+ if (cls && stableAttrs.length >= 2) {
154
+ const shortCls = shortClassLabel(cls);
155
+ for (let i = 0; i < stableAttrs.length; i++) {
156
+ for (let j = i + 1; j < stableAttrs.length; j++) {
157
+ const a = stableAttrs[i];
158
+ const b = stableAttrs[j];
159
+ const xp = `//${cls}[${xpathAttrEq(a.key, a.val)}]` + `[${xpathAttrEq(b.key, b.val)}]`;
160
+ out.push({
161
+ category: 'xpath',
162
+ subLabel: `${shortCls} + ${a.key} + ${b.key}`,
163
+ priority: 450,
164
+ code: `mobile.getByXpath(${jsString(xp)})`,
165
+ using: 'xpath',
166
+ value: xp,
167
+ });
168
+ }
169
+ }
170
+ }
171
+ }
172
+ else {
173
+ const name = attrs['name'];
174
+ const label = attrs['label'];
175
+ const value = attrs['value'];
176
+ const placeholder = attrs['placeholderValue'];
177
+ const type = attrs['type'];
178
+ const stableAttrs = [];
179
+ if (name)
180
+ stableAttrs.push({ key: 'name', val: name, priority: 500 });
181
+ if (label)
182
+ stableAttrs.push({ key: 'label', val: label, priority: 480 });
183
+ if (value)
184
+ stableAttrs.push({ key: 'value', val: value, priority: 460 });
185
+ if (placeholder)
186
+ stableAttrs.push({ key: 'placeholderValue', val: placeholder, priority: 470 });
187
+ if (name) {
188
+ out.push({
189
+ category: 'id',
190
+ subLabel: 'accessibility id',
191
+ priority: 1000,
192
+ code: `mobile.getById(${jsString(name)})`,
193
+ using: 'accessibility id',
194
+ value: name,
195
+ });
196
+ }
197
+ if (name) {
198
+ const expr = `name == ${nsString(name)}`;
199
+ out.push({
200
+ category: 'predicate',
201
+ subLabel: 'name',
202
+ priority: 950,
203
+ code: `mobile.getByPredicate(${jsString(expr)})`,
204
+ using: '-ios predicate string',
205
+ value: expr,
206
+ });
207
+ }
208
+ if (label) {
209
+ const expr = `label == ${nsString(label)}`;
210
+ out.push({
211
+ category: 'predicate',
212
+ subLabel: 'label',
213
+ priority: 920,
214
+ code: `mobile.getByPredicate(${jsString(expr)})`,
215
+ using: '-ios predicate string',
216
+ value: expr,
217
+ });
218
+ }
219
+ if (value) {
220
+ const expr = `value == ${nsString(value)}`;
221
+ out.push({
222
+ category: 'predicate',
223
+ subLabel: 'value',
224
+ priority: 900,
225
+ code: `mobile.getByPredicate(${jsString(expr)})`,
226
+ using: '-ios predicate string',
227
+ value: expr,
228
+ });
229
+ }
230
+ if (placeholder) {
231
+ const expr = `placeholderValue == ${nsString(placeholder)}`;
232
+ out.push({
233
+ category: 'predicate',
234
+ subLabel: 'placeholderValue',
235
+ priority: 890,
236
+ code: `mobile.getByPredicate(${jsString(expr)})`,
237
+ using: '-ios predicate string',
238
+ value: expr,
239
+ });
240
+ }
241
+ if (type && name) {
242
+ const expr = `type == ${nsString(type)} AND name == ${nsString(name)}`;
243
+ out.push({
244
+ category: 'predicate',
245
+ subLabel: 'type + name',
246
+ priority: 880,
247
+ code: `mobile.getByPredicate(${jsString(expr)})`,
248
+ using: '-ios predicate string',
249
+ value: expr,
250
+ });
251
+ }
252
+ if (type && label) {
253
+ const expr = `type == ${nsString(type)} AND label == ${nsString(label)}`;
254
+ out.push({
255
+ category: 'predicate',
256
+ subLabel: 'type + label',
257
+ priority: 870,
258
+ code: `mobile.getByPredicate(${jsString(expr)})`,
259
+ using: '-ios predicate string',
260
+ value: expr,
261
+ });
262
+ }
263
+ if (type && placeholder) {
264
+ const expr = `type == ${nsString(type)} AND placeholderValue == ${nsString(placeholder)}`;
265
+ out.push({
266
+ category: 'predicate',
267
+ subLabel: 'type + placeholderValue',
268
+ priority: 860,
269
+ code: `mobile.getByPredicate(${jsString(expr)})`,
270
+ using: '-ios predicate string',
271
+ value: expr,
272
+ });
273
+ }
274
+ if (type && name) {
275
+ const chain = `**/${type}[\`name == ${classChainQuote(name)}\`]`;
276
+ out.push({
277
+ category: 'classChain',
278
+ subLabel: 'type + name',
279
+ priority: 800,
280
+ code: `mobile.getByClassChain(${jsString(chain)})`,
281
+ using: '-ios class chain',
282
+ value: chain,
283
+ });
284
+ }
285
+ if (type && label) {
286
+ const chain = `**/${type}[\`label == ${classChainQuote(label)}\`]`;
287
+ out.push({
288
+ category: 'classChain',
289
+ subLabel: 'type + label',
290
+ priority: 780,
291
+ code: `mobile.getByClassChain(${jsString(chain)})`,
292
+ using: '-ios class chain',
293
+ value: chain,
294
+ });
295
+ }
296
+ if (type && value) {
297
+ const chain = `**/${type}[\`value == ${classChainQuote(value)}\`]`;
298
+ out.push({
299
+ category: 'classChain',
300
+ subLabel: 'type + value',
301
+ priority: 770,
302
+ code: `mobile.getByClassChain(${jsString(chain)})`,
303
+ using: '-ios class chain',
304
+ value: chain,
305
+ });
306
+ }
307
+ if (type && placeholder) {
308
+ const chain = `**/${type}[\`placeholderValue == ${classChainQuote(placeholder)}\`]`;
309
+ out.push({
310
+ category: 'classChain',
311
+ subLabel: 'type + placeholderValue',
312
+ priority: 760,
313
+ code: `mobile.getByClassChain(${jsString(chain)})`,
314
+ using: '-ios class chain',
315
+ value: chain,
316
+ });
317
+ }
318
+ if (type) {
319
+ const chain = `**/${type}`;
320
+ out.push({
321
+ category: 'classChain',
322
+ subLabel: 'type only',
323
+ priority: 600,
324
+ code: `mobile.getByClassChain(${jsString(chain)})`,
325
+ using: '-ios class chain',
326
+ value: chain,
327
+ });
328
+ }
329
+ for (const a of stableAttrs) {
330
+ const xp = `//*[${xpathAttrEq(a.key, a.val)}]`;
331
+ out.push({
332
+ category: 'xpath',
333
+ subLabel: a.key,
334
+ priority: a.priority,
335
+ code: `mobile.getByXpath(${jsString(xp)})`,
336
+ using: 'xpath',
337
+ value: xp,
338
+ });
339
+ }
340
+ if (type) {
341
+ const shortType = type.replace(/^XCUIElementType/, '');
342
+ for (const a of stableAttrs) {
343
+ const xp = `//${type}[${xpathAttrEq(a.key, a.val)}]`;
344
+ out.push({
345
+ category: 'xpath',
346
+ subLabel: `${shortType} + ${a.key}`,
347
+ priority: a.priority - 50,
348
+ code: `mobile.getByXpath(${jsString(xp)})`,
349
+ using: 'xpath',
350
+ value: xp,
351
+ });
352
+ }
353
+ }
354
+ if (type && stableAttrs.length >= 2) {
355
+ const shortType = type.replace(/^XCUIElementType/, '');
356
+ for (let i = 0; i < stableAttrs.length; i++) {
357
+ for (let j = i + 1; j < stableAttrs.length; j++) {
358
+ const a = stableAttrs[i];
359
+ const b = stableAttrs[j];
360
+ const xp = `//${type}[${xpathAttrEq(a.key, a.val)}]` + `[${xpathAttrEq(b.key, b.val)}]`;
361
+ out.push({
362
+ category: 'xpath',
363
+ subLabel: `${shortType} + ${a.key} + ${b.key}`,
364
+ priority: 350,
365
+ code: `mobile.getByXpath(${jsString(xp)})`,
366
+ using: 'xpath',
367
+ value: xp,
368
+ });
369
+ }
370
+ }
371
+ }
372
+ }
373
+ return out;
374
+ }
375
+ function generateWebCandidates(attrs, xpath) {
376
+ const out = [];
377
+ const tag = (attrs['__tag'] ?? '').toLowerCase();
378
+ const pushCss = (value, subLabel, priority) => {
379
+ out.push({
380
+ category: 'css',
381
+ subLabel,
382
+ priority,
383
+ code: `mobile.getByCss(${jsString(value)})`,
384
+ using: 'css selector',
385
+ value,
386
+ });
387
+ };
388
+ const testKey = attrs['data-testid'] !== undefined
389
+ ? 'data-testid'
390
+ : attrs['data-test-id'] !== undefined
391
+ ? 'data-test-id'
392
+ : attrs['data-test'] !== undefined
393
+ ? 'data-test'
394
+ : undefined;
395
+ if (testKey) {
396
+ pushCss(`[${testKey}="${cssAttrValue(attrs[testKey])}"]`, testKey, 1000);
397
+ }
398
+ const id = attrs['id'];
399
+ if (id) {
400
+ if (/^[A-Za-z][\w-]*$/.test(id))
401
+ pushCss('#' + id, 'id', 950);
402
+ else
403
+ pushCss(`[id="${cssAttrValue(id)}"]`, 'id', 950);
404
+ }
405
+ const name = attrs['name'];
406
+ if (name)
407
+ pushCss(`[name="${cssAttrValue(name)}"]`, 'name', 900);
408
+ const cls = attrs['class'];
409
+ if (cls) {
410
+ const classes = cls.split(/\s+/).filter((c) => /^[A-Za-z_-][\w-]*$/.test(c));
411
+ if (classes.length) {
412
+ pushCss((tag || '') + '.' + classes.join('.'), tag ? 'tag + class' : 'class', 700);
413
+ }
414
+ }
415
+ if (xpath) {
416
+ const webXpath = xpath.replace(/(^|\/)([A-Za-z][A-Za-z0-9]*)/g, (_m, sep, name) => sep + name.toLowerCase());
417
+ out.push({
418
+ category: 'xpath',
419
+ subLabel: 'position',
420
+ priority: 100,
421
+ code: `mobile.getByXpath(${jsString(webXpath)})`,
422
+ using: 'xpath',
423
+ value: webXpath,
424
+ });
425
+ }
426
+ return out;
427
+ }
428
+ export function selectBestPerCategory(platform, suggestions, isWeb = false) {
429
+ const order = isWeb ? WEB_CATEGORY_ORDER : CATEGORY_ORDER[platform];
430
+ const byCategory = new Map();
431
+ for (const s of suggestions) {
432
+ if (!byCategory.has(s.category))
433
+ byCategory.set(s.category, []);
434
+ byCategory.get(s.category).push(s);
435
+ }
436
+ const out = [];
437
+ for (const cat of order) {
438
+ const list = byCategory.get(cat);
439
+ if (!list || list.length === 0)
440
+ continue;
441
+ list.sort((a, b) => b.priority - a.priority);
442
+ const unique = list.find((s) => s.unique);
443
+ out.push(unique ?? list[0]);
444
+ }
445
+ return out;
446
+ }
447
+ export function isPositional(s) {
448
+ return s.descriptor?.kind === 'nth';
449
+ }
450
+ export function pickRecommended(platform, verified, isWeb = false) {
451
+ const order = isWeb ? WEB_CATEGORY_ORDER : CATEGORY_ORDER[platform];
452
+ const rank = (s) => {
453
+ const ci = order.indexOf(s.category);
454
+ return ci === -1 ? order.length : ci;
455
+ };
456
+ const better = (a, b) => {
457
+ if (a.priority !== b.priority)
458
+ return a.priority > b.priority;
459
+ return rank(a) < rank(b);
460
+ };
461
+ const unique = verified.filter((s) => s.unique);
462
+ if (unique.length === 0)
463
+ return undefined;
464
+ const robust = unique.filter((s) => !isPositional(s));
465
+ const pool = robust.length > 0 ? robust : unique;
466
+ let best;
467
+ for (const s of pool)
468
+ if (!best || better(s, best))
469
+ best = s;
470
+ return best;
471
+ }
472
+ function shortClassLabel(cls) {
473
+ if (cls.startsWith('android.widget.'))
474
+ return cls.slice('android.widget.'.length);
475
+ if (cls.startsWith('android.view.'))
476
+ return cls.slice('android.view.'.length);
477
+ return cls.split('.').pop() || cls;
478
+ }
479
+ function jsString(s) {
480
+ return JSON.stringify(s);
481
+ }
482
+ function cssAttrValue(s) {
483
+ return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
484
+ }
485
+ function xpathLiteral(s) {
486
+ if (!s.includes("'"))
487
+ return `'${s}'`;
488
+ if (!s.includes('"'))
489
+ return `"${s}"`;
490
+ const parts = s.split("'");
491
+ return `concat(${parts.map((p, i) => (i === 0 ? `'${p}'` : `"'", '${p}'`)).join(', ')})`;
492
+ }
493
+ function nonEmptyLines(val) {
494
+ return val
495
+ .split('\n')
496
+ .map((l) => l.trim())
497
+ .filter((l) => l.length > 0);
498
+ }
499
+ function longestLine(val) {
500
+ const lines = nonEmptyLines(val);
501
+ if (lines.length === 0)
502
+ return undefined;
503
+ return lines.reduce((a, b) => (b.length > a.length ? b : a));
504
+ }
505
+ function xpathAttrEq(key, val) {
506
+ if (val.includes('\n')) {
507
+ const lines = nonEmptyLines(val);
508
+ if (lines.length > 0) {
509
+ return lines.map((l) => `contains(@${key}, ${xpathLiteral(l)})`).join(' and ');
510
+ }
511
+ }
512
+ return `@${key}=${xpathLiteral(val)}`;
513
+ }
514
+ function nsString(s) {
515
+ return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
516
+ }
517
+ function classChainQuote(s) {
518
+ return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
519
+ }
520
+ export function makeNthSuggestion(base, idx) {
521
+ const descriptor = {
522
+ kind: 'nth',
523
+ on: {
524
+ kind: 'leaf',
525
+ using: base.using,
526
+ value: base.value,
527
+ },
528
+ n: idx,
529
+ };
530
+ return {
531
+ ...base,
532
+ code: `${base.code}.nth(${idx})`,
533
+ subLabel: `${base.subLabel} + nth(${idx})`,
534
+ priority: base.priority - 100,
535
+ descriptor,
536
+ count: 1,
537
+ unique: true,
538
+ };
539
+ }
@@ -0,0 +1,128 @@
1
+ import type { LocatorDescriptor } from '../types/index.js';
2
+ export type SwipeDirection = 'up' | 'down' | 'left' | 'right';
3
+ export interface RecordedLocator {
4
+ code: string;
5
+ using?: string;
6
+ value?: string;
7
+ descriptor?: LocatorDescriptor;
8
+ }
9
+ export type RecordedAction = {
10
+ kind: 'tap';
11
+ x: number;
12
+ y: number;
13
+ } | {
14
+ kind: 'swipe';
15
+ x1: number;
16
+ y1: number;
17
+ x2: number;
18
+ y2: number;
19
+ durationMs: number;
20
+ } | {
21
+ kind: 'screenScroll';
22
+ direction: SwipeDirection;
23
+ fromX?: number;
24
+ toX?: number;
25
+ fromY?: number;
26
+ toY?: number;
27
+ } | ({
28
+ kind: 'locatorClick';
29
+ } & RecordedLocator) | ({
30
+ kind: 'locatorDoubleTap';
31
+ } & RecordedLocator) | ({
32
+ kind: 'locatorLongPress';
33
+ } & RecordedLocator) | ({
34
+ kind: 'locatorFill';
35
+ text: string;
36
+ } & RecordedLocator) | ({
37
+ kind: 'locatorClear';
38
+ } & RecordedLocator) | ({
39
+ kind: 'assertVisible';
40
+ } & RecordedLocator) | ({
41
+ kind: 'assertHidden';
42
+ } & RecordedLocator) | ({
43
+ kind: 'assertEnabled';
44
+ } & RecordedLocator) | ({
45
+ kind: 'assertDisabled';
46
+ } & RecordedLocator) | ({
47
+ kind: 'assertText';
48
+ expected: string;
49
+ mode: 'exact' | 'contains';
50
+ } & RecordedLocator) | ({
51
+ kind: 'assertValue';
52
+ expected: string;
53
+ } & RecordedLocator) | ({
54
+ kind: 'locatorSwipe';
55
+ direction: SwipeDirection;
56
+ } & RecordedLocator) | ({
57
+ kind: 'locatorScrollIntoView';
58
+ } & RecordedLocator) | ({
59
+ kind: 'locatorPinch';
60
+ direction: 'in' | 'out';
61
+ } & RecordedLocator) | ({
62
+ kind: 'locatorDragTo';
63
+ targetCode: string;
64
+ target?: RecordedLocator;
65
+ } & RecordedLocator) | ({
66
+ kind: 'locatorCheck';
67
+ } & RecordedLocator) | ({
68
+ kind: 'locatorUncheck';
69
+ } & RecordedLocator) | ({
70
+ kind: 'locatorFocus';
71
+ } & RecordedLocator) | ({
72
+ kind: 'locatorBlur';
73
+ } & RecordedLocator) | ({
74
+ kind: 'locatorPress';
75
+ key: string;
76
+ } & RecordedLocator) | ({
77
+ kind: 'locatorPressSequentially';
78
+ text: string;
79
+ delay?: number;
80
+ } & RecordedLocator) | ({
81
+ kind: 'locatorSelectOption';
82
+ value: SelectOptionInput | string;
83
+ } & RecordedLocator) | ({
84
+ kind: 'assertChecked';
85
+ } & RecordedLocator) | ({
86
+ kind: 'assertUnchecked';
87
+ } & RecordedLocator) | ({
88
+ kind: 'assertEditable';
89
+ } & RecordedLocator) | ({
90
+ kind: 'assertReadonly';
91
+ } & RecordedLocator) | ({
92
+ kind: 'assertFocused';
93
+ } & RecordedLocator) | ({
94
+ kind: 'assertAttached';
95
+ } & RecordedLocator) | ({
96
+ kind: 'assertEmpty';
97
+ } & RecordedLocator) | ({
98
+ kind: 'assertInViewport';
99
+ } & RecordedLocator) | ({
100
+ kind: 'assertCount';
101
+ expected: number;
102
+ } & RecordedLocator) | ({
103
+ kind: 'assertAttribute';
104
+ name: string;
105
+ expected: string;
106
+ } & RecordedLocator) | {
107
+ kind: 'sendKeys';
108
+ text: string;
109
+ } | {
110
+ kind: 'switchContext';
111
+ context: string;
112
+ } | {
113
+ kind: 'comment';
114
+ text: string;
115
+ };
116
+ export interface SelectOptionInput {
117
+ label?: string;
118
+ index?: number;
119
+ date?: string;
120
+ time?: string;
121
+ }
122
+ export declare class Recorder {
123
+ private readonly actions;
124
+ push(a: RecordedAction): void;
125
+ list(): RecordedAction[];
126
+ clear(): void;
127
+ toSpec(testName?: string): string;
128
+ }