appium-uiwatchers-plugin 1.0.0 → 1.0.1

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 (84) hide show
  1. package/README.md +7 -2
  2. package/lib/commands/clear.d.ts +12 -0
  3. package/lib/commands/clear.d.ts.map +1 -0
  4. package/lib/commands/clear.js +22 -0
  5. package/lib/commands/clear.js.map +1 -0
  6. package/lib/commands/list.d.ts +12 -0
  7. package/lib/commands/list.d.ts.map +1 -0
  8. package/lib/commands/list.js +19 -0
  9. package/lib/commands/list.js.map +1 -0
  10. package/lib/commands/register.d.ts +13 -0
  11. package/lib/commands/register.d.ts.map +1 -0
  12. package/lib/commands/register.js +34 -0
  13. package/lib/commands/register.js.map +1 -0
  14. package/lib/commands/toggle.d.ts +18 -0
  15. package/lib/commands/toggle.d.ts.map +1 -0
  16. package/lib/commands/toggle.js +34 -0
  17. package/lib/commands/toggle.js.map +1 -0
  18. package/lib/commands/unregister.d.ts +13 -0
  19. package/lib/commands/unregister.d.ts.map +1 -0
  20. package/lib/commands/unregister.js +32 -0
  21. package/lib/commands/unregister.js.map +1 -0
  22. package/lib/config.d.ts +21 -0
  23. package/lib/config.d.ts.map +1 -0
  24. package/lib/config.js +13 -0
  25. package/lib/config.js.map +1 -0
  26. package/lib/element-cache.d.ts +93 -0
  27. package/lib/element-cache.d.ts.map +1 -0
  28. package/lib/element-cache.js +207 -0
  29. package/lib/element-cache.js.map +1 -0
  30. package/lib/plugin.d.ts +73 -0
  31. package/lib/plugin.d.ts.map +1 -0
  32. package/lib/plugin.js +353 -0
  33. package/lib/plugin.js.map +1 -0
  34. package/lib/types.d.ts +163 -0
  35. package/lib/types.d.ts.map +1 -0
  36. package/lib/types.js +5 -0
  37. package/lib/types.js.map +1 -0
  38. package/lib/utils.d.ts +24 -0
  39. package/lib/utils.d.ts.map +1 -0
  40. package/lib/utils.js +33 -0
  41. package/lib/utils.js.map +1 -0
  42. package/lib/validators.d.ts +18 -0
  43. package/lib/validators.d.ts.map +1 -0
  44. package/lib/validators.js +112 -0
  45. package/lib/validators.js.map +1 -0
  46. package/lib/watcher-checker.d.ts +12 -0
  47. package/lib/watcher-checker.d.ts.map +1 -0
  48. package/lib/watcher-checker.js +88 -0
  49. package/lib/watcher-checker.js.map +1 -0
  50. package/lib/watcher-store.d.ts +91 -0
  51. package/lib/watcher-store.d.ts.map +1 -0
  52. package/lib/watcher-store.js +177 -0
  53. package/lib/watcher-store.js.map +1 -0
  54. package/package.json +4 -1
  55. package/.c8rc.json +0 -12
  56. package/.github/workflows/npm-publish.yml +0 -28
  57. package/.husky/pre-commit +0 -4
  58. package/.lintstagedrc.json +0 -4
  59. package/.mocharc.json +0 -10
  60. package/.prettierignore +0 -6
  61. package/.prettierrc +0 -11
  62. package/eslint.config.js +0 -65
  63. package/src/commands/clear.ts +0 -28
  64. package/src/commands/list.ts +0 -23
  65. package/src/commands/register.ts +0 -47
  66. package/src/commands/toggle.ts +0 -43
  67. package/src/commands/unregister.ts +0 -43
  68. package/src/config.ts +0 -30
  69. package/src/element-cache.ts +0 -262
  70. package/src/plugin.ts +0 -437
  71. package/src/types.ts +0 -207
  72. package/src/utils.ts +0 -47
  73. package/src/validators.ts +0 -131
  74. package/src/watcher-checker.ts +0 -113
  75. package/src/watcher-store.ts +0 -210
  76. package/test/e2e/config.e2e.spec.cjs +0 -420
  77. package/test/e2e/plugin.e2e.spec.cjs +0 -312
  78. package/test/unit/element-cache.spec.js +0 -269
  79. package/test/unit/plugin.spec.js +0 -52
  80. package/test/unit/utils.spec.js +0 -85
  81. package/test/unit/validators.spec.js +0 -246
  82. package/test/unit/watcher-checker.spec.js +0 -274
  83. package/test/unit/watcher-store.spec.js +0 -405
  84. package/tsconfig.json +0 -31
