@appium/base-driver 10.6.0 → 10.7.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 (157) hide show
  1. package/build/lib/basedriver/capabilities.d.ts +1 -1
  2. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  3. package/build/lib/basedriver/capabilities.js +15 -7
  4. package/build/lib/basedriver/capabilities.js.map +1 -1
  5. package/build/lib/basedriver/commands/bidi.js +1 -1
  6. package/build/lib/basedriver/commands/event.js.map +1 -1
  7. package/build/lib/basedriver/commands/execute.js.map +1 -1
  8. package/build/lib/basedriver/commands/find.d.ts.map +1 -1
  9. package/build/lib/basedriver/commands/find.js +2 -1
  10. package/build/lib/basedriver/commands/find.js.map +1 -1
  11. package/build/lib/basedriver/commands/timeout.js +4 -4
  12. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  13. package/build/lib/basedriver/core.d.ts.map +1 -1
  14. package/build/lib/basedriver/core.js +5 -2
  15. package/build/lib/basedriver/core.js.map +1 -1
  16. package/build/lib/basedriver/device-settings.d.ts.map +1 -1
  17. package/build/lib/basedriver/device-settings.js.map +1 -1
  18. package/build/lib/basedriver/driver.d.ts.map +1 -1
  19. package/build/lib/basedriver/driver.js +23 -24
  20. package/build/lib/basedriver/driver.js.map +1 -1
  21. package/build/lib/basedriver/extension-core.d.ts.map +1 -1
  22. package/build/lib/basedriver/extension-core.js +11 -5
  23. package/build/lib/basedriver/extension-core.js.map +1 -1
  24. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  25. package/build/lib/basedriver/helpers.js +20 -4
  26. package/build/lib/basedriver/helpers.js.map +1 -1
  27. package/build/lib/basedriver/ipc.d.ts.map +1 -1
  28. package/build/lib/basedriver/ipc.js +6 -4
  29. package/build/lib/basedriver/ipc.js.map +1 -1
  30. package/build/lib/basedriver/validation.d.ts.map +1 -1
  31. package/build/lib/basedriver/validation.js +3 -2
  32. package/build/lib/basedriver/validation.js.map +1 -1
  33. package/build/lib/express/express-logging.d.ts +0 -1
  34. package/build/lib/express/express-logging.d.ts.map +1 -1
  35. package/build/lib/express/express-logging.js +9 -8
  36. package/build/lib/express/express-logging.js.map +1 -1
  37. package/build/lib/express/idempotency.js.map +1 -1
  38. package/build/lib/express/middleware.d.ts.map +1 -1
  39. package/build/lib/express/middleware.js.map +1 -1
  40. package/build/lib/express/server.d.ts +1 -1
  41. package/build/lib/express/server.d.ts.map +1 -1
  42. package/build/lib/express/server.js +19 -20
  43. package/build/lib/express/server.js.map +1 -1
  44. package/build/lib/express/websocket.d.ts.map +1 -1
  45. package/build/lib/express/websocket.js.map +1 -1
  46. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  47. package/build/lib/helpers/capabilities.js.map +1 -1
  48. package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
  49. package/build/lib/helpers/levenshtein-match.js +4 -1
  50. package/build/lib/helpers/levenshtein-match.js.map +1 -1
  51. package/build/lib/index.d.ts +1 -1
  52. package/build/lib/index.d.ts.map +1 -1
  53. package/build/lib/index.js +3 -2
  54. package/build/lib/index.js.map +1 -1
  55. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  56. package/build/lib/jsonwp-proxy/protocol-converter.js +14 -7
  57. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  58. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  59. package/build/lib/jsonwp-proxy/proxy.js +17 -11
  60. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  61. package/build/lib/protocol/errors.d.ts.map +1 -1
  62. package/build/lib/protocol/errors.js +13 -13
  63. package/build/lib/protocol/errors.js.map +1 -1
  64. package/build/lib/protocol/protocol.d.ts +1 -1
  65. package/build/lib/protocol/protocol.d.ts.map +1 -1
  66. package/build/lib/protocol/protocol.js +35 -18
  67. package/build/lib/protocol/protocol.js.map +1 -1
  68. package/build/lib/protocol/routes.d.ts.map +1 -1
  69. package/build/lib/protocol/routes.js +7 -5
  70. package/build/lib/protocol/routes.js.map +1 -1
  71. package/build/lib/test-pages/crash.d.ts.map +1 -0
  72. package/build/lib/test-pages/crash.js.map +1 -0
  73. package/build/lib/test-pages/env.d.ts +5 -0
  74. package/build/lib/test-pages/env.d.ts.map +1 -0
  75. package/build/lib/test-pages/env.js +12 -0
  76. package/build/lib/test-pages/env.js.map +1 -0
  77. package/build/lib/{express/static.d.ts → test-pages/handlers.d.ts} +1 -2
  78. package/build/lib/test-pages/handlers.d.ts.map +1 -0
  79. package/build/lib/{express/static.js → test-pages/handlers.js} +7 -17
  80. package/build/lib/test-pages/handlers.js.map +1 -0
  81. package/build/lib/test-pages/index.d.ts +6 -0
  82. package/build/lib/test-pages/index.d.ts.map +1 -0
  83. package/build/lib/test-pages/index.js +35 -0
  84. package/build/lib/test-pages/index.js.map +1 -0
  85. package/build/lib/test-pages/static-dir.d.ts +8 -0
  86. package/build/lib/test-pages/static-dir.d.ts.map +1 -0
  87. package/build/lib/test-pages/static-dir.js +24 -0
  88. package/build/lib/test-pages/static-dir.js.map +1 -0
  89. package/build/lib/test-pages/template.d.ts +3 -0
  90. package/build/lib/test-pages/template.d.ts.map +1 -0
  91. package/build/lib/test-pages/template.js +19 -0
  92. package/build/lib/test-pages/template.js.map +1 -0
  93. package/build/lib/utils.d.ts +0 -2
  94. package/build/lib/utils.d.ts.map +1 -1
  95. package/build/lib/utils.js +0 -16
  96. package/build/lib/utils.js.map +1 -1
  97. package/lib/basedriver/capabilities.ts +72 -66
  98. package/lib/basedriver/commands/bidi.ts +1 -1
  99. package/lib/basedriver/commands/event.ts +10 -5
  100. package/lib/basedriver/commands/execute.ts +12 -9
  101. package/lib/basedriver/commands/find.ts +20 -12
  102. package/lib/basedriver/commands/log.ts +2 -2
  103. package/lib/basedriver/commands/timeout.ts +17 -8
  104. package/lib/basedriver/core.ts +14 -14
  105. package/lib/basedriver/device-settings.ts +4 -8
  106. package/lib/basedriver/driver.ts +50 -40
  107. package/lib/basedriver/extension-core.ts +33 -17
  108. package/lib/basedriver/helpers.ts +57 -26
  109. package/lib/basedriver/ipc.ts +37 -18
  110. package/lib/basedriver/validation.ts +13 -6
  111. package/lib/express/express-logging.ts +14 -17
  112. package/lib/express/idempotency.ts +6 -6
  113. package/lib/express/middleware.ts +10 -12
  114. package/lib/express/server.ts +53 -61
  115. package/lib/express/websocket.ts +5 -7
  116. package/lib/helpers/capabilities.ts +5 -4
  117. package/lib/helpers/extension-command-name.ts +1 -1
  118. package/lib/helpers/levenshtein-match.ts +20 -11
  119. package/lib/index.js +2 -1
  120. package/lib/jsonwp-proxy/protocol-converter.ts +51 -27
  121. package/lib/jsonwp-proxy/proxy.ts +42 -42
  122. package/lib/protocol/errors.ts +47 -67
  123. package/lib/protocol/protocol.ts +116 -72
  124. package/lib/protocol/routes.ts +9 -9
  125. package/lib/test-pages/env.ts +9 -0
  126. package/lib/{express/static.ts → test-pages/handlers.ts} +7 -27
  127. package/lib/test-pages/index.ts +34 -0
  128. package/lib/test-pages/static-dir.ts +19 -0
  129. package/lib/test-pages/template.ts +17 -0
  130. package/lib/utils.ts +3 -23
  131. package/package.json +9 -10
  132. package/tsconfig.json +1 -0
  133. package/build/lib/express/crash.d.ts.map +0 -1
  134. package/build/lib/express/crash.js.map +0 -1
  135. package/build/lib/express/static.d.ts.map +0 -1
  136. package/build/lib/express/static.js.map +0 -1
  137. /package/build/lib/{express → test-pages}/crash.d.ts +0 -0
  138. /package/build/lib/{express → test-pages}/crash.js +0 -0
  139. /package/lib/{express → test-pages}/crash.ts +0 -0
  140. /package/{static → test-fixtures/static}/appium.png +0 -0
  141. /package/{static → test-fixtures/static}/favicon.ico +0 -0
  142. /package/{static → test-fixtures/static}/js/jquery.min.js +0 -0
  143. /package/{static → test-fixtures/static}/test/frameset.html +0 -0
  144. /package/{static → test-fixtures/static}/test/guinea-pig-app-banner.html +0 -0
  145. /package/{static → test-fixtures/static}/test/guinea-pig-scrollable.html +0 -0
  146. /package/{static → test-fixtures/static}/test/guinea-pig.html +0 -0
  147. /package/{static → test-fixtures/static}/test/guinea-pig2.html +0 -0
  148. /package/{static → test-fixtures/static}/test/guinea-pig3.html +0 -0
  149. /package/{static → test-fixtures/static}/test/guinea-pig4.html +0 -0
  150. /package/{static → test-fixtures/static}/test/guinea-pig5.html +0 -0
  151. /package/{static → test-fixtures/static}/test/iframes.html +0 -0
  152. /package/{static → test-fixtures/static}/test/shadow-dom.html +0 -0
  153. /package/{static → test-fixtures/static}/test/subframe1.html +0 -0
  154. /package/{static → test-fixtures/static}/test/subframe2.html +0 -0
  155. /package/{static → test-fixtures/static}/test/subframe3.html +0 -0
  156. /package/{static → test-fixtures/static}/test/touch.html +0 -0
  157. /package/{static → test-fixtures/static}/test/welcome.html +0 -0
