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,265 +0,0 @@
1
- import { type Component, createSignal, Show, For } from 'solid-js';
2
- import { Dialog } from '@kobalte/core/dialog';
3
- import type { Interaction } from '../types';
4
- import { parseXmlSource } from '../utils/xml-parser';
5
- import { generateLocators } from '../utils/locators';
6
- import type { ParsedElement, Locator } from '../types';
7
- import './Inspector.css';
8
-
9
- type InspectorProps = {
10
- interaction: Interaction | null;
11
- open: boolean;
12
- onClose: () => void;
13
- };
14
-
15
- export const Inspector: Component<InspectorProps> = (props) => {
16
- const [selectedElement, setSelectedElement] = createSignal<ParsedElement | null>(null);
17
- const [queryStrategy, setQueryStrategy] = createSignal('accessibility id');
18
- const [queryValue, setQueryValue] = createSignal('');
19
- const [foundElements, setFoundElements] = createSignal<ParsedElement[]>([]);
20
- const [showSource, setShowSource] = createSignal(false);
21
-
22
- const parsedElements = () => {
23
- if (!props.interaction?.source) return [];
24
- return parseXmlSource(props.interaction.source);
25
- };
26
-
27
- const runQuery = () => {
28
- const strategy = queryStrategy();
29
- const value = queryValue().trim();
30
-
31
- if (!value) return;
32
-
33
- const elements = parsedElements();
34
- let found: ParsedElement[] = [];
35
-
36
- switch (strategy) {
37
- case 'accessibility id':
38
- found = elements.filter(el => el.name === value || el.label === value);
39
- break;
40
- case 'class name':
41
- found = elements.filter(el => el.type === value);
42
- break;
43
- case 'xpath':
44
- // Properly evaluate XPath against the XML source
45
- if (props.interaction?.source) {
46
- try {
47
- const parser = new DOMParser();
48
- const doc = parser.parseFromString(props.interaction.source, 'text/xml');
49
- const result = doc.evaluate(value, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
50
- const matchedNodes: Element[] = [];
51
- for (let i = 0; i < result.snapshotLength; i++) {
52
- const node = result.snapshotItem(i);
53
- if (node && node.nodeType === 1) {
54
- matchedNodes.push(node as Element);
55
- }
56
- }
57
- // Match found nodes back to parsed elements
58
- found = elements.filter(el => matchedNodes.some(node => el.node.isEqualNode(node)));
59
- } catch (e) {
60
- console.error('Invalid XPath expression:', e);
61
- }
62
- }
63
- break;
64
- case '-ios predicate string':
65
- // iOS predicate string matching (simplified attribute matching)
66
- found = elements.filter(el => {
67
- // Parse simple predicates like "name == 'value'" or "label CONTAINS 'text'"
68
- const predicateLower = value.toLowerCase();
69
- if (predicateLower.includes('name')) {
70
- const match = value.match(/name\s*(==|CONTAINS)\s*['"](.*)['"]/i);
71
- if (match) {
72
- return match[1] === '=='
73
- ? el.name === match[2]
74
- : el.name.includes(match[2]);
75
- }
76
- }
77
- if (predicateLower.includes('label')) {
78
- const match = value.match(/label\s*(==|CONTAINS)\s*['"](.*)['"]/i);
79
- if (match) {
80
- return match[1] === '=='
81
- ? el.label === match[2]
82
- : el.label.includes(match[2]);
83
- }
84
- }
85
- if (predicateLower.includes('type')) {
86
- const match = value.match(/type\s*(==|CONTAINS)\s*['"](.*)['"]/i);
87
- if (match) {
88
- return match[1] === '=='
89
- ? el.type === match[2]
90
- : el.type.includes(match[2]);
91
- }
92
- }
93
- return false;
94
- });
95
- break;
96
- case '-ios class chain':
97
- // iOS class chain matching (simplified type/index matching)
98
- // Format: **/XCUIElementTypeButton[`name == "buttonName"`]
99
- const classChainMatch = value.match(/\*\*\/(\w+)(?:\[`(.+?)`\])?/);
100
- if (classChainMatch) {
101
- const targetType = classChainMatch[1];
102
- const predicate = classChainMatch[2];
103
- found = elements.filter(el => {
104
- if (el.type !== targetType) return false;
105
- if (!predicate) return true;
106
- // Simple predicate matching within class chain
107
- const nameMatch = predicate.match(/name\s*==\s*['"](.*)['"]/i);
108
- if (nameMatch) return el.name === nameMatch[1];
109
- const labelMatch = predicate.match(/label\s*==\s*['"](.*)['"]/i);
110
- if (labelMatch) return el.label === labelMatch[1];
111
- return true;
112
- });
113
- }
114
- break;
115
- }
116
-
117
- setFoundElements(found);
118
- if (found.length > 0) {
119
- setSelectedElement(found[0]);
120
- }
121
- };
122
-
123
- const copyText = (text: string) => {
124
- navigator.clipboard.writeText(text);
125
- };
126
-
127
- const locators = (): Locator[] => {
128
- const el = selectedElement();
129
- return el ? generateLocators(el) : [];
130
- };
131
-
132
- const resetState = () => {
133
- setSelectedElement(null);
134
- setQueryStrategy('accessibility id');
135
- setQueryValue('');
136
- setFoundElements([]);
137
- setShowSource(false);
138
- };
139
-
140
- const handleClose = () => {
141
- resetState();
142
- props.onClose();
143
- };
144
-
145
- return (
146
- <Dialog open={props.open} onOpenChange={(open) => !open && handleClose()}>
147
- <Dialog.Portal>
148
- <Dialog.Overlay class="inspector-overlay" />
149
- <Dialog.Content class="inspector-modal">
150
- <Dialog.CloseButton class="inspector-close">✕</Dialog.CloseButton>
151
-
152
- <div class="inspector-panel">
153
- <div class="inspector-left">
154
- <Show when={props.interaction?.screenshot}>
155
- <img
156
- src={`data:image/png;base64,${props.interaction!.screenshot}`}
157
- alt="Screenshot"
158
- class="inspector-screenshot"
159
- />
160
- </Show>
161
- </div>
162
-
163
- <div class="inspector-right">
164
- <div class="inspector-section">
165
- <h3>Query Tester</h3>
166
- <div class="query-tester">
167
- <div class="query-row">
168
- <select
169
- value={queryStrategy()}
170
- onChange={(e) => setQueryStrategy(e.currentTarget.value)}
171
- class="query-select"
172
- >
173
- <option value="accessibility id">accessibility id</option>
174
- <option value="xpath">xpath</option>
175
- <option value="class name">class name</option>
176
- <option value="-ios predicate string">-ios predicate string</option>
177
- <option value="-ios class chain">-ios class chain</option>
178
- </select>
179
- <input
180
- type="text"
181
- value={queryValue()}
182
- onInput={(e) => setQueryValue(e.currentTarget.value)}
183
- onKeyPress={(e) => e.key === 'Enter' && runQuery()}
184
- placeholder="Enter locator value..."
185
- class="query-input"
186
- />
187
- <button onClick={runQuery} class="query-btn">
188
- Find
189
- </button>
190
- </div>
191
-
192
- <Show when={foundElements().length > 0}>
193
- <div class="query-result success">
194
- Found {foundElements().length} element(s)
195
- </div>
196
- </Show>
197
- </div>
198
- </div>
199
- <Show when={selectedElement()}>
200
- <div class="inspector-section">
201
- <h3>Element Details</h3>
202
- <div class="element-details">
203
- <div class="element-attr">
204
- <span class="attr-name">Type:</span>
205
- <span class="attr-value">{selectedElement()!.type}</span>
206
- </div>
207
- <Show when={selectedElement()!.name}>
208
- <div class="element-attr">
209
- <span class="attr-name">Name:</span>
210
- <span class="attr-value">{selectedElement()!.name}</span>
211
- </div>
212
- </Show>
213
- <Show when={selectedElement()!.label}>
214
- <div class="element-attr">
215
- <span class="attr-name">Label:</span>
216
- <span class="attr-value">{selectedElement()!.label}</span>
217
- </div>
218
- </Show>
219
- <div class="element-attr">
220
- <span class="attr-name">Bounds:</span>
221
- <span class="attr-value">
222
- x={selectedElement()!.x}, y={selectedElement()!.y},
223
- w={selectedElement()!.width}, h={selectedElement()!.height}
224
- </span>
225
- </div>
226
- </div>
227
- </div>
228
-
229
- <div class="inspector-section">
230
- <h3>Locators (click to copy)</h3>
231
- <div class="locators-list">
232
- <For each={locators()}>
233
- {(locator) => (
234
- <div class="locator-row" onClick={() => copyText(locator.value)}>
235
- <span class="locator-strategy">{locator.strategy}</span>
236
- <span class="locator-value">{locator.value}</span>
237
- </div>
238
- )}
239
- </For>
240
- </div>
241
- </div>
242
- </Show>
243
-
244
- {/* XML Source Toggle */}
245
- <Show when={props.interaction?.source}>
246
- <div class="inspector-section">
247
- <button
248
- class="source-toggle-btn"
249
- onClick={() => setShowSource(!showSource())}
250
- >
251
- {showSource() ? 'Hide' : 'Show'} XML Source
252
- </button>
253
- <Show when={showSource()}>
254
- <pre class="xml-source">{props.interaction!.source}</pre>
255
- </Show>
256
- </div>
257
- </Show>
258
- </div>
259
- </div>
260
- </Dialog.Content>
261
- </Dialog.Portal>
262
- </Dialog>
263
- );
264
- };
265
-
@@ -1,159 +0,0 @@
1
- .interaction-card {
2
- background: var(--color-bg-secondary);
3
- border-radius: var(--radius-xl);
4
- padding: var(--spacing-5);
5
- border-left: 3px solid var(--color-accent-primary);
6
- transition: all var(--transition-base);
7
- box-shadow: var(--shadow-xs);
8
- border-top: 1px solid var(--color-border);
9
- border-right: 1px solid var(--color-border);
10
- border-bottom: 1px solid var(--color-border);
11
- }
12
-
13
- .interaction-card.action {
14
- border-left-color: var(--color-accent-warning);
15
- }
16
-
17
- .interaction-card:hover {
18
- box-shadow: var(--shadow-md);
19
- transform: translateY(-1px);
20
- }
21
-
22
- .interaction-header {
23
- display: flex;
24
- justify-content: space-between;
25
- align-items: center;
26
- margin-bottom: var(--spacing-3);
27
- flex-wrap: wrap;
28
- gap: var(--spacing-2);
29
- }
30
-
31
- .interaction-header-left {
32
- display: flex;
33
- align-items: center;
34
- gap: var(--spacing-2);
35
- flex-wrap: wrap;
36
- }
37
-
38
- .interaction-id {
39
- background: var(--color-bg-tertiary);
40
- color: var(--color-text-secondary);
41
- padding: 2px var(--spacing-2);
42
- border-radius: var(--radius-full);
43
- font-size: var(--font-size-xs);
44
- font-weight: var(--font-weight-semibold);
45
- }
46
-
47
- .interaction-card.action .interaction-id {
48
- background: #FFF3E0;
49
- color: var(--color-accent-warning);
50
- }
51
-
52
- .interaction-method {
53
- font-weight: var(--font-weight-semibold);
54
- padding: 2px var(--spacing-2);
55
- border-radius: var(--radius-sm);
56
- font-size: var(--font-size-xs);
57
- text-transform: uppercase;
58
- letter-spacing: 0.03em;
59
- }
60
-
61
- .interaction-method.POST {
62
- background: #E8F5EE;
63
- color: var(--color-accent-success);
64
- }
65
-
66
- .interaction-method.GET {
67
- background: #E8F0FE;
68
- color: #3B7DD8;
69
- }
70
-
71
- .interaction-method.DELETE {
72
- background: #FDECEB;
73
- color: var(--color-accent-error);
74
- }
75
-
76
- .interaction-path {
77
- color: var(--color-text-secondary);
78
- font-size: var(--font-size-sm);
79
- word-break: break-all;
80
- overflow-wrap: break-word;
81
- max-width: 100%;
82
- }
83
-
84
- .interaction-time {
85
- color: var(--color-text-tertiary);
86
- font-size: var(--font-size-xs);
87
- }
88
-
89
- .element-info {
90
- background: var(--color-bg-tertiary);
91
- padding: var(--spacing-2) var(--spacing-3);
92
- border-radius: var(--radius-md);
93
- margin-top: var(--spacing-3);
94
- font-size: var(--font-size-sm);
95
- word-wrap: break-word;
96
- overflow-wrap: break-word;
97
- }
98
-
99
- .element-info-using {
100
- color: var(--color-accent-primary);
101
- font-weight: var(--font-weight-medium);
102
- }
103
-
104
- .element-info-value {
105
- color: var(--color-text-primary);
106
- word-break: break-all;
107
- }
108
-
109
- .interaction-body {
110
- background: var(--color-bg-primary);
111
- padding: var(--spacing-3);
112
- border-radius: var(--radius-md);
113
- margin-top: var(--spacing-3);
114
- font-size: var(--font-size-xs);
115
- font-family: var(--font-mono);
116
- overflow: auto;
117
- max-height: 200px;
118
- border: 1px solid var(--color-border);
119
- white-space: pre-wrap;
120
- word-break: break-all;
121
- color: var(--color-text-secondary);
122
- }
123
-
124
- .screenshot-container {
125
- margin-top: var(--spacing-4);
126
- display: flex;
127
- flex-direction: column;
128
- gap: var(--spacing-3);
129
- align-items: flex-start;
130
- }
131
-
132
- .screenshot {
133
- max-width: 300px;
134
- border-radius: var(--radius-xl);
135
- cursor: pointer;
136
- border: 1px solid var(--color-border);
137
- transition: all var(--transition-base);
138
- }
139
-
140
- .screenshot:hover {
141
- border-color: var(--color-accent-primary);
142
- box-shadow: var(--shadow-md);
143
- }
144
-
145
- .inspect-btn {
146
- background: var(--color-accent-primary);
147
- color: white;
148
- border: none;
149
- padding: var(--spacing-2) var(--spacing-5);
150
- border-radius: var(--radius-lg);
151
- cursor: pointer;
152
- font-weight: var(--font-weight-medium);
153
- font-size: var(--font-size-sm);
154
- transition: all var(--transition-fast);
155
- }
156
-
157
- .inspect-btn:hover {
158
- background: var(--color-accent-secondary);
159
- }
@@ -1,60 +0,0 @@
1
- import { type Component, Show } from 'solid-js';
2
- import type { Interaction } from '../types';
3
- import './InteractionCard.css';
4
-
5
- type InteractionCardProps = {
6
- interaction: Interaction;
7
- onInspect?: () => void;
8
- };
9
-
10
- export const InteractionCard: Component<InteractionCardProps> = (props) => {
11
- const formattedTime = () => new Date(props.interaction.timestamp).toLocaleTimeString();
12
- const isAction = () => !!props.interaction.screenshot;
13
-
14
- return (
15
- <div classList={{ 'interaction-card': true, 'action': isAction() }}>
16
- <div class="interaction-header">
17
- <div class="interaction-header-left">
18
- <span class="interaction-id">#{props.interaction.id}</span>
19
- <span classList={{
20
- 'interaction-method': true,
21
- [props.interaction.method]: true,
22
- }}>
23
- {props.interaction.method}
24
- </span>
25
- <span class="interaction-path">{props.interaction.path}</span>
26
- </div>
27
- <span class="interaction-time">{formattedTime()}</span>
28
- </div>
29
-
30
- <Show when={props.interaction.elementInfo}>
31
- <div class="element-info">
32
- <span class="element-info-using">{props.interaction.elementInfo!.using}:</span>
33
- {' "'}
34
- <span class="element-info-value">{props.interaction.elementInfo!.value}</span>
35
- {'"'}
36
- </div>
37
- </Show>
38
-
39
- <Show when={props.interaction.body}>
40
- <pre class="interaction-body">
41
- {JSON.stringify(props.interaction.body, null, 2)}
42
- </pre>
43
- </Show>
44
-
45
- <Show when={props.interaction.screenshot}>
46
- <div class="screenshot-container">
47
- <img
48
- src={`data:image/png;base64,${props.interaction.screenshot}`}
49
- alt="Screenshot"
50
- class="screenshot"
51
- onClick={props.onInspect}
52
- />
53
- <button class="inspect-btn" onClick={props.onInspect}>
54
- Inspect Elements
55
- </button>
56
- </div>
57
- </Show>
58
- </div>
59
- );
60
- };