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,74 +0,0 @@
1
- import type { Interaction, ServerEvent } from './types';
2
-
3
- export class InteractionRecorder {
4
- private history: Interaction[] = [];
5
- private interactionId = 0;
6
- private listeners: Set<(event: ServerEvent) => void> = new Set();
7
-
8
- shouldRecord(method: string, path: string): boolean {
9
- if (method === 'POST') {
10
- return true;
11
- }
12
- return false;
13
- }
14
-
15
- isActionEndpoint(method: string, path: string): boolean {
16
- const actionPatterns = [
17
- /\/element\/[^/]+\/click$/,
18
- /\/element\/[^/]+\/value$/,
19
- /\/element\/[^/]+\/clear$/,
20
- /\/element$/,
21
- /\/elements$/,
22
- /\/touch\/perform$/,
23
- /\/actions$/,
24
- /\/back$/,
25
- /\/forward$/,
26
- /\/refresh$/,
27
- ];
28
-
29
- if (method === 'POST' || method === 'DELETE') {
30
- return actionPatterns.some(pattern => pattern.test(path));
31
- }
32
- return false;
33
- }
34
-
35
- recordInteraction(interaction: Omit<Interaction, 'id' | 'timestamp'>): Interaction {
36
- const fullInteraction: Interaction = {
37
- id: ++this.interactionId,
38
- timestamp: new Date().toISOString(),
39
- ...interaction,
40
- };
41
-
42
- this.history.push(fullInteraction);
43
- this.emit({ type: 'interaction', data: fullInteraction });
44
-
45
- return fullInteraction;
46
- }
47
-
48
- updateInteraction(id: number, updates: Partial<Interaction>): void {
49
- const interaction = this.history.find(i => i.id === id);
50
- if (interaction) {
51
- Object.assign(interaction, updates);
52
- this.emit({ type: 'interaction', data: interaction });
53
- }
54
- }
55
-
56
- getHistory(): Interaction[] {
57
- return this.history;
58
- }
59
-
60
- clearHistory(): void {
61
- this.history = [];
62
- this.interactionId = 0;
63
- this.emit({ type: 'clear', data: null });
64
- }
65
-
66
- on(listener: (event: ServerEvent) => void): () => void {
67
- this.listeners.add(listener);
68
- return () => this.listeners.delete(listener);
69
- }
70
-
71
- private emit(event: ServerEvent): void {
72
- this.listeners.forEach(listener => listener(event));
73
- }
74
- }
@@ -1,68 +0,0 @@
1
- import { Request, Response, NextFunction } from 'express';
2
- import { createProxyMiddleware, fixRequestBody } from 'http-proxy-middleware';
3
- import type { Interaction } from './types';
4
- import { AppiumClient } from './appium-client';
5
- import { InteractionRecorder } from './interaction-recorder';
6
-
7
- export function createSessionMiddleware(
8
- recorder: InteractionRecorder,
9
- appiumClient: AppiumClient,
10
- ) {
11
- return async (req: Request, res: Response, next: NextFunction) => {
12
- const { sessionId } = req.params;
13
-
14
- // Skip ignored endpoints
15
- if (!recorder.shouldRecord(req.method, req.originalUrl)) {
16
- return next();
17
- }
18
-
19
- const isAction = recorder.isActionEndpoint(req.method, req.path);
20
-
21
- // Create interaction record
22
- const interactionData: Omit<Interaction, 'id' | 'timestamp'> = {
23
- method: req.method,
24
- path: req.originalUrl,
25
- body: req.body && Object.keys(req.body).length > 0 ? req.body : undefined,
26
- };
27
-
28
- // Extract element info for find operations
29
- if (req.body?.using && req.body?.value) {
30
- interactionData.elementInfo = {
31
- using: req.body.using,
32
- value: req.body.value,
33
- };
34
- }
35
-
36
- const interaction = recorder.recordInteraction(interactionData);
37
-
38
- console.log(`[${interaction.id}] ${req.method} ${req.originalUrl}`);
39
- if (interaction.body) {
40
- console.log('Body:', JSON.stringify(interaction.body, null, 2));
41
- }
42
-
43
- // For actions, capture state after the action completes
44
- if (isAction) {
45
- res.on('finish', async () => {
46
- const state = await appiumClient.captureState(sessionId);
47
- recorder.updateInteraction(interaction.id, {
48
- screenshot: state.screenshot,
49
- source: state.source,
50
- });
51
- console.log(`[${interaction.id}] State captured (screenshot + source)`);
52
- });
53
- }
54
-
55
- next();
56
- };
57
- }
58
-
59
- export function createAppiumProxy(appiumUrl: string) {
60
- return createProxyMiddleware({
61
- target: appiumUrl,
62
- changeOrigin: true,
63
- ws: true,
64
- on: {
65
- proxyReq: fixRequestBody,
66
- },
67
- });
68
- }
@@ -1,64 +0,0 @@
1
- import express, { Router } from 'express';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import type { InteractionRecorder } from './interaction-recorder';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
-
10
- // Resolve UI dist path: works from both source (src/server/) and bundle (dist/)
11
- function resolveUiDir(): string {
12
- const candidates = [
13
- path.join(__dirname, '../../dist/ui'), // dev: running from src/server/
14
- path.join(__dirname, 'ui'), // prod: running from dist/
15
- ];
16
- return candidates.find(p => fs.existsSync(p)) || candidates[0];
17
- }
18
-
19
- const uiDir = resolveUiDir();
20
-
21
- export function createRoutes(recorder: InteractionRecorder) {
22
- const router = Router();
23
-
24
- // Serve the UI
25
- router.get('/_recorder', (_req, res) => {
26
- res.sendFile(path.join(uiDir, 'index.html'));
27
- });
28
-
29
- // Serve static assets using express.static for better performance and caching
30
- router.use('/_recorder/assets', express.static(path.join(uiDir, 'assets')));
31
-
32
- // API: Get history
33
- router.get('/_recorder/api/history', (_req, res) => {
34
- res.json(recorder.getHistory());
35
- });
36
-
37
- // API: Clear history
38
- router.delete('/_recorder/api/history', (_req, res) => {
39
- recorder.clearHistory();
40
- res.json({ ok: true });
41
- });
42
-
43
- // API: Server-Sent Events for real-time updates
44
- router.get('/_recorder/api/stream', (req, res) => {
45
- res.setHeader('Content-Type', 'text/event-stream');
46
- res.setHeader('Cache-Control', 'no-cache');
47
- res.setHeader('Connection', 'keep-alive');
48
-
49
- // Send initial history
50
- res.write(`data: ${JSON.stringify({ type: 'init', data: recorder.getHistory() })}\n\n`);
51
-
52
- // Listen for new interactions
53
- const unsubscribe = recorder.on((event) => {
54
- res.write(`data: ${JSON.stringify(event)}\n\n`);
55
- });
56
-
57
- // Clean up on close
58
- req.on('close', () => {
59
- unsubscribe();
60
- });
61
- });
62
-
63
- return router;
64
- }
@@ -1,43 +0,0 @@
1
- import express from 'express';
2
- import type { RecorderOptions } from './types';
3
- import { AppiumClient } from './appium-client';
4
- import { InteractionRecorder } from './interaction-recorder';
5
- import { createSessionMiddleware, createAppiumProxy } from './proxy-middleware';
6
- import { createRoutes } from './routes';
7
-
8
- export function createServer(options: RecorderOptions = {}) {
9
- const appiumUrl = options.appiumUrl ?? 'http://127.0.0.1:4723';
10
-
11
- const app = express();
12
- const appiumClient = new AppiumClient(appiumUrl);
13
- const recorder = new InteractionRecorder();
14
-
15
- // Parse JSON bodies
16
- app.use(express.json({ type: ['application/json', 'application/*+json'], limit: '10mb' }));
17
- app.use(express.urlencoded({ extended: true, limit: '1mb' }));
18
-
19
- // Register routes
20
- app.use(createRoutes(recorder));
21
-
22
- // Intercept session requests
23
- app.use('/session/:sessionId', createSessionMiddleware(recorder, appiumClient));
24
-
25
- // Proxy everything to Appium
26
- app.use(createAppiumProxy(appiumUrl));
27
-
28
- return { app, recorder };
29
- }
30
-
31
- export function startServer(options: RecorderOptions = {}) {
32
- const host = options.host ?? '127.0.0.1';
33
- const port = options.port ?? 4724;
34
- const appiumUrl = options.appiumUrl ?? 'http://127.0.0.1:4723';
35
-
36
- const { app } = createServer(options);
37
-
38
- return app.listen(port, host, () => {
39
- console.log(`\n✅ Session Recorder started:`);
40
- console.log(` Proxy: http://${host}:${port} → ${appiumUrl}`);
41
- console.log(` Viewer: http://${host}:${port}/_recorder\n`);
42
- });
43
- }
@@ -1,34 +0,0 @@
1
- export type RecorderOptions = {
2
- appiumUrl?: string;
3
- host?: string;
4
- port?: number;
5
- };
6
-
7
- export type ElementInfo = {
8
- using: string;
9
- value: string;
10
- };
11
-
12
- export type Interaction = {
13
- id: number;
14
- timestamp: string;
15
- method: string;
16
- path: string;
17
- body?: any;
18
- screenshot?: string; // base64
19
- source?: string; // XML
20
- elementInfo?: ElementInfo;
21
- };
22
-
23
- export type AppiumResponse = {
24
- value: any;
25
- sessionId?: string;
26
- status?: number;
27
- };
28
-
29
- export type ServerEventType = 'interaction' | 'clear';
30
-
31
- export type ServerEvent = {
32
- type: ServerEventType;
33
- data: Interaction | null;
34
- };