appium-session-recorder 0.0.2 → 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 (73) hide show
  1. package/dist/index.js +32422 -0
  2. package/dist/ui/assets/index-CUcJNRfB.css +1 -0
  3. package/dist/ui/assets/index-Cl_X3tPj.js +4 -0
  4. package/{src → dist}/ui/index.html +2 -1
  5. package/package.json +10 -3
  6. package/bun.lock +0 -731
  7. package/src/cli/arg-parser.ts +0 -311
  8. package/src/cli/commands/drive.ts +0 -147
  9. package/src/cli/commands/index.ts +0 -54
  10. package/src/cli/commands/proxy.ts +0 -41
  11. package/src/cli/commands/screen.ts +0 -73
  12. package/src/cli/commands/selectors.ts +0 -42
  13. package/src/cli/commands/session.ts +0 -64
  14. package/src/cli/commands/types.ts +0 -11
  15. package/src/cli/index.ts +0 -158
  16. package/src/cli/prompts.ts +0 -64
  17. package/src/cli/response.ts +0 -44
  18. package/src/core/appium/client.ts +0 -248
  19. package/src/core/index.ts +0 -5
  20. package/src/core/selectors/generate-candidates.ts +0 -155
  21. package/src/core/selectors/score-candidates.ts +0 -184
  22. package/src/core/types.ts +0 -79
  23. package/src/core/xml/parse-source.ts +0 -197
  24. package/src/index.ts +0 -7
  25. package/src/server/appium-client.ts +0 -24
  26. package/src/server/index.ts +0 -6
  27. package/src/server/interaction-recorder.ts +0 -74
  28. package/src/server/proxy-middleware.ts +0 -68
  29. package/src/server/routes.ts +0 -64
  30. package/src/server/server.ts +0 -43
  31. package/src/server/types.ts +0 -34
  32. package/src/ui/bun.lock +0 -311
  33. package/src/ui/package.json +0 -20
  34. package/src/ui/src/App.css +0 -12
  35. package/src/ui/src/App.tsx +0 -41
  36. package/src/ui/src/components/ActionCarousel.css +0 -128
  37. package/src/ui/src/components/ActionCarousel.tsx +0 -92
  38. package/src/ui/src/components/Inspector.css +0 -314
  39. package/src/ui/src/components/Inspector.tsx +0 -265
  40. package/src/ui/src/components/InteractionCard.css +0 -159
  41. package/src/ui/src/components/InteractionCard.tsx +0 -60
  42. package/src/ui/src/components/MainInspector.css +0 -304
  43. package/src/ui/src/components/MainInspector.tsx +0 -304
  44. package/src/ui/src/components/Stats.css +0 -27
  45. package/src/ui/src/components/Timeline.css +0 -31
  46. package/src/ui/src/components/Timeline.tsx +0 -37
  47. package/src/ui/src/hooks/useInteractions.ts +0 -73
  48. package/src/ui/src/index.tsx +0 -11
  49. package/src/ui/src/services/api.ts +0 -41
  50. package/src/ui/src/styles/tokens.css +0 -126
  51. package/src/ui/src/types.ts +0 -34
  52. package/src/ui/src/utils/__tests__/locators.test.ts +0 -304
  53. package/src/ui/src/utils/__tests__/xml-parser.test.ts +0 -326
  54. package/src/ui/src/utils/locators.ts +0 -14
  55. package/src/ui/src/utils/xml-parser.ts +0 -45
  56. package/src/ui/tsconfig.json +0 -34
  57. package/src/ui/tsconfig.node.json +0 -11
  58. package/src/ui/vite.config.ts +0 -22
  59. package/tests/cli/arg-parser.test.ts +0 -397
  60. package/tests/cli/drive-commands.test.ts +0 -151
  61. package/tests/cli/selectors-best.test.ts +0 -42
  62. package/tests/cli/session-commands.test.ts +0 -53
  63. package/tests/core/selector-candidates.test.ts +0 -83
  64. package/tests/core/selector-scoring.test.ts +0 -75
  65. package/tests/core/xml-parser.test.ts +0 -56
  66. package/tests/server/appium-client.test.ts +0 -229
  67. package/tests/server/interaction-recorder.test.ts +0 -377
  68. package/tests/server/proxy-middleware.test.ts +0 -343
  69. package/tests/server/routes.test.ts +0 -305
  70. package/tsconfig.json +0 -26
  71. package/vitest.config.ts +0 -16
  72. package/vitest.ui.config.ts +0 -15
  73. package/workflow.gif +0 -0
