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 { describe, it, expect } from 'vitest';
2
- import { generateLocators } from '../locators';
3
- import type { ParsedElement } from '../../types';
4
-
5
- function createMockElement(overrides: Partial<ParsedElement> = {}): ParsedElement {
6
- return {
7
- type: 'XCUIElementTypeButton',
8
- name: '',
9
- label: '',
10
- value: '',
11
- enabled: true,
12
- visible: true,
13
- accessible: false,
14
- x: 0,
15
- y: 0,
16
- width: 100,
17
- height: 50,
18
- xpath: '/Application[1]/Button[1]',
19
- node: document.createElement('div') as unknown as Element,
20
- ...overrides,
21
- };
22
- }
23
-
24
- describe('generateLocators', () => {
25
- describe('accessibility id locators', () => {
26
- it('should generate accessibility id from name attribute', () => {
27
- const element = createMockElement({ name: 'loginButton' });
28
- const locators = generateLocators(element);
29
-
30
- const accessibilityIdLocators = locators.filter(l => l.strategy === 'accessibility id');
31
- expect(accessibilityIdLocators).toContainEqual({
32
- strategy: 'accessibility id',
33
- value: 'loginButton',
34
- });
35
- });
36
-
37
- it('should generate accessibility id from label attribute', () => {
38
- const element = createMockElement({ label: 'Login' });
39
- const locators = generateLocators(element);
40
-
41
- const accessibilityIdLocators = locators.filter(l => l.strategy === 'accessibility id');
42
- expect(accessibilityIdLocators).toContainEqual({
43
- strategy: 'accessibility id',
44
- value: 'Login',
45
- });
46
- });
47
-
48
- it('should not duplicate accessibility id if name and label are same', () => {
49
- const element = createMockElement({ name: 'Login', label: 'Login' });
50
- const locators = generateLocators(element);
51
-
52
- const accessibilityIdLocators = locators.filter(l => l.strategy === 'accessibility id');
53
- expect(accessibilityIdLocators).toHaveLength(1);
54
- });
55
-
56
- it('should generate both accessibility ids if name and label differ', () => {
57
- const element = createMockElement({ name: 'loginBtn', label: 'Login' });
58
- const locators = generateLocators(element);
59
-
60
- const accessibilityIdLocators = locators.filter(l => l.strategy === 'accessibility id');
61
- expect(accessibilityIdLocators).toHaveLength(2);
62
- expect(accessibilityIdLocators).toContainEqual({
63
- strategy: 'accessibility id',
64
- value: 'loginBtn',
65
- });
66
- expect(accessibilityIdLocators).toContainEqual({
67
- strategy: 'accessibility id',
68
- value: 'Login',
69
- });
70
- });
71
-
72
- it('should not generate accessibility id if name is empty', () => {
73
- const element = createMockElement({ name: '' });
74
- const locators = generateLocators(element);
75
-
76
- const accessibilityIdFromName = locators.find(
77
- l => l.strategy === 'accessibility id' && l.value === ''
78
- );
79
- expect(accessibilityIdFromName).toBeUndefined();
80
- });
81
- });
82
-
83
- describe('xpath locator', () => {
84
- it('should always include xpath locator', () => {
85
- const element = createMockElement({ xpath: '/App[1]/Window[1]/Button[1]' });
86
- const locators = generateLocators(element);
87
-
88
- const xpathLocator = locators.find(l => l.strategy === 'xpath');
89
- expect(xpathLocator).toEqual({
90
- strategy: 'xpath',
91
- value: '/App[1]/Window[1]/Button[1]',
92
- });
93
- });
94
-
95
- it('should include xpath even when no other attributes present', () => {
96
- const element = createMockElement({
97
- name: '',
98
- label: '',
99
- xpath: '/root[1]',
100
- });
101
- const locators = generateLocators(element);
102
-
103
- expect(locators.find(l => l.strategy === 'xpath')).toBeDefined();
104
- });
105
- });
106
-
107
- describe('class name locator', () => {
108
- it('should always include class name locator', () => {
109
- const element = createMockElement({ type: 'XCUIElementTypeButton' });
110
- const locators = generateLocators(element);
111
-
112
- const classNameLocator = locators.find(l => l.strategy === 'class name');
113
- expect(classNameLocator).toEqual({
114
- strategy: 'class name',
115
- value: 'XCUIElementTypeButton',
116
- });
117
- });
118
-
119
- it('should handle Android class names', () => {
120
- const element = createMockElement({ type: 'android.widget.Button' });
121
- const locators = generateLocators(element);
122
-
123
- const classNameLocator = locators.find(l => l.strategy === 'class name');
124
- expect(classNameLocator).toEqual({
125
- strategy: 'class name',
126
- value: 'android.widget.Button',
127
- });
128
- });
129
- });
130
-
131
- describe('iOS predicate string locators', () => {
132
- it('should generate predicate for name attribute', () => {
133
- const element = createMockElement({ name: 'loginButton' });
134
- const locators = generateLocators(element);
135
-
136
- const predicateLocators = locators.filter(l => l.strategy === '-ios predicate string');
137
- expect(predicateLocators).toContainEqual({
138
- strategy: '-ios predicate string',
139
- value: 'name == "loginButton"',
140
- });
141
- });
142
-
143
- it('should generate predicate for label attribute', () => {
144
- const element = createMockElement({ label: 'Login' });
145
- const locators = generateLocators(element);
146
-
147
- const predicateLocators = locators.filter(l => l.strategy === '-ios predicate string');
148
- expect(predicateLocators).toContainEqual({
149
- strategy: '-ios predicate string',
150
- value: 'label == "Login"',
151
- });
152
- });
153
-
154
- it('should generate both predicates if name and label are different', () => {
155
- const element = createMockElement({ name: 'loginBtn', label: 'Login' });
156
- const locators = generateLocators(element);
157
-
158
- const predicateLocators = locators.filter(l => l.strategy === '-ios predicate string');
159
- expect(predicateLocators).toHaveLength(2);
160
- });
161
-
162
- it('should not generate predicate if name is empty', () => {
163
- const element = createMockElement({ name: '' });
164
- const locators = generateLocators(element);
165
-
166
- const namePredicates = locators.filter(
167
- l => l.strategy === '-ios predicate string' && l.value.includes('name ==')
168
- );
169
- expect(namePredicates).toHaveLength(0);
170
- });
171
- });
172
-
173
- describe('iOS class chain locator', () => {
174
- it('should generate class chain for element with name', () => {
175
- const element = createMockElement({
176
- type: 'XCUIElementTypeButton',
177
- name: 'loginButton',
178
- });
179
- const locators = generateLocators(element);
180
-
181
- const classChainLocator = locators.find(l => l.strategy === '-ios class chain');
182
- expect(classChainLocator).toEqual({
183
- strategy: '-ios class chain',
184
- value: '**/XCUIElementTypeButton[`name == "loginButton"`]',
185
- });
186
- });
187
-
188
- it('should not generate class chain if name is empty', () => {
189
- const element = createMockElement({ name: '' });
190
- const locators = generateLocators(element);
191
-
192
- const classChainLocator = locators.find(l => l.strategy === '-ios class chain');
193
- expect(classChainLocator).toBeUndefined();
194
- });
195
- });
196
-
197
- describe('complete locator generation', () => {
198
- it('should generate all locators for element with all attributes', () => {
199
- const element = createMockElement({
200
- type: 'XCUIElementTypeButton',
201
- name: 'submitBtn',
202
- label: 'Submit',
203
- xpath: '/App[1]/Button[1]',
204
- });
205
- const locators = generateLocators(element);
206
-
207
- const strategies = locators.map(l => l.strategy);
208
- expect(strategies).toContain('accessibility id');
209
- expect(strategies).toContain('xpath');
210
- expect(strategies).toContain('class name');
211
- expect(strategies).toContain('-ios predicate string');
212
- expect(strategies).toContain('-ios class chain');
213
- });
214
-
215
- it('should generate minimal locators for element with no optional attributes', () => {
216
- const element = createMockElement({
217
- name: '',
218
- label: '',
219
- });
220
- const locators = generateLocators(element);
221
-
222
- // Should only have xpath and class name
223
- expect(locators).toHaveLength(2);
224
- expect(locators.map(l => l.strategy)).toEqual(['xpath', 'class name']);
225
- });
226
-
227
- it('should preserve locator order', () => {
228
- const element = createMockElement({
229
- name: 'testBtn',
230
- label: 'Test',
231
- type: 'Button',
232
- xpath: '/xpath',
233
- });
234
- const locators = generateLocators(element);
235
-
236
- // Verify expected order based on implementation
237
- const strategies = locators.map(l => l.strategy);
238
- expect(strategies[0]).toBe('accessibility id'); // from name
239
- expect(strategies[1]).toBe('accessibility id'); // from label
240
- expect(strategies[2]).toBe('xpath');
241
- expect(strategies[3]).toBe('class name');
242
- });
243
- });
244
-
245
- describe('edge cases', () => {
246
- it('should handle special characters in name', () => {
247
- const element = createMockElement({ name: 'button-1_test' });
248
- const locators = generateLocators(element);
249
-
250
- expect(locators).toContainEqual({
251
- strategy: 'accessibility id',
252
- value: 'button-1_test',
253
- });
254
- });
255
-
256
- it('should handle spaces in label', () => {
257
- const element = createMockElement({ label: 'Log In Now' });
258
- const locators = generateLocators(element);
259
-
260
- expect(locators).toContainEqual({
261
- strategy: 'accessibility id',
262
- value: 'Log In Now',
263
- });
264
- expect(locators).toContainEqual({
265
- strategy: '-ios predicate string',
266
- value: 'label == "Log In Now"',
267
- });
268
- });
269
-
270
- it('should handle quotes in name/label correctly in predicates', () => {
271
- const element = createMockElement({ name: 'test"button' });
272
- const locators = generateLocators(element);
273
-
274
- // The current implementation doesn't escape quotes, but we verify it includes them
275
- const predicate = locators.find(l => l.strategy === '-ios predicate string');
276
- expect(predicate).toBeDefined();
277
- });
278
-
279
- it('should handle unicode characters', () => {
280
- const element = createMockElement({ name: '登录按钮', label: 'ログイン' });
281
- const locators = generateLocators(element);
282
-
283
- expect(locators).toContainEqual({
284
- strategy: 'accessibility id',
285
- value: '登录按钮',
286
- });
287
- expect(locators).toContainEqual({
288
- strategy: 'accessibility id',
289
- value: 'ログイン',
290
- });
291
- });
292
-
293
- it('should handle very long names', () => {
294
- const longName = 'a'.repeat(1000);
295
- const element = createMockElement({ name: longName });
296
- const locators = generateLocators(element);
297
-
298
- expect(locators).toContainEqual({
299
- strategy: 'accessibility id',
300
- value: longName,
301
- });
302
- });
303
- });
304
- });
@@ -1,326 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { parseXmlSource } from '../xml-parser';
3
-
4
- describe('parseXmlSource', () => {
5
- describe('basic parsing', () => {
6
- it('should parse empty XML', () => {
7
- const xml = '<root></root>';
8
- const result = parseXmlSource(xml);
9
-
10
- expect(result).toHaveLength(1);
11
- expect(result[0].type).toBe('root');
12
- });
13
-
14
- it('should parse XML with type attribute', () => {
15
- const xml = '<root type="XCUIElementTypeApplication"></root>';
16
- const result = parseXmlSource(xml);
17
-
18
- expect(result[0].type).toBe('XCUIElementTypeApplication');
19
- });
20
-
21
- it('should use tagName when type attribute is missing', () => {
22
- const xml = '<CustomElement></CustomElement>';
23
- const result = parseXmlSource(xml);
24
-
25
- expect(result[0].type).toBe('CustomElement');
26
- });
27
- });
28
-
29
- describe('element attributes', () => {
30
- it('should parse name attribute', () => {
31
- const xml = '<element name="LoginButton"></element>';
32
- const result = parseXmlSource(xml);
33
-
34
- expect(result[0].name).toBe('LoginButton');
35
- });
36
-
37
- it('should parse label attribute', () => {
38
- const xml = '<element label="Login"></element>';
39
- const result = parseXmlSource(xml);
40
-
41
- expect(result[0].label).toBe('Login');
42
- });
43
-
44
- it('should parse value attribute', () => {
45
- const xml = '<element value="test@example.com"></element>';
46
- const result = parseXmlSource(xml);
47
-
48
- expect(result[0].value).toBe('test@example.com');
49
- });
50
-
51
- it('should parse enabled attribute', () => {
52
- const xml = '<element enabled="true"></element>';
53
- const result = parseXmlSource(xml);
54
-
55
- expect(result[0].enabled).toBe(true);
56
- });
57
-
58
- it('should parse visible attribute', () => {
59
- const xml = '<element visible="true"></element>';
60
- const result = parseXmlSource(xml);
61
-
62
- expect(result[0].visible).toBe(true);
63
- });
64
-
65
- it('should parse accessible attribute', () => {
66
- const xml = '<element accessible="true"></element>';
67
- const result = parseXmlSource(xml);
68
-
69
- expect(result[0].accessible).toBe(true);
70
- });
71
-
72
- it('should default boolean attributes to false', () => {
73
- const xml = '<element></element>';
74
- const result = parseXmlSource(xml);
75
-
76
- expect(result[0].enabled).toBe(false);
77
- expect(result[0].visible).toBe(false);
78
- expect(result[0].accessible).toBe(false);
79
- });
80
-
81
- it('should default string attributes to empty string', () => {
82
- const xml = '<element></element>';
83
- const result = parseXmlSource(xml);
84
-
85
- expect(result[0].name).toBe('');
86
- expect(result[0].label).toBe('');
87
- expect(result[0].value).toBe('');
88
- });
89
- });
90
-
91
- describe('coordinates', () => {
92
- it('should parse x, y, width, height attributes', () => {
93
- const xml = '<element x="100" y="200" width="50" height="30"></element>';
94
- const result = parseXmlSource(xml);
95
-
96
- expect(result[0].x).toBe(100);
97
- expect(result[0].y).toBe(200);
98
- expect(result[0].width).toBe(50);
99
- expect(result[0].height).toBe(30);
100
- });
101
-
102
- it('should default coordinates to 0', () => {
103
- const xml = '<element></element>';
104
- const result = parseXmlSource(xml);
105
-
106
- expect(result[0].x).toBe(0);
107
- expect(result[0].y).toBe(0);
108
- expect(result[0].width).toBe(0);
109
- expect(result[0].height).toBe(0);
110
- });
111
-
112
- it('should handle negative coordinates', () => {
113
- const xml = '<element x="-10" y="-20"></element>';
114
- const result = parseXmlSource(xml);
115
-
116
- expect(result[0].x).toBe(-10);
117
- expect(result[0].y).toBe(-20);
118
- });
119
- });
120
-
121
- describe('xpath generation', () => {
122
- it('should generate xpath for root element', () => {
123
- const xml = '<root></root>';
124
- const result = parseXmlSource(xml);
125
-
126
- expect(result[0].xpath).toBe('/root[1]');
127
- });
128
-
129
- it('should generate xpath with type attribute', () => {
130
- const xml = '<root type="XCUIElementTypeApplication"></root>';
131
- const result = parseXmlSource(xml);
132
-
133
- expect(result[0].xpath).toBe('/XCUIElementTypeApplication[1]');
134
- });
135
-
136
- it('should generate nested xpath', () => {
137
- const xml = `
138
- <app type="Application">
139
- <window type="Window">
140
- <button type="Button"></button>
141
- </window>
142
- </app>
143
- `;
144
- const result = parseXmlSource(xml);
145
-
146
- expect(result[0].xpath).toBe('/Application[1]');
147
- expect(result[1].xpath).toBe('/Application[1]/Window[1]');
148
- expect(result[2].xpath).toBe('/Application[1]/Window[1]/Button[1]');
149
- });
150
-
151
- it('should generate xpath with sibling indexing', () => {
152
- const xml = `
153
- <app type="Application">
154
- <button type="Button"></button>
155
- <button type="Button"></button>
156
- <button type="Button"></button>
157
- </app>
158
- `;
159
- const result = parseXmlSource(xml);
160
-
161
- expect(result[1].xpath).toBe('/Application[1]/Button[1]');
162
- expect(result[2].xpath).toBe('/Application[1]/Button[2]');
163
- expect(result[3].xpath).toBe('/Application[1]/Button[3]');
164
- });
165
-
166
- it('should handle mixed child types', () => {
167
- const xml = `
168
- <app type="Application">
169
- <button type="Button"></button>
170
- <text type="Text"></text>
171
- <button type="Button"></button>
172
- </app>
173
- `;
174
- const result = parseXmlSource(xml);
175
-
176
- expect(result[1].xpath).toBe('/Application[1]/Button[1]');
177
- expect(result[2].xpath).toBe('/Application[1]/Text[1]');
178
- expect(result[3].xpath).toBe('/Application[1]/Button[2]');
179
- });
180
- });
181
-
182
- describe('nested elements', () => {
183
- it('should parse all nested elements', () => {
184
- const xml = `
185
- <app type="Application">
186
- <window type="Window">
187
- <view type="View">
188
- <button type="Button"></button>
189
- <text type="Text"></text>
190
- </view>
191
- </window>
192
- </app>
193
- `;
194
- const result = parseXmlSource(xml);
195
-
196
- expect(result).toHaveLength(5);
197
- expect(result.map(e => e.type)).toEqual([
198
- 'Application',
199
- 'Window',
200
- 'View',
201
- 'Button',
202
- 'Text',
203
- ]);
204
- });
205
-
206
- it('should preserve element order (depth-first)', () => {
207
- const xml = `
208
- <root>
209
- <a>
210
- <b></b>
211
- </a>
212
- <c></c>
213
- </root>
214
- `;
215
- const result = parseXmlSource(xml);
216
-
217
- expect(result.map(e => e.type)).toEqual(['root', 'a', 'b', 'c']);
218
- });
219
- });
220
-
221
- describe('real-world Appium XML', () => {
222
- it('should parse iOS page source', () => {
223
- const xml = `
224
- <AppiumAUT type="XCUIElementTypeApplication" name="MyApp" label="" enabled="true" visible="true">
225
- <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="true" x="0" y="0" width="390" height="844">
226
- <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="0" width="390" height="844">
227
- <XCUIElementTypeButton type="XCUIElementTypeButton" name="loginButton" label="Log In" enabled="true" visible="true" x="20" y="400" width="350" height="44">
228
- </XCUIElementTypeButton>
229
- </XCUIElementTypeOther>
230
- </XCUIElementTypeWindow>
231
- </AppiumAUT>
232
- `;
233
- const result = parseXmlSource(xml);
234
-
235
- expect(result).toHaveLength(4);
236
-
237
- const button = result.find(e => e.type === 'XCUIElementTypeButton');
238
- expect(button).toBeDefined();
239
- expect(button!.name).toBe('loginButton');
240
- expect(button!.label).toBe('Log In');
241
- expect(button!.enabled).toBe(true);
242
- expect(button!.visible).toBe(true);
243
- expect(button!.x).toBe(20);
244
- expect(button!.y).toBe(400);
245
- expect(button!.width).toBe(350);
246
- expect(button!.height).toBe(44);
247
- });
248
-
249
- it('should parse Android page source', () => {
250
- const xml = `
251
- <hierarchy type="android.widget.FrameLayout">
252
- <android.widget.LinearLayout type="android.widget.LinearLayout" x="0" y="0" width="1080" height="2400">
253
- <android.widget.Button type="android.widget.Button" name="" label="" value="" enabled="true" visible="true" x="100" y="500" width="200" height="80">
254
- </android.widget.Button>
255
- </android.widget.LinearLayout>
256
- </hierarchy>
257
- `;
258
- const result = parseXmlSource(xml);
259
-
260
- expect(result).toHaveLength(3);
261
- expect(result.map(e => e.type)).toEqual([
262
- 'android.widget.FrameLayout',
263
- 'android.widget.LinearLayout',
264
- 'android.widget.Button',
265
- ]);
266
- });
267
- });
268
-
269
- describe('edge cases', () => {
270
- it('should handle empty document', () => {
271
- const xml = '';
272
- // parseXmlSource expects valid XML, empty string may produce error
273
- // In real usage, empty strings are filtered out before calling
274
- const result = parseXmlSource(xml);
275
- // DOMParser will create an error document or empty result
276
- expect(Array.isArray(result)).toBe(true);
277
- });
278
-
279
- it('should handle deeply nested elements', () => {
280
- const xml = `
281
- <level1 type="L1">
282
- <level2 type="L2">
283
- <level3 type="L3">
284
- <level4 type="L4">
285
- <level5 type="L5">
286
- </level5>
287
- </level4>
288
- </level3>
289
- </level2>
290
- </level1>
291
- `;
292
- const result = parseXmlSource(xml);
293
-
294
- expect(result).toHaveLength(5);
295
- expect(result[4].xpath).toBe('/L1[1]/L2[1]/L3[1]/L4[1]/L5[1]');
296
- });
297
-
298
- it('should handle elements with special characters in attributes', () => {
299
- const xml = '<element name="Button &amp; Text" label="Click &lt;here&gt;"></element>';
300
- const result = parseXmlSource(xml);
301
-
302
- expect(result[0].name).toBe('Button & Text');
303
- expect(result[0].label).toBe('Click <here>');
304
- });
305
-
306
- it('should handle many sibling elements', () => {
307
- let children = '';
308
- for (let i = 0; i < 100; i++) {
309
- children += `<item type="Item" name="item${i}"></item>`;
310
- }
311
- const xml = `<root>${children}</root>`;
312
- const result = parseXmlSource(xml);
313
-
314
- expect(result).toHaveLength(101); // root + 100 items
315
- expect(result[100].xpath).toBe('/root[1]/Item[100]');
316
- });
317
-
318
- it('should include node reference in result', () => {
319
- const xml = '<element name="TestElement"></element>';
320
- const result = parseXmlSource(xml);
321
-
322
- expect(result[0].node).toBeDefined();
323
- expect(result[0].node instanceof Element).toBe(true);
324
- });
325
- });
326
- });
@@ -1,14 +0,0 @@
1
- import { generateLegacyLocators } from '../../../core/selectors/generate-candidates';
2
- import type { ParsedElement, Locator } from '../types';
3
-
4
- export function generateLocators(element: ParsedElement): Locator[] {
5
- return generateLegacyLocators({
6
- type: element.type,
7
- name: element.name,
8
- label: element.label,
9
- xpath: element.xpath,
10
- }).map(locator => ({
11
- strategy: locator.strategy,
12
- value: locator.value,
13
- }));
14
- }
@@ -1,45 +0,0 @@
1
- import type { ParsedElement } from '../types';
2
-
3
- export function parseXmlSource(xmlString: string): ParsedElement[] {
4
- const parser = new DOMParser();
5
- const doc = parser.parseFromString(xmlString, 'text/xml');
6
- const elements: ParsedElement[] = [];
7
-
8
- function traverse(node: Element, xpath = '', index = 0) {
9
- if (node.nodeType !== 1) return;
10
-
11
- const type = node.getAttribute('type') || node.tagName;
12
- const x = parseInt(node.getAttribute('x') || '0');
13
- const y = parseInt(node.getAttribute('y') || '0');
14
- const width = parseInt(node.getAttribute('width') || '0');
15
- const height = parseInt(node.getAttribute('height') || '0');
16
-
17
- const currentXpath = xpath + '/' + type + '[' + (index + 1) + ']';
18
-
19
- elements.push({
20
- type,
21
- name: node.getAttribute('name') || '',
22
- label: node.getAttribute('label') || '',
23
- value: node.getAttribute('value') || '',
24
- enabled: node.getAttribute('enabled') === 'true',
25
- visible: node.getAttribute('visible') === 'true',
26
- accessible: node.getAttribute('accessible') === 'true',
27
- x, y, width, height,
28
- xpath: currentXpath,
29
- node
30
- });
31
-
32
- const childCounts: Record<string, number> = {};
33
- for (const child of Array.from(node.children)) {
34
- const childType = child.getAttribute('type') || child.tagName;
35
- childCounts[childType] = (childCounts[childType] || 0);
36
- traverse(child as Element, currentXpath, childCounts[childType]);
37
- childCounts[childType]++;
38
- }
39
- }
40
-
41
- if (doc.documentElement) {
42
- traverse(doc.documentElement);
43
- }
44
- return elements;
45
- }