package/src/validators.ts DELETED
@@ -1,131 +0,0 @@
1
- /**
2
- * Validation functions for UI Watcher registration parameters
3
- */
4
-
5
- /**
6
- * Valid Appium locator strategies
7
- */
8
- const VALID_LOCATOR_STRATEGIES = [
9
- 'id',
10
- 'accessibility id',
11
- 'class name',
12
- 'xpath',
13
- 'name',
14
- '-android uiautomator',
15
- '-ios predicate string',
16
- '-ios class chain',
17
- 'css selector',
18
- ];
19
-
20
- /**
21
- * Validates a locator object
22
- * @param locator - The locator to validate
23
- * @param fieldName - Name of the field for error messages
24
- * @throws Error if locator is invalid
25
- */
26
- export function validateLocator(locator: any, fieldName: string): void {
27
- // Check if locator exists
28
- if (!locator || typeof locator !== 'object') {
29
- throw new Error(`${fieldName} is required`);
30
- }
31
-
32
- // Check if 'using' field exists and is a string
33
- if (!locator.using) {
34
- throw new Error(`'using' is mandatory for ${fieldName}`);
35
- }
36
-
37
- if (typeof locator.using !== 'string') {
38
- throw new Error(`'using' must be string for ${fieldName} `);
39
- }
40
-
41
- // Check if 'value' field exists and is a string
42
- if (!locator.value) {
43
- throw new Error(`'value' is mandatory for ${fieldName}`);
44
- }
45
-
46
- if (typeof locator.value !== 'string') {
47
- throw new Error(`'value' must be string for ${fieldName} `);
48
- }
49
-
50
- // Validate locator strategy (case-insensitive)
51
- const strategy = locator.using.toLowerCase();
52
- if (!VALID_LOCATOR_STRATEGIES.includes(strategy)) {
53
- throw new Error(`Invalid locator strategy for ${fieldName}`);
54
- }
55
- }
56
-
57
- /**
58
- * Validates all watcher registration parameters
59
- * @param params - Watcher registration parameters
60
- * @param maxDurationMs - Maximum allowed duration in milliseconds
61
- * @throws Error if any validation rule fails
62
- */
63
- export function validateWatcherParams(params: any, maxDurationMs: number): void {
64
- // Check if params exists
65
- if (!params || typeof params !== 'object') {
66
- throw new Error('Invalid watcher parameters');
67
- }
68
-
69
- // Validate required field: name
70
- if (!params.name) {
71
- throw new Error('UIWatcher name is required');
72
- }
73
-
74
- if (typeof params.name !== 'string') {
75
- throw new Error('UIWatcher name must be string type');
76
- }
77
-
78
- if (params.name.trim() === '') {
79
- throw new Error('UIWatcher name is empty');
80
- }
81
-
82
- // Validate required field: referenceLocator
83
- if (!params.referenceLocator) {
84
- throw new Error('UIWatcher referenceLocator is required');
85
- }
86
- validateLocator(params.referenceLocator, 'referenceLocator');
87
-
88
- // Validate required field: actionLocator
89
- if (!params.actionLocator) {
90
- throw new Error('UIWatcher actionLocator is required');
91
- }
92
- validateLocator(params.actionLocator, 'actionLocator');
93
-
94
- // Validate required field: duration
95
- if (params.duration === undefined || params.duration === null) {
96
- throw new Error('UIWatcher duration is required');
97
- }
98
-
99
- if (typeof params.duration !== 'number' || params.duration <= 0) {
100
- throw new Error('UIWatcher duration must be a positive number');
101
- }
102
-
103
- if (params.duration > maxDurationMs) {
104
- throw new Error(`UIWatcher duration must be ≤ ${maxDurationMs / 1000} seconds`);
105
- }
106
-
107
- // Validate optional field: priority (if provided)
108
- if (params.priority !== undefined && params.priority !== null) {
109
- if (typeof params.priority !== 'number') {
110
- throw new Error('UIWatcher priority must be a number');
111
- }
112
- }
113
-
114
- // Validate optional field: stopOnFound (if provided)
115
- if (params.stopOnFound !== undefined && params.stopOnFound !== null) {
116
- if (typeof params.stopOnFound !== 'boolean') {
117
- throw new Error('UIWatcher stopOnFound must be a boolean');
118
- }
119
- }
120
-
121
- // Validate optional field: cooldownMs (if provided)
122
- if (params.cooldownMs !== undefined && params.cooldownMs !== null) {
123
- if (typeof params.cooldownMs !== 'number') {
124
- throw new Error('UIWatcher cooldownMs must be a number');
125
- }
126
-
127
- if (params.cooldownMs < 0) {
128
- throw new Error('UIWatcher cooldownMs must be ≥ 0');
129
- }
130
- }
131
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * Core watcher checking algorithm
3
- */
4
-
5
- /* global setTimeout */
6
-
7
- import { logger } from '@appium/support';
8
- import type { WatcherStore } from './watcher-store.js';
9
-
10
- const log = logger.getLogger('AppiumUIWatchers');
11
-
12
- /**
13
- * Check all active watchers and execute actions for matching elements
14
- * @param driver - Appium driver instance
15
- * @param store - Watcher store instance
16
- * @returns true if at least one watcher was successfully triggered, false otherwise
17
- */
18
- export async function checkWatchers(driver: any, store: WatcherStore): Promise<boolean> {
19
- // Track if any watcher was triggered
20
- let watcherTriggered = false;
21
-
22
- // Check if watchers are globally disabled
23
- if (!store.isEnabled()) {
24
- log.debug('[UIWatchers] Watcher checking is disabled, skipping');
25
- return false;
26
- }
27
-
28
- // Get active, sorted watchers (by priority desc, then FIFO)
29
- const watchers = store.getSortedWatchers();
30
-
31
- // Early return if no active watchers
32
- if (watchers.length === 0) {
33
- log.debug('[UIWatchers] No active watchers to check');
34
- return false;
35
- }
36
-
37
- log.debug(`[UIWatchers] Checking ${watchers.length} active watchers`);
38
-
39
- // Check each watcher in priority order
40
- for (const watcher of watchers) {
41
- // Skip inactive watchers (marked inactive by stopOnFound)
42
- if (watcher.status === 'inactive') {
43
- log.debug(`[UIWatchers] Skipping inactive watcher '${watcher.name}'`);
44
- continue;
45
- }
46
-
47
- log.debug(`[UIWatchers] Checking watcher '${watcher.name}' (priority=${watcher.priority})`);
48
-
49
- try {
50
- // Try to find the reference element
51
- try {
52
- await driver.findElement(watcher.referenceLocator.using, watcher.referenceLocator.value);
53
- } catch {
54
- // Reference element not found - continue to next watcher
55
- log.debug(`[UIWatchers] Watcher '${watcher.name}': Reference element not found`);
56
- continue;
57
- }
58
-
59
- // Reference element found
60
- log.debug(`[UIWatchers] Watcher '${watcher.name}': Reference element found`);
61
-
62
- // Try to find and click the action element
63
- try {
64
- const actionElement = await driver.findElement(
65
- watcher.actionLocator.using,
66
- watcher.actionLocator.value
67
- );
68
-
69
- // Click the action element
70
- await driver.click(actionElement.ELEMENT || actionElement);
71
-
72
- // Action click succeeded
73
- log.info(`[UIWatchers] UIWatcher '${watcher.name}' triggered successfully`);
74
- watcherTriggered = true;
75
-
76
- // Update trigger statistics
77
- store.incrementTriggerCount(watcher.name);
78
-
79
- // Mark as inactive if stopOnFound is true
80
- if (watcher.stopOnFound) {
81
- store.markInactive(watcher.name);
82
- log.debug(`[UIWatchers] Watcher '${watcher.name}' marked inactive (stopOnFound=true)`);
83
- }
84
-
85
- // Execute cooldown wait if configured
86
- if (watcher.cooldownMs > 0) {
87
- log.debug(
88
- `[UIWatchers] Watcher '${watcher.name}': Executing cooldown wait (${watcher.cooldownMs}ms)`
89
- );
90
- await new Promise((resolve) => setTimeout(resolve, watcher.cooldownMs));
91
- log.debug(`[UIWatchers] Watcher '${watcher.name}': Cooldown complete`);
92
- }
93
- } catch (clickError: any) {
94
- // Action click failed
95
- log.warn(
96
- `[UIWatchers] UIWatcher '${watcher.name}' action click failed: ${clickError.message}`
97
- );
98
- // Continue to next watcher (don't execute cooldown or mark inactive)
99
- continue;
100
- }
101
- } catch (error: any) {
102
- // Unexpected error during watcher checking
103
- log.error(
104
- `[UIWatchers] Unexpected error checking watcher '${watcher.name}': ${error.message}`
105
- );
106
- // Continue to next watcher
107
- continue;
108
- }
109
- }
110
-
111
- log.debug(`[UIWatchers] Watcher checking complete (triggered=${watcherTriggered})`);
112
- return watcherTriggered;
113
- }
@@ -1,210 +0,0 @@
1
- /**
2
- * WatcherStore - In-memory storage for registered UI watchers
3
- */
4
-
5
- import type { WatcherState, UIWatcher } from './types.js';
6
- import type { PluginConfig } from './config.js';
7
-
8
- /**
9
- * WatcherStore manages the lifecycle and state of all registered UI watchers
10
- */
11
- export class WatcherStore {
12
- /** Internal storage for watchers (keyed by watcher name) */
13
- private watchers: Map<string, WatcherState>;
14
-
15
- /** Global enable/disable flag for watcher checking */
16
- private enabled: boolean;
17
-
18
- /** Plugin configuration */
19
- private config: Required<PluginConfig>;
20
-
21
- constructor(config: Required<PluginConfig>) {
22
- this.watchers = new Map();
23
- this.enabled = true;
24
- this.config = config;
25
- }
26
-
27
- /**
28
- * Get the plugin configuration
29
- * @returns The plugin configuration
30
- */
31
- getConfig(): Required<PluginConfig> {
32
- return this.config;
33
- }
34
-
35
- /**
36
- * Add a new watcher to the store
37
- * @param watcher - Watcher registration parameters
38
- * @returns The created watcher state
39
- * @throws Error if validation fails
40
- */
41
- add(watcher: UIWatcher): WatcherState {
42
- // Check for duplicate name
43
- if (this.watchers.has(watcher.name)) {
44
- throw new Error(`UIWatcher with name '${watcher.name}' already exists`);
45
- }
46
-
47
- // Check maximum watcher limit (count only non-expired watchers)
48
- const activeCount = this.getActiveWatchers().length;
49
- if (activeCount >= this.config.maxWatchers) {
50
- throw new Error(`Maximum ${this.config.maxWatchers} UI watchers allowed per session`);
51
- }
52
-
53
- // Create watcher state with computed fields
54
- const now = Date.now();
55
- const watcherState: WatcherState = {
56
- name: watcher.name,
57
- priority: watcher.priority ?? 0,
58
- referenceLocator: watcher.referenceLocator,
59
- actionLocator: watcher.actionLocator,
60
- duration: watcher.duration,
61
- stopOnFound: watcher.stopOnFound ?? false,
62
- cooldownMs: watcher.cooldownMs ?? 0,
63
- registeredAt: now,
64
- expiresAt: now + watcher.duration,
65
- status: 'active',
66
- triggerCount: 0,
67
- lastTriggeredAt: null,
68
- };
69
-
70
- this.watchers.set(watcher.name, watcherState);
71
- return watcherState;
72
- }
73
-
74
- /**
75
- * Remove a watcher by name
76
- * @param name - Name of the watcher to remove
77
- * @returns True if watcher was removed, false if not found
78
- */
79
- remove(name: string): boolean {
80
- return this.watchers.delete(name);
81
- }
82
-
83
- /**
84
- * Get a watcher by name
85
- * @param name - Name of the watcher to retrieve
86
- * @returns The watcher state, or undefined if not found
87
- */
88
- get(name: string): WatcherState | undefined {
89
- return this.watchers.get(name);
90
- }
91
-
92
- /**
93
- * Get all watchers (including expired ones)
94
- * @returns Array of all watchers
95
- */
96
- list(): WatcherState[] {
97
- return Array.from(this.watchers.values());
98
- }
99
-
100
- /**
101
- * Get only active (non-expired) watchers
102
- * Automatically removes expired watchers from storage
103
- * @returns Array of active watchers
104
- */
105
- getActiveWatchers(): WatcherState[] {
106
- const now = Date.now();
107
- const activeWatchers: WatcherState[] = [];
108
- const expiredNames: string[] = [];
109
-
110
- // Identify active and expired watchers
111
- for (const [name, watcher] of this.watchers.entries()) {
112
- if (now >= watcher.expiresAt) {
113
- expiredNames.push(name);
114
- } else {
115
- activeWatchers.push(watcher);
116
- }
117
- }
118
-
119
- // Remove expired watchers
120
- for (const name of expiredNames) {
121
- this.watchers.delete(name);
122
- }
123
-
124
- return activeWatchers;
125
- }
126
-
127
- /**
128
- * Get active watchers sorted by priority (descending) and registration time (FIFO)
129
- * @returns Array of sorted active watchers
130
- */
131
- getSortedWatchers(): WatcherState[] {
132
- const activeWatchers = this.getActiveWatchers();
133
-
134
- return activeWatchers.sort((a, b) => {
135
- // Sort by priority descending (higher priority first)
136
- if (a.priority !== b.priority) {
137
- return b.priority - a.priority;
138
- }
139
-
140
- // If priority is the same, sort by registration time ascending (FIFO)
141
- return a.registeredAt - b.registeredAt;
142
- });
143
- }
144
-
145
- /**
146
- * Clear all watchers from the store
147
- * @returns Number of watchers removed
148
- */
149
- clear(): number {
150
- const count = this.watchers.size;
151
- this.watchers.clear();
152
- return count;
153
- }
154
-
155
- /**
156
- * Mark a watcher as inactive (used for stopOnFound)
157
- * @param name - Name of the watcher to mark inactive
158
- */
159
- markInactive(name: string): void {
160
- const watcher = this.watchers.get(name);
161
- if (watcher) {
162
- watcher.status = 'inactive';
163
- }
164
- }
165
-
166
- /**
167
- * Increment the trigger count for a watcher
168
- * @param name - Name of the watcher
169
- */
170
- incrementTriggerCount(name: string): void {
171
- const watcher = this.watchers.get(name);
172
- if (watcher) {
173
- watcher.triggerCount++;
174
- watcher.lastTriggeredAt = Date.now();
175
- }
176
- }
177
-
178
- /**
179
- * Update the last triggered timestamp for a watcher
180
- * @param name - Name of the watcher
181
- */
182
- updateLastTriggered(name: string): void {
183
- const watcher = this.watchers.get(name);
184
- if (watcher) {
185
- watcher.lastTriggeredAt = Date.now();
186
- }
187
- }
188
-
189
- /**
190
- * Disable watcher checking globally
191
- */
192
- disable(): void {
193
- this.enabled = false;
194
- }
195
-
196
- /**
197
- * Enable watcher checking globally
198
- */
199
- enable(): void {
200
- this.enabled = true;
201
- }
202
-
203
- /**
204
- * Check if watcher checking is enabled
205
- * @returns True if enabled, false otherwise
206
- */
207
- isEnabled(): boolean {
208
- return this.enabled;
209
- }
210
- }