@@ -1,304 +0,0 @@
1
- import { type Component, createSignal, Show, For, createEffect, createRenderEffect, onCleanup } from 'solid-js';
2
- import type { Interaction } from '../types';
3
- import { parseXmlSource } from '../utils/xml-parser';
4
- import { generateLocators } from '../utils/locators';
5
- import type { ParsedElement, Locator } from '../types';
6
- import './MainInspector.css';
7
-
8
- type MainInspectorProps = {
9
- interaction: Interaction | null;
10
- };
11
-
12
- export const MainInspector: Component<MainInspectorProps> = (props) => {
13
- const [selectedElement, setSelectedElement] = createSignal<ParsedElement | null>(null);
14
- const [queryStrategy, setQueryStrategy] = createSignal('accessibility id');
15
- const [queryValue, setQueryValue] = createSignal('');
16
- const [foundElements, setFoundElements] = createSignal<ParsedElement[]>([]);
17
- const [copiedText, setCopiedText] = createSignal<string | null>(null);
18
- const [queryError, setQueryError] = createSignal<string | null>(null);
19
- const [xmlPreRef, setXmlPreRef] = createSignal<HTMLPreElement | undefined>(undefined);
20
-
21
- // Reset state when interaction changes
22
- createEffect(() => {
23
- if (props.interaction) {
24
- setSelectedElement(null);
25
- setQueryValue('');
26
- setFoundElements([]);
27
- }
28
- });
29
-
30
- const parsedElements = () => {
31
- if (!props.interaction?.source) return [];
32
- return parseXmlSource(props.interaction.source);
33
- };
34
-
35
- const runQuery = () => {
36
- const strategy = queryStrategy();
37
- const value = queryValue().trim();
38
-
39
- setQueryError(null);
40
-
41
- if (!value) return;
42
-
43
- const elements = parsedElements();
44
- let found: ParsedElement[] = [];
45
-
46
- switch (strategy) {
47
- case 'accessibility id':
48
- found = elements.filter(el => el.name === value || el.label === value);
49
- break;
50
- case 'class name':
51
- found = elements.filter(el => el.type === value);
52
- break;
53
- case 'xpath':
54
- if (props.interaction?.source) {
55
- try {
56
- const parser = new DOMParser();
57
- const doc = parser.parseFromString(props.interaction.source, 'text/xml');
58
- const result = doc.evaluate(value, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
59
- const matchedNodes: Element[] = [];
60
- for (let i = 0; i < result.snapshotLength; i++) {
61
- const node = result.snapshotItem(i);
62
- if (node && node.nodeType === 1) {
63
- matchedNodes.push(node as Element);
64
- }
65
- }
66
- found = elements.filter(el => matchedNodes.some(node => el.node.isEqualNode(node)));
67
- } catch (e) {
68
- console.error('Invalid XPath expression:', e);
69
- }
70
- }
71
- break;
72
- case '-ios predicate string':
73
- found = elements.filter(el => {
74
- const predicateLower = value.toLowerCase();
75
- if (predicateLower.includes('name')) {
76
- const match = value.match(/name\s*(==|CONTAINS)\s*['"](.*)['"]/i);
77
- if (match) {
78
- return match[1] === '==' ? el.name === match[2] : el.name.includes(match[2]);
79
- }
80
- }
81
- if (predicateLower.includes('label')) {
82
- const match = value.match(/label\s*(==|CONTAINS)\s*['"](.*)['"]/i);
83
- if (match) {
84
- return match[1] === '==' ? el.label === match[2] : el.label.includes(match[2]);
85
- }
86
- }
87
- if (predicateLower.includes('type')) {
88
- const match = value.match(/type\s*(==|CONTAINS)\s*['"](.*)['"]/i);
89
- if (match) {
90
- return match[1] === '==' ? el.type === match[2] : el.type.includes(match[2]);
91
- }
92
- }
93
- return false;
94
- });
95
- break;
96
- case '-ios class chain':
97
- const classChainMatch = value.match(/\*\*\/(\w+)(?:\[`(.+?)`\])?/);
98
- if (classChainMatch) {
99
- const targetType = classChainMatch[1];
100
- const predicate = classChainMatch[2];
101
- found = elements.filter(el => {
102
- if (el.type !== targetType) return false;
103
- if (!predicate) return true;
104
- const nameMatch = predicate.match(/name\s*==\s*['"](.*)['"]/i);
105
- if (nameMatch) return el.name === nameMatch[1];
106
- const labelMatch = predicate.match(/label\s*==\s*['"](.*)['"]/i);
107
- if (labelMatch) return el.label === labelMatch[1];
108
- return true;
109
- });
110
- }
111
- break;
112
- }
113
-
114
- setFoundElements(found);
115
- if (found.length > 0) {
116
- setSelectedElement(found[0]);
117
- setQueryError(null);
118
- } else {
119
- setSelectedElement(null);
120
- setQueryError(`No elements found for ${strategy}: "${value}"`);
121
- }
122
- };
123
-
124
- const copyText = (text: string) => {
125
- navigator.clipboard.writeText(text);
126
- setCopiedText(text);
127
- setTimeout(() => setCopiedText(null), 2000);
128
- };
129
-
130
- const locators = (): Locator[] => {
131
- const el = selectedElement();
132
- return el ? generateLocators(el) : [];
133
- };
134
-
135
- const formatXml = (xml: string) => {
136
- // Simple XML formatting for better readability
137
- let formatted = '';
138
- let indent = 0;
139
- const lines = xml.replace(/></g, '>\n<').split('\n');
140
-
141
- for (const line of lines) {
142
- const trimmed = line.trim();
143
- if (!trimmed) continue;
144
-
145
- if (trimmed.startsWith('</')) {
146
- indent = Math.max(0, indent - 1);
147
- }
148
-
149
- formatted += ' '.repeat(indent) + trimmed + '\n';
150
-
151
- if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.endsWith('/>') && !trimmed.includes('</')) {
152
- indent++;
153
- }
154
- }
155
-
156
- return formatted;
157
- };
158
-
159
- // Defense-in-depth: render XML as textContent (never HTML)
160
- createRenderEffect(() => {
161
- const el = xmlPreRef();
162
- if (!el) return;
163
- el.textContent = formatXml(props.interaction?.source || '');
164
- });
165
-
166
- return (
167
- <div class="main-inspector">
168
- <Show
169
- when={props.interaction}
170
- fallback={
171
- <div class="inspector-empty">
172
- <div class="inspector-empty-content">
173
- <span class="inspector-empty-icon">🔍</span>
174
- <span class="inspector-empty-text">Select an action to inspect</span>
175
- </div>
176
- </div>
177
- }
178
- >
179
- {/* Query Tester Section */}
180
- <div class="query-section">
181
- <h3 class="section-title">Query Tester</h3>
182
- <div class="query-row">
183
- <select
184
- value={queryStrategy()}
185
- onChange={(e) => setQueryStrategy(e.currentTarget.value)}
186
- class="query-select"
187
- >
188
- <option value="accessibility id">accessibility id</option>
189
- <option value="xpath">xpath</option>
190
- <option value="class name">class name</option>
191
- <option value="-ios predicate string">-ios predicate string</option>
192
- <option value="-ios class chain">-ios class chain</option>
193
- </select>
194
- <input
195
- type="text"
196
- value={queryValue()}
197
- onInput={(e) => setQueryValue(e.currentTarget.value)}
198
- onKeyPress={(e) => e.key === 'Enter' && runQuery()}
199
- placeholder="Enter locator value..."
200
- class="query-input"
201
- />
202
- <button onClick={runQuery} class="query-btn">
203
- Find
204
- </button>
205
- </div>
206
-
207
- <Show when={foundElements().length > 0}>
208
- <div class="query-result success">
209
- Found {foundElements().length} element(s)
210
- </div>
211
- </Show>
212
-
213
- <Show when={queryError()}>
214
- <div class="query-result error">
215
- <span class="error-icon">⚠️</span>
216
- <span>{queryError()}</span>
217
- <button class="error-dismiss" onClick={() => setQueryError(null)}>✕</button>
218
- </div>
219
- </Show>
220
-
221
- {/* Element Details */}
222
- <Show when={selectedElement()}>
223
- <div class="element-panel">
224
- <div class="element-details">
225
- <div class="element-attr">
226
- <span class="attr-name">Type:</span>
227
- <span class="attr-value">{selectedElement()!.type}</span>
228
- </div>
229
- <Show when={selectedElement()!.name}>
230
- <div class="element-attr">
231
- <span class="attr-name">Name:</span>
232
- <span class="attr-value">{selectedElement()!.name}</span>
233
- </div>
234
- </Show>
235
- <Show when={selectedElement()!.label}>
236
- <div class="element-attr">
237
- <span class="attr-name">Label:</span>
238
- <span class="attr-value">{selectedElement()!.label}</span>
239
- </div>
240
- </Show>
241
- <div class="element-attr">
242
- <span class="attr-name">Bounds:</span>
243
- <span class="attr-value">
244
- x={selectedElement()!.x}, y={selectedElement()!.y},
245
- w={selectedElement()!.width}, h={selectedElement()!.height}
246
- </span>
247
- </div>
248
- </div>
249
-
250
- <div class="locators-section">
251
- <h4>Locators (click to copy)</h4>
252
- <div class="locators-list">
253
- <For each={locators()}>
254
- {(locator) => (
255
- <div
256
- class="locator-row"
257
- classList={{ copied: copiedText() === locator.value }}
258
- onClick={() => copyText(locator.value)}
259
- >
260
- <span class="locator-strategy">{locator.strategy}</span>
261
- <span class="locator-value">{locator.value}</span>
262
- <Show when={copiedText() === locator.value}>
263
- <span class="copied-badge">Copied!</span>
264
- </Show>
265
- </div>
266
- )}
267
- </For>
268
- </div>
269
- </div>
270
- </div>
271
- </Show>
272
- </div>
273
-
274
- {/* Content Area: Screenshot Left, XML Right */}
275
- <div class="content-area">
276
- {/* Screenshot Section */}
277
- <div class="screenshot-section">
278
- <Show when={props.interaction!.screenshot}>
279
- <img
280
- src={`data:image/png;base64,${props.interaction!.screenshot}`}
281
- alt="Screenshot"
282
- class="screenshot-image"
283
- />
284
- </Show>
285
- </div>
286
-
287
- {/* XML Source Section */}
288
- <div class="xml-section">
289
- <h3 class="section-title">XML Source</h3>
290
- <pre
291
- ref={(el) => {
292
- setXmlPreRef(el);
293
- onCleanup(() => {
294
- setXmlPreRef(undefined);
295
- });
296
- }}
297
- class="xml-source"
298
- />
299
- </div>
300
- </div>
301
- </Show>
302
- </div>
303
- );
304
- };
@@ -1,27 +0,0 @@
1
- .stats {
2
- display: flex;
3
- gap: var(--spacing-6);
4
- padding: var(--spacing-5);
5
- background: var(--color-bg-secondary);
6
- border-radius: var(--radius-xl);
7
- margin-bottom: var(--spacing-6);
8
- box-shadow: var(--shadow-sm);
9
- border: 1px solid var(--color-border);
10
- }
11
-
12
- .stat {
13
- text-align: center;
14
- flex: 1;
15
- }
16
-
17
- .stat-value {
18
- font-size: var(--font-size-3xl);
19
- font-weight: var(--font-weight-bold);
20
- color: var(--color-accent-primary);
21
- }
22
-
23
- .stat-label {
24
- font-size: var(--font-size-sm);
25
- color: var(--color-text-tertiary);
26
- margin-top: var(--spacing-2);
27
- }
@@ -1,31 +0,0 @@
1
- .timeline {
2
- display: flex;
3
- flex-direction: column;
4
- gap: var(--spacing-4);
5
- }
6
-
7
- .empty-state {
8
- text-align: center;
9
- padding: var(--spacing-12) var(--spacing-6);
10
- background: var(--color-bg-secondary);
11
- border-radius: var(--radius-2xl);
12
- border: 1px dashed var(--color-border);
13
- }
14
-
15
- .empty-icon {
16
- font-size: 3rem;
17
- margin-bottom: var(--spacing-4);
18
- opacity: 0.4;
19
- }
20
-
21
- .empty-title {
22
- font-size: var(--font-size-lg);
23
- font-weight: var(--font-weight-semibold);
24
- color: var(--color-text-primary);
25
- margin-bottom: var(--spacing-2);
26
- }
27
-
28
- .empty-text {
29
- color: var(--color-text-tertiary);
30
- font-size: var(--font-size-sm);
31
- }
@@ -1,37 +0,0 @@
1
- import { type Component, For, Show } from 'solid-js';
2
- import type { Interaction } from '../types';
3
- import { InteractionCard } from './InteractionCard';
4
- import './Timeline.css';
5
-
6
- type TimelineProps = {
7
- interactions: Interaction[];
8
- onInspect?: (interaction: Interaction) => void;
9
- };
10
-
11
- export const Timeline: Component<TimelineProps> = (props) => {
12
- return (
13
- <div class="timeline">
14
- <Show
15
- when={props.interactions.length > 0}
16
- fallback={
17
- <div class="empty-state">
18
- <div class="empty-icon">📱</div>
19
- <div class="empty-title">No interactions recorded yet</div>
20
- <div class="empty-text">
21
- Connect Appium Inspector to port 4724 and start interacting
22
- </div>
23
- </div>
24
- }
25
- >
26
- <For each={props.interactions}>
27
- {(interaction) => (
28
- <InteractionCard
29
- interaction={interaction}
30
- onInspect={() => props.onInspect?.(interaction)}
31
- />
32
- )}
33
- </For>
34
- </Show>
35
- </div>
36
- );
37
- };
@@ -1,73 +0,0 @@
1
- import { createSignal, onCleanup, createEffect } from 'solid-js';
2
- import type { Interaction } from '../types';
3
- import { api } from '../services/api';
4
-
5
- export function useInteractions() {
6
- const [interactions, setInteractions] = createSignal<Interaction[]>([]);
7
- const [loading, setLoading] = createSignal(true);
8
-
9
- // Load initial history
10
- async function loadHistory() {
11
- setLoading(true);
12
- try {
13
- const history = await api.getHistory();
14
- setInteractions(history);
15
- } catch (error) {
16
- console.error('Failed to load history:', error);
17
- } finally {
18
- setLoading(false);
19
- }
20
- }
21
-
22
- // Connect to SSE stream
23
- createEffect(() => {
24
- const unsubscribe = api.connectToStream((event) => {
25
- if (event.type === 'init') {
26
- setInteractions(event.data);
27
- setLoading(false);
28
- } else if (event.type === 'interaction') {
29
- setInteractions(prev => {
30
- const existing = prev.findIndex(i => i.id === event.data.id);
31
- if (existing >= 0) {
32
- // Update existing
33
- const updated = [...prev];
34
- updated[existing] = event.data;
35
- return updated;
36
- } else {
37
- // Add new
38
- return [...prev, event.data];
39
- }
40
- });
41
- } else if (event.type === 'clear') {
42
- setInteractions([]);
43
- }
44
- });
45
-
46
- onCleanup(unsubscribe);
47
- });
48
-
49
- // Note: loadHistory() is not called here because the SSE stream
50
- // already sends the complete initial history via the 'init' event
51
-
52
- async function clearHistory() {
53
- await api.clearHistory();
54
- }
55
-
56
- async function refresh() {
57
- await loadHistory();
58
- }
59
-
60
- const actions = interactions().filter(i => i.screenshot);
61
- const stats = () => ({
62
- total: interactions().length,
63
- actions: actions.length,
64
- });
65
-
66
- return {
67
- interactions,
68
- loading,
69
- stats,
70
- clearHistory,
71
- refresh,
72
- };
73
- }
@@ -1,11 +0,0 @@
1
- import { render } from 'solid-js/web';
2
- import App from './App';
3
- import './styles/tokens.css';
4
-
5
- const root = document.getElementById('root');
6
-
7
- if (!root) {
8
- throw new Error('Root element not found');
9
- }
10
-
11
- render(() => <App />, root);
@@ -1,41 +0,0 @@
1
- import type { Interaction } from '../types';
2
-
3
- class ApiClient {
4
- async getHistory(): Promise<Interaction[]> {
5
- const response = await fetch('/_recorder/api/history');
6
-
7
- if(!response.ok) {
8
- throw new Error(`Failed to fetch history: ${response.statusText}`);
9
- }
10
-
11
- return response.json();
12
- }
13
-
14
- async clearHistory(): Promise<void> {
15
- const response = await fetch('/_recorder/api/clear', { method: 'POST' });
16
- if(!response.ok) {
17
- throw new Error(`Failed to clear history: ${response.statusText}`);
18
- }
19
- }
20
-
21
- connectToStream(onEvent: (event: any) => void): () => void {
22
- const eventSource = new EventSource('/_recorder/api/stream');
23
-
24
- eventSource.onmessage = (event) => {
25
- try {
26
- const data = JSON.parse(event.data);
27
- onEvent(data);
28
- } catch (error) {
29
- console.error('Failed to parse SSE event:', error);
30
- }
31
- };
32
-
33
- eventSource.onerror = (error) => {
34
- console.error('SSE connection error:', error);
35
- };
36
-
37
- return () => eventSource.close();
38
- }
39
- }
40
-
41
- export const api = new ApiClient();
@@ -1,126 +0,0 @@
1
- :root {
2
- /* Colors - Claude-inspired warm light theme */
3
- --color-bg-primary: #F9F7F3;
4
- --color-bg-secondary: #FFFFFF;
5
- --color-bg-tertiary: #F0EDE6;
6
- --color-bg-elevated: #FFFFFF;
7
-
8
- --color-accent-primary: #C4682A;
9
- --color-accent-secondary: #B05C26;
10
- --color-accent-success: #2D7D4F;
11
- --color-accent-warning: #B8862E;
12
- --color-accent-error: #C4453A;
13
-
14
- --color-text-primary: #1B1B18;
15
- --color-text-secondary: #6B6B64;
16
- --color-text-tertiary: #9B9B94;
17
-
18
- --color-border: #E3DFD7;
19
- --color-border-hover: #C4682A;
20
-
21
- /* Typography */
22
- --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
23
- --font-mono: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace;
24
- --font-size-xs: 0.75rem;
25
- --font-size-sm: 0.8125rem;
26
- --font-size-base: 0.9375rem;
27
- --font-size-lg: 1.0625rem;
28
- --font-size-xl: 1.25rem;
29
- --font-size-2xl: 1.5rem;
30
- --font-size-3xl: 2rem;
31
-
32
- --font-weight-normal: 400;
33
- --font-weight-medium: 500;
34
- --font-weight-semibold: 600;
35
- --font-weight-bold: 700;
36
-
37
- /* Spacing */
38
- --spacing-1: 0.25rem;
39
- --spacing-2: 0.5rem;
40
- --spacing-3: 0.75rem;
41
- --spacing-4: 1rem;
42
- --spacing-5: 1.25rem;
43
- --spacing-6: 1.5rem;
44
- --spacing-8: 2rem;
45
- --spacing-10: 2.5rem;
46
- --spacing-12: 3rem;
47
-
48
- /* Border radius */
49
- --radius-sm: 0.375rem;
50
- --radius-md: 0.5rem;
51
- --radius-lg: 0.75rem;
52
- --radius-xl: 1rem;
53
- --radius-2xl: 1.25rem;
54
- --radius-full: 9999px;
55
-
56
- /* Shadows - soft and subtle */
57
- --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.03);
58
- --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.03);
59
- --shadow-md: 0 4px 8px -2px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.04);
60
- --shadow-lg: 0 12px 28px -6px rgba(0, 0, 0, 0.08), 0 4px 12px -4px rgba(0, 0, 0, 0.04);
61
-
62
- /* Transitions */
63
- --transition-fast: 150ms ease;
64
- --transition-base: 200ms ease;
65
- --transition-slow: 300ms ease;
66
-
67
- /* Z-index */
68
- --z-dropdown: 1000;
69
- --z-sticky: 1020;
70
- --z-modal: 1050;
71
- --z-tooltip: 1080;
72
- }
73
-
74
- * {
75
- box-sizing: border-box;
76
- margin: 0;
77
- padding: 0;
78
- }
79
-
80
- body {
81
- font-family: var(--font-family);
82
- background: var(--color-bg-primary);
83
- color: var(--color-text-primary);
84
- line-height: 1.6;
85
- -webkit-font-smoothing: antialiased;
86
- -moz-osx-font-smoothing: grayscale;
87
- }
88
-
89
- #root {
90
- min-height: 100vh;
91
- }
92
-
93
- /* Scrollbar styling */
94
- ::-webkit-scrollbar {
95
- width: 6px;
96
- height: 6px;
97
- }
98
-
99
- ::-webkit-scrollbar-track {
100
- background: transparent;
101
- }
102
-
103
- ::-webkit-scrollbar-thumb {
104
- background: var(--color-border);
105
- border-radius: var(--radius-full);
106
- }
107
-
108
- ::-webkit-scrollbar-thumb:hover {
109
- background: var(--color-text-tertiary);
110
- }
111
-
112
- /* Utility classes */
113
- .text-xs { font-size: var(--font-size-xs); }
114
- .text-sm { font-size: var(--font-size-sm); }
115
- .text-base { font-size: var(--font-size-base); }
116
- .text-lg { font-size: var(--font-size-lg); }
117
- .text-xl { font-size: var(--font-size-xl); }
118
- .text-2xl { font-size: var(--font-size-2xl); }
119
- .text-3xl { font-size: var(--font-size-3xl); }
120
-
121
- .font-normal { font-weight: var(--font-weight-normal); }
122
- .font-medium { font-weight: var(--font-weight-medium); }
123
- .font-semibold { font-weight: var(--font-weight-semibold); }
124
- .font-bold { font-weight: var(--font-weight-bold); }
125
-
126
- .transition { transition: all var(--transition-base); }
@@ -1,34 +0,0 @@
1
- export type Interaction = {
2
- id: number;
3
- timestamp: string;
4
- method: string;
5
- path: string;
6
- body?: any;
7
- screenshot?: string;
8
- source?: string;
9
- elementInfo?: {
10
- using: string;
11
- value: string;
12
- };
13
- };
14
-
15
- export type ParsedElement = {
16
- type: string;
17
- name: string;
18
- label: string;
19
- value: string;
20
- enabled: boolean;
21
- visible: boolean;
22
- accessible: boolean;
23
- x: number;
24
- y: number;
25
- width: number;
26
- height: number;
27
- xpath: string;
28
- node: Element;
29
- };
30
-
31
- export type Locator = {
32
- strategy: string;
33
- value: string;
34
- };