appium-session-recorder 0.0.1 → 0.0.3

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 (74) hide show
  1. package/README.md +34 -16
  2. package/dist/index.js +32422 -0
  3. package/dist/ui/assets/index-CUcJNRfB.css +1 -0
  4. package/dist/ui/assets/index-Cl_X3tPj.js +4 -0
  5. package/{src → dist}/ui/index.html +2 -1
  6. package/package.json +10 -3
  7. package/bun.lock +0 -731
  8. package/src/cli/arg-parser.ts +0 -311
  9. package/src/cli/commands/drive.ts +0 -147
  10. package/src/cli/commands/index.ts +0 -54
  11. package/src/cli/commands/proxy.ts +0 -41
  12. package/src/cli/commands/screen.ts +0 -73
  13. package/src/cli/commands/selectors.ts +0 -42
  14. package/src/cli/commands/session.ts +0 -64
  15. package/src/cli/commands/types.ts +0 -11
  16. package/src/cli/index.ts +0 -158
  17. package/src/cli/prompts.ts +0 -64
  18. package/src/cli/response.ts +0 -44
  19. package/src/core/appium/client.ts +0 -248
  20. package/src/core/index.ts +0 -5
  21. package/src/core/selectors/generate-candidates.ts +0 -155
  22. package/src/core/selectors/score-candidates.ts +0 -184
  23. package/src/core/types.ts +0 -79
  24. package/src/core/xml/parse-source.ts +0 -197
  25. package/src/index.ts +0 -7
  26. package/src/server/appium-client.ts +0 -24
  27. package/src/server/index.ts +0 -6
  28. package/src/server/interaction-recorder.ts +0 -74
  29. package/src/server/proxy-middleware.ts +0 -68
  30. package/src/server/routes.ts +0 -53
  31. package/src/server/server.ts +0 -43
  32. package/src/server/types.ts +0 -34
  33. package/src/ui/bun.lock +0 -311
  34. package/src/ui/package.json +0 -20
  35. package/src/ui/src/App.css +0 -12
  36. package/src/ui/src/App.tsx +0 -41
  37. package/src/ui/src/components/ActionCarousel.css +0 -128
  38. package/src/ui/src/components/ActionCarousel.tsx +0 -92
  39. package/src/ui/src/components/Inspector.css +0 -314
  40. package/src/ui/src/components/Inspector.tsx +0 -265
  41. package/src/ui/src/components/InteractionCard.css +0 -159
  42. package/src/ui/src/components/InteractionCard.tsx +0 -60
  43. package/src/ui/src/components/MainInspector.css +0 -304
  44. package/src/ui/src/components/MainInspector.tsx +0 -304
  45. package/src/ui/src/components/Stats.css +0 -27
  46. package/src/ui/src/components/Timeline.css +0 -31
  47. package/src/ui/src/components/Timeline.tsx +0 -37
  48. package/src/ui/src/hooks/useInteractions.ts +0 -73
  49. package/src/ui/src/index.tsx +0 -11
  50. package/src/ui/src/services/api.ts +0 -41
  51. package/src/ui/src/styles/tokens.css +0 -126
  52. package/src/ui/src/types.ts +0 -34
  53. package/src/ui/src/utils/__tests__/locators.test.ts +0 -304
  54. package/src/ui/src/utils/__tests__/xml-parser.test.ts +0 -326
  55. package/src/ui/src/utils/locators.ts +0 -14
  56. package/src/ui/src/utils/xml-parser.ts +0 -45
  57. package/src/ui/tsconfig.json +0 -34
  58. package/src/ui/tsconfig.node.json +0 -11
  59. package/src/ui/vite.config.ts +0 -22
  60. package/tests/cli/arg-parser.test.ts +0 -397
  61. package/tests/cli/drive-commands.test.ts +0 -151
  62. package/tests/cli/selectors-best.test.ts +0 -42
  63. package/tests/cli/session-commands.test.ts +0 -53
  64. package/tests/core/selector-candidates.test.ts +0 -83
  65. package/tests/core/selector-scoring.test.ts +0 -75
  66. package/tests/core/xml-parser.test.ts +0 -56
  67. package/tests/server/appium-client.test.ts +0 -229
  68. package/tests/server/interaction-recorder.test.ts +0 -377
  69. package/tests/server/proxy-middleware.test.ts +0 -343
  70. package/tests/server/routes.test.ts +0 -305
  71. package/tsconfig.json +0 -26
  72. package/vitest.config.ts +0 -16
  73. package/vitest.ui.config.ts +0 -15
  74. package/workflow.gif +0 -0