@@ -1,5 +1,13 @@
1
1
  import {log} from './logger';
2
- import type {StringRecord, IIpcSubscription, IAppiumIpc, IpcMessage, IpcEvent, AppiumLogger, IpcData} from '@appium/types';
2
+ import type {
3
+ StringRecord,
4
+ IIpcSubscription,
5
+ IAppiumIpc,
6
+ IpcMessage,
7
+ IpcEvent,
8
+ AppiumLogger,
9
+ IpcData,
10
+ } from '@appium/types';
3
11
  import EventEmitter from 'node:events';
4
12
  import {sleep} from 'asyncbox';
5
13
  import {node} from '@appium/support';
@@ -18,12 +26,14 @@ export type AppiumIpcOpts = {
18
26
 
19
27
  const ASYNC_ITERATOR_STOP = Symbol('asyncIteratorStop');
20
28
 
21
- export class IpcSubscription<T extends IpcData> extends EventEmitter<IpcEvent<T>> implements IIpcSubscription<T> {
22
-
29
+ export class IpcSubscription<T extends IpcData>
30
+ extends EventEmitter<IpcEvent<T>>
31
+ implements IIpcSubscription<T>
32
+ {
23
33
  constructor(
24
34
  public readonly subscriber: string,
25
35
  public readonly topic: string,
26
- private readonly ipc: AppiumIpc
36
+ private readonly ipc: AppiumIpc,
27
37
  ) {
28
38
  super();
29
39
  }
@@ -87,18 +97,22 @@ export class AppiumIpc implements IAppiumIpc {
87
97
  protected readonly maxTopics: number;
88
98
  protected readonly log: AppiumLogger;
89
99
 
90
- constructor (opts: AppiumIpcOpts = {}) {
100
+ constructor(opts: AppiumIpcOpts = {}) {
91
101
  this.maxObjSize = opts.maxObjSize ?? DEF_MAX_OBJ_SIZE_BYTES;
92
102
  this.maxTopics = opts.maxTopics ?? DEF_MAX_TOPICS;
93
103
  this.log = opts.log ?? log;
94
- this.log.debug(`Initialized new IPC object with max object size of ${this.maxObjSize} bytes ` +
95
- `and max topics of ${this.maxTopics}`);
104
+ this.log.debug(
105
+ `Initialized new IPC object with max object size of ${this.maxObjSize} bytes ` +
106
+ `and max topics of ${this.maxTopics}`,
107
+ );
96
108
  }
97
109
 
98
110
  subscribe<T extends IpcData>(topic: string, subscriber: string): IpcSubscription<T> {
99
111
  this.log.info(`Subscribing ${subscriber} to topic '${topic}'`);
100
112
  if (this.subscriptionExists(topic, subscriber)) {
101
- throw new Error(`Subscription already exists for topic "${topic}" and subscriber "${subscriber}"`);
113
+ throw new Error(
114
+ `Subscription already exists for topic "${topic}" and subscriber "${subscriber}"`,
115
+ );
102
116
  }
103
117
 
104
118
  this.ensureTopic(topic);
@@ -124,24 +138,28 @@ export class AppiumIpc implements IAppiumIpc {
124
138
 
125
139
  const messageSize = node.getObjectSize(data);
126
140
  if (messageSize > this.maxObjSize) {
127
- throw new Error(`Error when ${publisher} is publishing to topic '${topic}': ` +
128
- `Message with size ${messageSize} bytes is bigger than max size of ${this.maxObjSize} bytes`);
141
+ throw new Error(
142
+ `Error when ${publisher} is publishing to topic '${topic}': ` +
143
+ `Message with size ${messageSize} bytes is bigger than max size of ${this.maxObjSize} bytes`,
144
+ );
129
145
  }
130
146
 
131
147
  let clonedData: T;
132
148
  try {
133
149
  clonedData = structuredClone(data);
134
150
  } catch (e) {
135
- throw new Error(`Could not clone data for IPC publish from ${publisher} on topic ${topic}`, {cause: e});
151
+ throw new Error(`Could not clone data for IPC publish from ${publisher} on topic ${topic}`, {
152
+ cause: e,
153
+ });
136
154
  }
137
155
 
138
156
  const message: IpcMessage<T> = {publisher, data: clonedData, topic, timestampMs: Date.now()};
139
157
 
140
158
  this.messageByTopic[topic] = message;
141
159
 
142
- const subs = this.subs[topic] ?
143
- this.subs[topic].filter((sub) => sub.subscriber !== publisher) :
144
- [];
160
+ const subs = this.subs[topic]
161
+ ? this.subs[topic].filter((sub) => sub.subscriber !== publisher)
162
+ : [];
145
163
 
146
164
  for (const sub of subs) {
147
165
  sub.emit(EVT_MESSAGE, structuredClone(message));
@@ -150,7 +168,6 @@ export class AppiumIpc implements IAppiumIpc {
150
168
  // we don't want to return from publish until the async iterators on subscriptions have had
151
169
  // a chance to observe the emitted value, otherwise some might get lost
152
170
  await sleep(0);
153
-
154
171
  }
155
172
 
156
173
  getMessage<T extends IpcData>(topic: string): IpcMessage<T> | undefined {
@@ -170,9 +187,11 @@ export class AppiumIpc implements IAppiumIpc {
170
187
  return;
171
188
  }
172
189
  if (this.topics.size >= this.maxTopics) {
173
- throw new Error(`Cannot create new IPC topic '${topic}': ` +
174
- `maximum of ${this.maxTopics} topics per session reached. ` +
175
- `Adjust with the --max-ipc-topics server arg.`);
190
+ throw new Error(
191
+ `Cannot create new IPC topic '${topic}': ` +
192
+ `maximum of ${this.maxTopics} topics per session reached. ` +
193
+ `Adjust with the --max-ipc-topics server arg.`,
194
+ );
176
195
  }
177
196
  this.topics.add(topic);
178
197
  }
@@ -77,7 +77,7 @@ export class Validator {
77
77
  if (value !== undefined && options) {
78
78
  log.warn(
79
79
  `The '${key}' capability has been deprecated and must not be used anymore. ` +
80
- `Please check the driver documentation for possible alternatives.`
80
+ `Please check the driver documentation for possible alternatives.`,
81
81
  );
82
82
  }
83
83
  return null;
@@ -108,15 +108,19 @@ export class Validator {
108
108
  }
109
109
  if (
110
110
  !options?.allowEmpty &&
111
- ((value !== undefined && util.isEmpty(value)) || (typeof value === 'string' && !value.trim()))
111
+ ((value !== undefined && util.isEmpty(value)) ||
112
+ (typeof value === 'string' && !value.trim()))
112
113
  ) {
113
114
  return 'must not be empty or blank';
114
115
  }
115
116
  return null;
116
- }
117
+ },
117
118
  };
118
119
 
119
- validate(values: Record<string, any>, constraints: Record<string, Constraint>): Record<string, string[]> | null {
120
+ validate(
121
+ values: Record<string, any>,
122
+ constraints: Record<string, Constraint>,
123
+ ): Record<string, string[]> | null {
120
124
  const result: Record<string, string[]> = {};
121
125
  for (const [key, constraint] of Object.entries(constraints)) {
122
126
  const value = values[key];
@@ -125,7 +129,11 @@ export class Validator {
125
129
  continue;
126
130
  }
127
131
 
128
- const validationError = this._validators[validatorName as keyof Constraint](value, options, key);
132
+ const validationError = this._validators[validatorName as keyof Constraint](
133
+ value,
134
+ options,
135
+ key,
136
+ );
129
137
  if (validationError == null) {
130
138
  continue;
131
139
  }
@@ -139,7 +147,6 @@ export class Validator {
139
147
  }
140
148
  return util.isEmpty(result) ? null : result;
141
149
  }
142
-
143
150
  }
144
151
 
145
152
  export const validator = new Validator();
@@ -1,5 +1,4 @@
1
- import {util, logger} from '@appium/support';
2
- import '@colors/colors';
1
+ import {console, logger, util} from '@appium/support';
3
2
  import morgan from 'morgan';
4
3
  import type {Request, RequestHandler, Response} from 'express';
5
4
  import {log} from './logger';
@@ -23,17 +22,18 @@ export const startLogFormatter: RequestHandler = morgan(startLogFormatterHandler
23
22
  type MorganTokens = unknown;
24
23
  type FormatFn = (tokens: MorganTokens, req: Request, res: Response) => string;
25
24
 
26
- function endLogFormatterHandler(tokens: MorganTokens, req: Request, res: Response): void {
25
+ function endLogFormatterHandler(tokens: MorganTokens, req: Request, res: Response): undefined {
27
26
  log.info(requestEndLoggingFormat(tokens, req, res));
27
+ return undefined;
28
28
  }
29
29
 
30
- function startLogFormatterHandler(tokens: unknown, req: Request, res: Response): void {
30
+ function startLogFormatterHandler(tokens: unknown, req: Request, res: Response): undefined {
31
31
  let reqBody = '';
32
32
  if (req.body) {
33
33
  try {
34
34
  reqBody = util.truncateString(
35
35
  typeof req.body === 'string' ? req.body : JSON.stringify(req.body),
36
- {length: MAX_LOG_BODY_LENGTH}
36
+ {length: MAX_LOG_BODY_LENGTH},
37
37
  );
38
38
  } catch {
39
39
  // ignore
@@ -41,8 +41,9 @@ function startLogFormatterHandler(tokens: unknown, req: Request, res: Response):
41
41
  }
42
42
  log.info(
43
43
  requestStartLoggingFormat(tokens, req, res),
44
- logger.markSensitive(reqBody.grey)
44
+ logger.markSensitive(console.styleText('grey', reqBody)),
45
45
  );
46
+ return undefined;
46
47
  }
47
48
 
48
49
  // Copied the morgan compile function over so that cooler formats may be configured
@@ -55,29 +56,25 @@ function compile(fmt: string): FormatFn {
55
56
  return new Function('tokens', 'req', 'res', js) as FormatFn;
56
57
  }
57
58
 
58
- function requestEndLoggingFormat(
59
- tokens: MorganTokens,
60
- req: Request,
61
- res: Response
62
- ): string {
59
+ function requestEndLoggingFormat(tokens: MorganTokens, req: Request, res: Response): string {
63
60
  const status = res.statusCode;
64
61
  let statusStr = ':status';
65
62
  if (status >= 500) {
66
- statusStr = statusStr.red;
63
+ statusStr = console.styleText('red', statusStr);
67
64
  } else if (status >= 400) {
68
- statusStr = statusStr.yellow;
65
+ statusStr = console.styleText('yellow', statusStr);
69
66
  } else if (status >= 300) {
70
- statusStr = statusStr.cyan;
67
+ statusStr = console.styleText('cyan', statusStr);
71
68
  } else {
72
- statusStr = statusStr.green;
69
+ statusStr = console.styleText('green', statusStr);
73
70
  }
74
71
  const fn = compile(
75
- `${'<-- :method :url '.white}${statusStr} ${':response-time ms - :res[content-length]'.grey}`
72
+ `${console.styleText('white', '<-- :method :url ')}${statusStr} ${console.styleText('grey', ':response-time ms - :res[content-length]')}`,
76
73
  );
77
74
  return fn(tokens, req, res);
78
75
  }
79
76
 
80
77
  const requestStartLoggingFormat = compile(
81
- `${'-->'.white} ${':method'.white} ${':url'.white}`
78
+ `${console.styleText('white', '-->')} ${console.styleText('white', ':method')} ${console.styleText('white', ':url')}`,
82
79
  );
83
80
  // #endregion
@@ -31,7 +31,7 @@ const MAX_CACHED_PAYLOAD_SIZE_BYTES = 1 * 1024 * 1024; // 1 MiB
31
31
  export async function handleIdempotency(
32
32
  req: Request,
33
33
  res: Response,
34
- next: NextFunction
34
+ next: NextFunction,
35
35
  ): Promise<void> {
36
36
  const keyOrArr = req.headers[IDEMPOTENCY_KEY_HEADER];
37
37
  if (util.isEmpty(keyOrArr) || !keyOrArr) {
@@ -114,7 +114,7 @@ function cacheResponse(key: string, req: Request, res: Response): void {
114
114
  const patchedWriter = (
115
115
  chunk: unknown,
116
116
  encoding: BufferEncoding | (() => void),
117
- next?: (() => void) | ((err?: Error) => void)
117
+ next?: (() => void) | ((err?: Error) => void),
118
118
  ): boolean => {
119
119
  if (errorMessage || !responseRef.deref()) {
120
120
  responseChunks = [];
@@ -122,7 +122,7 @@ function cacheResponse(key: string, req: Request, res: Response): void {
122
122
  return originalSocketWriter(
123
123
  chunk as string | Buffer | Uint8Array,
124
124
  encoding as BufferEncoding,
125
- next as (err?: Error) => void
125
+ next as (err?: Error | null) => void,
126
126
  );
127
127
  }
128
128
 
@@ -139,7 +139,7 @@ function cacheResponse(key: string, req: Request, res: Response): void {
139
139
  return originalSocketWriter(
140
140
  chunk as string | Buffer | Uint8Array,
141
141
  encoding as BufferEncoding,
142
- next as (err?: Error) => void
142
+ next as (err?: Error | null) => void,
143
143
  );
144
144
  };
145
145
  socket.write = patchedWriter as typeof socket.write;
@@ -153,7 +153,7 @@ function cacheResponse(key: string, req: Request, res: Response): void {
153
153
  if (!IDEMPOTENT_RESPONSES.has(key)) {
154
154
  log.info(
155
155
  `Could not cache the response identified by '${key}'. ` +
156
- `Cache consistency has been damaged`
156
+ `Cache consistency has been damaged`,
157
157
  );
158
158
  } else {
159
159
  log.info(`Could not cache the response identified by '${key}': ${errorMessage}`);
@@ -175,7 +175,7 @@ function cacheResponse(key: string, req: Request, res: Response): void {
175
175
  if (!IDEMPOTENT_RESPONSES.has(key)) {
176
176
  log.info(
177
177
  `Could not cache the response identified by '${key}'. ` +
178
- `Cache consistency has been damaged`
178
+ `Cache consistency has been damaged`,
179
179
  );
180
180
  } else if (errorMessage) {
181
181
  log.info(`Could not cache the response identified by '${key}': ${errorMessage}`);
@@ -21,7 +21,7 @@ export function allowCrossDomain(req: Request, res: Response, next: NextFunction
21
21
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
22
22
  res.header(
23
23
  'Access-Control-Allow-Headers',
24
- 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent'
24
+ 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent',
25
25
  );
26
26
 
27
27
  if (req.method === 'OPTIONS') {
@@ -42,10 +42,10 @@ export function allowCrossDomainAsyncExecute(basePath: string): RequestHandler {
42
42
  function allowCrossDomainAsyncExecuteHandler(
43
43
  req: Request,
44
44
  res: Response,
45
- next: NextFunction
45
+ next: NextFunction,
46
46
  ): void {
47
47
  const receiveAsyncResponseRegExp = new RegExp(
48
- `${util.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`
48
+ `${util.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`,
49
49
  );
50
50
  if (!receiveAsyncResponseRegExp.test(req.url)) {
51
51
  next();
@@ -72,9 +72,11 @@ export function handleLogContext(req: Request, _res: Response, next: NextFunctio
72
72
  {
73
73
  requestId,
74
74
  ...sessionInfo,
75
- isSensitive: ['true', '1', 'yes'].includes(String(isSensitiveHeaderValue ?? '').toLowerCase()),
75
+ isSensitive: ['true', '1', 'yes'].includes(
76
+ String(isSensitiveHeaderValue ?? '').toLowerCase(),
77
+ ),
76
78
  },
77
- true
79
+ true,
78
80
  );
79
81
 
80
82
  next();
@@ -83,11 +85,7 @@ export function handleLogContext(req: Request, _res: Response, next: NextFunctio
83
85
  /**
84
86
  * Ensures requests default to JSON content-type when none is provided.
85
87
  */
86
- export function defaultToJSONContentType(
87
- req: Request,
88
- _res: Response,
89
- next: NextFunction
90
- ): void {
88
+ export function defaultToJSONContentType(req: Request, _res: Response, next: NextFunction): void {
91
89
  if (!req.headers['content-type']) {
92
90
  req.headers['content-type'] = 'application/json; charset=utf-8';
93
91
  }
@@ -107,7 +105,7 @@ export function tryHandleWebSocketUpgrade(
107
105
  req: IncomingMessage,
108
106
  socket: Duplex,
109
107
  head: Buffer,
110
- webSocketsMapping: StringRecord<WSServer>
108
+ webSocketsMapping: StringRecord<WSServer>,
111
109
  ): boolean {
112
110
  if (String(req.headers?.upgrade ?? '').toLowerCase() !== 'websocket') {
113
111
  return false;
@@ -157,7 +155,7 @@ export function catchAllHandler(
157
155
  err: Error,
158
156
  _req: Request,
159
157
  res: Response,
160
- next: NextFunction
158
+ next: NextFunction,
161
159
  ): void {
162
160
  if (res.headersSent) {
163
161
  next(err);
@@ -1,9 +1,8 @@
1
- import path from 'node:path';
2
1
  import express from 'express';
3
2
  import type {Express, RequestHandler} from 'express';
4
3
  import http from 'node:http';
5
4
  import type {Server as HttpServer} from 'node:http';
6
- import favicon from 'serve-favicon';
5
+ import {createRequire} from 'node:module';
7
6
  import bodyParser from 'body-parser';
8
7
  import methodOverride from 'method-override';
9
8
  import {log} from './logger';
@@ -19,14 +18,9 @@ import {
19
18
  catch404Handler,
20
19
  handleLogContext,
21
20
  } from './middleware';
22
- import {
23
- guineaPig,
24
- guineaPigScrollable,
25
- guineaPigAppBanner,
26
- welcome,
27
- STATIC_DIR,
28
- } from './static';
29
- import {produceError, produceCrash} from './crash';
21
+ // Import env helper directly — not from the test-pages barrel — so Express handlers and
22
+ // fixture code stay unloaded unless APPIUM_ENABLE_LEGACY_TEST_PAGES is set.
23
+ import {isLegacyTestPagesEnabled} from '../test-pages/env';
30
24
  import {
31
25
  addWebSocketHandler,
32
26
  removeWebSocketHandler,
@@ -53,10 +47,7 @@ export interface RouteConfiguringFunctionOpts {
53
47
  }
54
48
 
55
49
  /** A function which configures routes */
56
- export type RouteConfiguringFunction = (
57
- app: Express,
58
- opts?: RouteConfiguringFunctionOpts
59
- ) => void;
50
+ export type RouteConfiguringFunction = (app: Express, opts?: RouteConfiguringFunctionOpts) => void;
60
51
 
61
52
  /** Options for {@linkcode server} */
62
53
  export interface ServerOpts {
@@ -83,6 +74,12 @@ export interface ConfigureServerOpts {
83
74
  useLegacyUpgradeHandler?: boolean;
84
75
  }
85
76
 
77
+ /** @internal */
78
+ export interface ConfigureServerInternalOpts extends ConfigureServerOpts {
79
+ /** @deprecated Appium 4 */
80
+ registerTestPages?: (app: Express, opts: {basePath: string}) => void;
81
+ }
82
+
86
83
  /** Options for {@linkcode configureHttp} */
87
84
  export interface ConfigureHttpOpts {
88
85
  httpServer: HttpServer;
@@ -134,6 +131,11 @@ export async function server(opts: ServerOpts): Promise<AppiumServer> {
134
131
  gracefulShutdownTimeout: cliArgs.shutdownTimeout,
135
132
  });
136
133
  const useLegacyUpgradeHandler = !hasShouldUpgradeCallback(httpServer);
134
+ let registerTestPages: ConfigureServerInternalOpts['registerTestPages'];
135
+ if (isLegacyTestPagesEnabled()) {
136
+ const require = createRequire(__filename);
137
+ registerTestPages = require('../test-pages').registerTestPages;
138
+ }
137
139
  configureServer({
138
140
  app,
139
141
  addRoutes: routeConfiguringFunction,
@@ -142,7 +144,8 @@ export async function server(opts: ServerOpts): Promise<AppiumServer> {
142
144
  extraMethodMap,
143
145
  webSocketsMapping: appiumServer.webSocketsMapping,
144
146
  useLegacyUpgradeHandler,
145
- });
147
+ registerTestPages,
148
+ } as ConfigureServerInternalOpts);
146
149
  // allow extensions to update the app and http server objects
147
150
  for (const updater of serverUpdaters) {
148
151
  await updater(app, appiumServer, cliArgs);
@@ -174,27 +177,25 @@ export async function server(opts: ServerOpts): Promise<AppiumServer> {
174
177
  *
175
178
  * @param opts - Configuration options
176
179
  */
177
- export function configureServer({
178
- app,
179
- addRoutes,
180
- allowCors = true,
181
- basePath = DEFAULT_BASE_PATH,
182
- extraMethodMap = {},
183
- webSocketsMapping = {},
184
- useLegacyUpgradeHandler = true,
185
- }: ConfigureServerOpts): void {
186
- basePath = normalizeBasePath(basePath);
180
+ export function configureServer(opts: ConfigureServerOpts): void {
181
+ const {
182
+ app,
183
+ addRoutes,
184
+ allowCors = true,
185
+ basePath: rawBasePath = DEFAULT_BASE_PATH,
186
+ extraMethodMap = {},
187
+ webSocketsMapping = {},
188
+ useLegacyUpgradeHandler = true,
189
+ } = opts;
190
+ const {registerTestPages} = opts as ConfigureServerInternalOpts;
191
+ const basePath = normalizeBasePath(rawBasePath);
187
192
 
188
193
  app.use(endLogFormatter);
189
194
  app.use(handleLogContext);
190
195
 
191
- // set up static assets
192
- app.use(favicon(path.resolve(STATIC_DIR, 'favicon.ico')));
193
- app.use(express.static(STATIC_DIR));
194
-
195
- // crash routes, for testing
196
- app.use(`${basePath}/produce_error`, produceError);
197
- app.use(`${basePath}/crash`, produceCrash);
196
+ if (registerTestPages) {
197
+ registerTestPages(app, {basePath});
198
+ }
198
199
 
199
200
  // Only use legacy Express middleware for WebSocket upgrades if shouldUpgradeCallback is not available.
200
201
  // When shouldUpgradeCallback is available, upgrades are handled directly on the HTTP server
@@ -220,12 +221,6 @@ export function configureServer({
220
221
  app.use(startLogFormatter);
221
222
 
222
223
  addRoutes(app, {basePath, extraMethodMap});
223
-
224
- // dynamic routes for testing, etc.
225
- app.all('/welcome', welcome);
226
- app.all('/test/guinea-pig', guineaPig);
227
- app.all('/test/guinea-pig-scrollable', guineaPigScrollable);
228
- app.all('/test/guinea-pig-app-banner', guineaPigAppBanner);
229
224
  }
230
225
 
231
226
  /**
@@ -251,18 +246,13 @@ export function normalizeBasePath(basePath: string): string {
251
246
  return basePath;
252
247
  }
253
248
 
254
- async function createServer(
255
- app: Express,
256
- cliArgs?: Partial<ServerArgs>
257
- ): Promise<HttpServer> {
249
+ async function createServer(app: Express, cliArgs?: Partial<ServerArgs>): Promise<HttpServer> {
258
250
  const {sslCertificatePath, sslKeyPath} = cliArgs ?? {};
259
251
  if (!sslCertificatePath && !sslKeyPath) {
260
252
  return http.createServer(app);
261
253
  }
262
254
  if (!sslCertificatePath || !sslKeyPath) {
263
- throw new Error(
264
- `Both certificate path and key path must be provided to enable TLS`
265
- );
255
+ throw new Error(`Both certificate path and key path must be provided to enable TLS`);
266
256
  }
267
257
 
268
258
  const certKey = [sslCertificatePath, sslKeyPath];
@@ -272,20 +262,19 @@ async function createServer(
272
262
  [keyExists, 'key', sslKeyPath],
273
263
  ]) {
274
264
  if (!exists) {
275
- throw new Error(
276
- `The provided SSL ${desc} at '${p}' does not exist or is not accessible`
277
- );
265
+ throw new Error(`The provided SSL ${desc} at '${p}' does not exist or is not accessible`);
278
266
  }
279
267
  }
280
- const [cert, key] = await Promise.all(
281
- certKey.map((p) => fs.readFile(p, 'utf8'))
282
- ) as [string, string];
268
+ const [cert, key] = (await Promise.all(certKey.map((p) => fs.readFile(p, 'utf8')))) as [
269
+ string,
270
+ string,
271
+ ];
283
272
  log.debug('Enabling TLS/SPDY on the server using the provided certificate');
284
273
 
285
274
  const spdy = require('spdy') as {
286
275
  createServer: (
287
276
  options: {cert: string; key: string; spdy: {plain: boolean; ssl: boolean}},
288
- requestListener: RequestHandler
277
+ requestListener: RequestHandler,
289
278
  ) => HttpServer;
290
279
  };
291
280
  return spdy.createServer(
@@ -297,7 +286,7 @@ async function createServer(
297
286
  ssl: true,
298
287
  },
299
288
  },
300
- app
289
+ app,
301
290
  );
302
291
  }
303
292
 
@@ -329,7 +318,9 @@ function configureHttp({
329
318
  // See: https://github.com/nodejs/node/pull/59824
330
319
  if (hasShouldUpgradeCallback(httpServer)) {
331
320
  // shouldUpgradeCallback only returns a boolean to indicate if the upgrade should proceed
332
- (appiumServer as unknown as {shouldUpgradeCallback?: (req: http.IncomingMessage) => boolean}).shouldUpgradeCallback = (req) =>
321
+ (
322
+ appiumServer as unknown as {shouldUpgradeCallback?: (req: http.IncomingMessage) => boolean}
323
+ ).shouldUpgradeCallback = (req) =>
333
324
  String(req.headers?.upgrade ?? '').toLowerCase() === 'websocket';
334
325
  appiumServer.on('upgrade', (req, socket, head) => {
335
326
  if (!tryHandleWebSocketUpgrade(req, socket, head, appiumServer.webSocketsMapping)) {
@@ -340,7 +331,9 @@ function configureHttp({
340
331
 
341
332
  // http.Server.close() only stops new connections, but we need to wait until
342
333
  // all connections are closed and the `close` event is emitted
343
- const originalClose = appiumServer.close.bind(appiumServer);
334
+ const originalClose = appiumServer.close.bind(appiumServer) as (
335
+ callback?: (err?: Error | null) => void,
336
+ ) => void;
344
337
  appiumServer.close = async () =>
345
338
  await new Promise<void>((_resolve, _reject) => {
346
339
  log.info('Closing Appium HTTP server');
@@ -350,7 +343,7 @@ function configureHttp({
350
343
  log.info(
351
344
  `Not all active connections have been closed within ${gracefulShutdownTimeout}ms. ` +
352
345
  `This timeout might be customized by the --shutdown-timeout command line ` +
353
- `argument. Closing the server anyway.`
346
+ `argument. Closing the server anyway.`,
354
347
  );
355
348
  }
356
349
  process.exit(process.exitCode ?? 0);
@@ -358,13 +351,13 @@ function configureHttp({
358
351
  const onClose = () => {
359
352
  log.info(
360
353
  `Appium HTTP server has been successfully closed after ` +
361
- `${timer.getDuration().asMilliSeconds.toFixed(0)}ms`
354
+ `${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
362
355
  );
363
356
  clearTimeout(onTimeout);
364
357
  _resolve();
365
358
  };
366
359
  httpServer.once('close', onClose);
367
- originalClose((err?: Error) => {
360
+ originalClose((err?: Error | null) => {
368
361
  if (err) {
369
362
  clearTimeout(onTimeout);
370
363
  httpServer.removeListener('close', onClose);
@@ -376,14 +369,13 @@ function configureHttp({
376
369
  appiumServer.once('error', (err: NodeJS.ErrnoException) => {
377
370
  if (err.code === 'EADDRNOTAVAIL') {
378
371
  log.error(
379
- 'Could not start REST http interface listener. ' +
380
- 'Requested address is not available.'
372
+ 'Could not start REST http interface listener. ' + 'Requested address is not available.',
381
373
  );
382
374
  } else {
383
375
  log.error(
384
376
  'Could not start REST http interface listener. The requested ' +
385
377
  'port may already be in use. Please make sure there is no ' +
386
- 'other instance of this server running already.'
378
+ 'other instance of this server running already.',
387
379
  );
388
380
  }
389
381
  reject(err);
@@ -10,7 +10,7 @@ export const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
10
10
  export async function addWebSocketHandler(
11
11
  this: AppiumServer,
12
12
  handlerPathname: string,
13
- handlerServer: WSServer
13
+ handlerServer: WSServer,
14
14
  ): Promise<void> {
15
15
  this.webSocketsMapping[handlerPathname] = handlerServer;
16
16
  }
@@ -21,7 +21,7 @@ export async function addWebSocketHandler(
21
21
  */
22
22
  export async function getWebSocketHandlers(
23
23
  this: AppiumServer,
24
- keysFilter: string | null = null
24
+ keysFilter: string | null = null,
25
25
  ): Promise<Record<string, WSServer>> {
26
26
  return Object.entries(this.webSocketsMapping).reduce<Record<string, WSServer>>(
27
27
  (acc, [pathname, wsServer]) => {
@@ -30,7 +30,7 @@ export async function getWebSocketHandlers(
30
30
  }
31
31
  return acc;
32
32
  },
33
- {}
33
+ {},
34
34
  );
35
35
  }
36
36
 
@@ -40,7 +40,7 @@ export async function getWebSocketHandlers(
40
40
  */
41
41
  export async function removeWebSocketHandler(
42
42
  this: AppiumServer,
43
- handlerPathname: string
43
+ handlerPathname: string,
44
44
  ): Promise<boolean> {
45
45
  const wsServer = this.webSocketsMapping?.[handlerPathname];
46
46
  if (!wsServer) {
@@ -71,9 +71,7 @@ export async function removeAllWebSocketHandlers(this: AppiumServer): Promise<bo
71
71
  }
72
72
 
73
73
  const results = await Promise.all(
74
- Object.keys(this.webSocketsMapping).map((pathname) =>
75
- this.removeWebSocketHandler(pathname)
76
- )
74
+ Object.keys(this.webSocketsMapping).map((pathname) => this.removeWebSocketHandler(pathname)),
77
75
  );
78
76
  return results.some(Boolean);
79
77
  }