@@ -1,377 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { InteractionRecorder } from '../../src/server/interaction-recorder';
3
- import type { ServerEvent, Interaction } from '../../src/server/types';
4
-
5
- describe('InteractionRecorder', () => {
6
- let recorder: InteractionRecorder;
7
-
8
- beforeEach(() => {
9
- recorder = new InteractionRecorder();
10
- });
11
-
12
- describe('shouldRecord', () => {
13
- it('should return true for POST requests', () => {
14
- expect(recorder.shouldRecord('POST', '/session/123/element')).toBe(true);
15
- });
16
-
17
- it('should return false for GET requests', () => {
18
- expect(recorder.shouldRecord('GET', '/session/123/element')).toBe(false);
19
- });
20
-
21
- it('should return false for PUT requests', () => {
22
- expect(recorder.shouldRecord('PUT', '/session/123/element')).toBe(false);
23
- });
24
-
25
- it('should return false for DELETE requests', () => {
26
- expect(recorder.shouldRecord('DELETE', '/session/123/element')).toBe(false);
27
- });
28
-
29
- it('should return false for PATCH requests', () => {
30
- expect(recorder.shouldRecord('PATCH', '/session/123/element')).toBe(false);
31
- });
32
- });
33
-
34
- describe('isActionEndpoint', () => {
35
- describe('POST method', () => {
36
- it('should identify click endpoint', () => {
37
- expect(recorder.isActionEndpoint('POST', '/session/123/element/abc123/click')).toBe(true);
38
- });
39
-
40
- it('should identify value endpoint', () => {
41
- expect(recorder.isActionEndpoint('POST', '/session/123/element/abc123/value')).toBe(true);
42
- });
43
-
44
- it('should identify clear endpoint', () => {
45
- expect(recorder.isActionEndpoint('POST', '/session/123/element/abc123/clear')).toBe(true);
46
- });
47
-
48
- it('should identify element endpoint', () => {
49
- expect(recorder.isActionEndpoint('POST', '/session/123/element')).toBe(true);
50
- });
51
-
52
- it('should identify elements endpoint', () => {
53
- expect(recorder.isActionEndpoint('POST', '/session/123/elements')).toBe(true);
54
- });
55
-
56
- it('should identify touch/perform endpoint', () => {
57
- expect(recorder.isActionEndpoint('POST', '/session/123/touch/perform')).toBe(true);
58
- });
59
-
60
- it('should identify actions endpoint', () => {
61
- expect(recorder.isActionEndpoint('POST', '/session/123/actions')).toBe(true);
62
- });
63
-
64
- it('should identify back endpoint', () => {
65
- expect(recorder.isActionEndpoint('POST', '/session/123/back')).toBe(true);
66
- });
67
-
68
- it('should identify forward endpoint', () => {
69
- expect(recorder.isActionEndpoint('POST', '/session/123/forward')).toBe(true);
70
- });
71
-
72
- it('should identify refresh endpoint', () => {
73
- expect(recorder.isActionEndpoint('POST', '/session/123/refresh')).toBe(true);
74
- });
75
- });
76
-
77
- describe('DELETE method', () => {
78
- it('should identify action endpoints for DELETE', () => {
79
- expect(recorder.isActionEndpoint('DELETE', '/session/123/element/abc123/click')).toBe(true);
80
- });
81
- });
82
-
83
- describe('non-action endpoints', () => {
84
- it('should return false for GET requests', () => {
85
- expect(recorder.isActionEndpoint('GET', '/session/123/element/abc123/click')).toBe(false);
86
- });
87
-
88
- it('should return false for screenshot endpoint', () => {
89
- expect(recorder.isActionEndpoint('POST', '/session/123/screenshot')).toBe(false);
90
- });
91
-
92
- it('should return false for source endpoint', () => {
93
- expect(recorder.isActionEndpoint('POST', '/session/123/source')).toBe(false);
94
- });
95
-
96
- it('should return false for window endpoint', () => {
97
- expect(recorder.isActionEndpoint('POST', '/session/123/window')).toBe(false);
98
- });
99
- });
100
-
101
- describe('edge cases', () => {
102
- it('should handle element IDs with special characters', () => {
103
- expect(recorder.isActionEndpoint('POST', '/session/123/element/abc-123_xyz/click')).toBe(true);
104
- });
105
-
106
- it('should handle UUIDs as element IDs', () => {
107
- expect(recorder.isActionEndpoint('POST', '/session/123/element/550e8400-e29b-41d4-a716-446655440000/click')).toBe(true);
108
- });
109
- });
110
- });
111
-
112
- describe('recordInteraction', () => {
113
- it('should create interaction with auto-incrementing ID', () => {
114
- const interaction1 = recorder.recordInteraction({
115
- method: 'POST',
116
- path: '/session/123/element',
117
- });
118
- const interaction2 = recorder.recordInteraction({
119
- method: 'POST',
120
- path: '/session/123/elements',
121
- });
122
-
123
- expect(interaction1.id).toBe(1);
124
- expect(interaction2.id).toBe(2);
125
- });
126
-
127
- it('should add timestamp to interaction', () => {
128
- const before = new Date().toISOString();
129
- const interaction = recorder.recordInteraction({
130
- method: 'POST',
131
- path: '/session/123/element',
132
- });
133
- const after = new Date().toISOString();
134
-
135
- expect(interaction.timestamp).toBeDefined();
136
- expect(new Date(interaction.timestamp).getTime()).toBeGreaterThanOrEqual(new Date(before).getTime());
137
- expect(new Date(interaction.timestamp).getTime()).toBeLessThanOrEqual(new Date(after).getTime());
138
- });
139
-
140
- it('should store interaction in history', () => {
141
- const interaction = recorder.recordInteraction({
142
- method: 'POST',
143
- path: '/session/123/element',
144
- });
145
-
146
- const history = recorder.getHistory();
147
- expect(history).toHaveLength(1);
148
- expect(history[0]).toEqual(interaction);
149
- });
150
-
151
- it('should include body if provided', () => {
152
- const interaction = recorder.recordInteraction({
153
- method: 'POST',
154
- path: '/session/123/element',
155
- body: { using: 'xpath', value: '//button' },
156
- });
157
-
158
- expect(interaction.body).toEqual({ using: 'xpath', value: '//button' });
159
- });
160
-
161
- it('should include elementInfo if provided', () => {
162
- const interaction = recorder.recordInteraction({
163
- method: 'POST',
164
- path: '/session/123/element',
165
- elementInfo: { using: 'xpath', value: '//button' },
166
- });
167
-
168
- expect(interaction.elementInfo).toEqual({ using: 'xpath', value: '//button' });
169
- });
170
-
171
- it('should emit interaction event', () => {
172
- const listener = vi.fn();
173
- recorder.on(listener);
174
-
175
- const interaction = recorder.recordInteraction({
176
- method: 'POST',
177
- path: '/session/123/element',
178
- });
179
-
180
- expect(listener).toHaveBeenCalledTimes(1);
181
- expect(listener).toHaveBeenCalledWith({
182
- type: 'interaction',
183
- data: interaction,
184
- });
185
- });
186
- });
187
-
188
- describe('updateInteraction', () => {
189
- it('should update existing interaction', () => {
190
- const interaction = recorder.recordInteraction({
191
- method: 'POST',
192
- path: '/session/123/element',
193
- });
194
-
195
- recorder.updateInteraction(interaction.id, {
196
- screenshot: 'base64screenshot',
197
- source: '<xml>source</xml>',
198
- });
199
-
200
- const history = recorder.getHistory();
201
- expect(history[0].screenshot).toBe('base64screenshot');
202
- expect(history[0].source).toBe('<xml>source</xml>');
203
- });
204
-
205
- it('should emit event on update', () => {
206
- const listener = vi.fn();
207
- const interaction = recorder.recordInteraction({
208
- method: 'POST',
209
- path: '/session/123/element',
210
- });
211
-
212
- recorder.on(listener);
213
- recorder.updateInteraction(interaction.id, {
214
- screenshot: 'base64screenshot',
215
- });
216
-
217
- expect(listener).toHaveBeenCalledTimes(1);
218
- expect(listener).toHaveBeenCalledWith(
219
- expect.objectContaining({
220
- type: 'interaction',
221
- data: expect.objectContaining({
222
- screenshot: 'base64screenshot',
223
- }),
224
- })
225
- );
226
- });
227
-
228
- it('should not emit event for non-existent interaction', () => {
229
- const listener = vi.fn();
230
- recorder.on(listener);
231
-
232
- recorder.updateInteraction(999, {
233
- screenshot: 'base64screenshot',
234
- });
235
-
236
- expect(listener).not.toHaveBeenCalled();
237
- });
238
-
239
- it('should not affect other interactions', () => {
240
- const interaction1 = recorder.recordInteraction({
241
- method: 'POST',
242
- path: '/session/123/element',
243
- });
244
- const interaction2 = recorder.recordInteraction({
245
- method: 'POST',
246
- path: '/session/123/elements',
247
- });
248
-
249
- recorder.updateInteraction(interaction1.id, {
250
- screenshot: 'base64screenshot',
251
- });
252
-
253
- const history = recorder.getHistory();
254
- expect(history[0].screenshot).toBe('base64screenshot');
255
- expect(history[1].screenshot).toBeUndefined();
256
- });
257
- });
258
-
259
- describe('getHistory', () => {
260
- it('should return empty array initially', () => {
261
- expect(recorder.getHistory()).toEqual([]);
262
- });
263
-
264
- it('should return all recorded interactions', () => {
265
- recorder.recordInteraction({ method: 'POST', path: '/session/123/element' });
266
- recorder.recordInteraction({ method: 'POST', path: '/session/123/elements' });
267
- recorder.recordInteraction({ method: 'POST', path: '/session/123/back' });
268
-
269
- const history = recorder.getHistory();
270
- expect(history).toHaveLength(3);
271
- });
272
-
273
- it('should return interactions in order', () => {
274
- recorder.recordInteraction({ method: 'POST', path: '/path1' });
275
- recorder.recordInteraction({ method: 'POST', path: '/path2' });
276
- recorder.recordInteraction({ method: 'POST', path: '/path3' });
277
-
278
- const history = recorder.getHistory();
279
- expect(history[0].path).toBe('/path1');
280
- expect(history[1].path).toBe('/path2');
281
- expect(history[2].path).toBe('/path3');
282
- });
283
- });
284
-
285
- describe('clearHistory', () => {
286
- it('should clear all interactions', () => {
287
- recorder.recordInteraction({ method: 'POST', path: '/path1' });
288
- recorder.recordInteraction({ method: 'POST', path: '/path2' });
289
-
290
- recorder.clearHistory();
291
-
292
- expect(recorder.getHistory()).toEqual([]);
293
- });
294
-
295
- it('should reset interaction ID counter', () => {
296
- recorder.recordInteraction({ method: 'POST', path: '/path1' });
297
- recorder.recordInteraction({ method: 'POST', path: '/path2' });
298
-
299
- recorder.clearHistory();
300
-
301
- const newInteraction = recorder.recordInteraction({ method: 'POST', path: '/path3' });
302
- expect(newInteraction.id).toBe(1);
303
- });
304
-
305
- it('should emit clear event', () => {
306
- const listener = vi.fn();
307
- recorder.on(listener);
308
-
309
- recorder.clearHistory();
310
-
311
- expect(listener).toHaveBeenCalledWith({
312
- type: 'clear',
313
- data: null,
314
- });
315
- });
316
- });
317
-
318
- describe('event listener management', () => {
319
- it('should register listener', () => {
320
- const listener = vi.fn();
321
- recorder.on(listener);
322
-
323
- recorder.recordInteraction({ method: 'POST', path: '/path' });
324
-
325
- expect(listener).toHaveBeenCalled();
326
- });
327
-
328
- it('should support multiple listeners', () => {
329
- const listener1 = vi.fn();
330
- const listener2 = vi.fn();
331
-
332
- recorder.on(listener1);
333
- recorder.on(listener2);
334
-
335
- recorder.recordInteraction({ method: 'POST', path: '/path' });
336
-
337
- expect(listener1).toHaveBeenCalled();
338
- expect(listener2).toHaveBeenCalled();
339
- });
340
-
341
- it('should unsubscribe listener', () => {
342
- const listener = vi.fn();
343
- const unsubscribe = recorder.on(listener);
344
-
345
- unsubscribe();
346
-
347
- recorder.recordInteraction({ method: 'POST', path: '/path' });
348
-
349
- expect(listener).not.toHaveBeenCalled();
350
- });
351
-
352
- it('should not affect other listeners when unsubscribing', () => {
353
- const listener1 = vi.fn();
354
- const listener2 = vi.fn();
355
-
356
- const unsubscribe1 = recorder.on(listener1);
357
- recorder.on(listener2);
358
-
359
- unsubscribe1();
360
-
361
- recorder.recordInteraction({ method: 'POST', path: '/path' });
362
-
363
- expect(listener1).not.toHaveBeenCalled();
364
- expect(listener2).toHaveBeenCalled();
365
- });
366
-
367
- it('should handle unsubscribing same listener multiple times', () => {
368
- const listener = vi.fn();
369
- const unsubscribe = recorder.on(listener);
370
-
371
- unsubscribe();
372
- unsubscribe(); // Should not throw
373
-
374
- expect(listener).not.toHaveBeenCalled();
375
- });
376
- });
377
- });