appium-ios-simulator 8.0.13 → 8.1.0

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 (48) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/lib/defaults-utils.d.ts +24 -24
  3. package/build/lib/defaults-utils.d.ts.map +1 -1
  4. package/build/lib/defaults-utils.js +61 -65
  5. package/build/lib/defaults-utils.js.map +1 -1
  6. package/build/lib/extensions/applications.d.ts.map +1 -1
  7. package/build/lib/extensions/applications.js +6 -7
  8. package/build/lib/extensions/applications.js.map +1 -1
  9. package/build/lib/extensions/biometric.d.ts.map +1 -1
  10. package/build/lib/extensions/biometric.js +3 -6
  11. package/build/lib/extensions/biometric.js.map +1 -1
  12. package/build/lib/extensions/keychain.d.ts.map +1 -1
  13. package/build/lib/extensions/keychain.js +7 -6
  14. package/build/lib/extensions/keychain.js.map +1 -1
  15. package/build/lib/extensions/permissions.d.ts.map +1 -1
  16. package/build/lib/extensions/permissions.js +16 -18
  17. package/build/lib/extensions/permissions.js.map +1 -1
  18. package/build/lib/extensions/safari.d.ts.map +1 -1
  19. package/build/lib/extensions/safari.js +2 -4
  20. package/build/lib/extensions/safari.js.map +1 -1
  21. package/build/lib/extensions/settings.d.ts.map +1 -1
  22. package/build/lib/extensions/settings.js +52 -50
  23. package/build/lib/extensions/settings.js.map +1 -1
  24. package/build/lib/simulator-xcode-14.d.ts +37 -37
  25. package/build/lib/simulator-xcode-14.d.ts.map +1 -1
  26. package/build/lib/simulator-xcode-14.js +57 -59
  27. package/build/lib/simulator-xcode-14.js.map +1 -1
  28. package/build/lib/simulator-xcode-15.d.ts.map +1 -1
  29. package/build/lib/simulator-xcode-15.js +3 -5
  30. package/build/lib/simulator-xcode-15.js.map +1 -1
  31. package/build/lib/types.d.ts +8 -8
  32. package/build/lib/types.d.ts.map +1 -1
  33. package/build/lib/utils.d.ts +11 -3
  34. package/build/lib/utils.d.ts.map +1 -1
  35. package/build/lib/utils.js +50 -35
  36. package/build/lib/utils.js.map +1 -1
  37. package/lib/defaults-utils.ts +69 -68
  38. package/lib/extensions/applications.ts +6 -7
  39. package/lib/extensions/biometric.ts +3 -3
  40. package/lib/extensions/keychain.ts +11 -6
  41. package/lib/extensions/permissions.ts +17 -19
  42. package/lib/extensions/safari.ts +2 -4
  43. package/lib/extensions/settings.ts +71 -62
  44. package/lib/simulator-xcode-14.ts +70 -71
  45. package/lib/simulator-xcode-15.ts +3 -5
  46. package/lib/types.ts +9 -9
  47. package/lib/utils.ts +52 -37
  48. package/package.json +1 -5
@@ -1,8 +1,64 @@
1
- import _ from 'lodash';
2
1
  import {DOMParser, XMLSerializer, type Document, type Element} from '@xmldom/xmldom';
3
2
  import {exec} from 'teen_process';
4
- import B from 'bluebird';
5
3
  import {log} from './logger';
4
+ import {isPlainObject} from './utils';
5
+
6
+ export class NSUserDefaults {
7
+ plist: string;
8
+
9
+ constructor(plist: string) {
10
+ this.plist = plist;
11
+ }
12
+
13
+ /**
14
+ * Reads the content of the given plist file using plutil command line tool
15
+ * and serializes it to a JSON representation
16
+ *
17
+ * @returns The serialized plist content
18
+ * @throws {Error} If there was an error during serialization
19
+ */
20
+ async asJson(): Promise<Record<string, any>> {
21
+ try {
22
+ const {stdout} = await exec('plutil', ['-convert', 'json', '-o', '-', this.plist]);
23
+ return JSON.parse(stdout);
24
+ } catch (e: any) {
25
+ throw new Error(
26
+ `'${this.plist}' cannot be converted to JSON. Original error: ${e.stderr || e.message}`,
27
+ );
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Updates the content of the given plist file.
33
+ * If the plist does not exist yet then it is going to be created.
34
+ *
35
+ * @param valuesMap Mapping of preference values to update.
36
+ * If any of item values are of dictionary type then only the first level dictionary gets
37
+ * updated. Everything below this level will be replaced. This is the known limitation
38
+ * of the `defaults` command line tool. A workaround for it would be to read the current
39
+ * preferences mapping first and merge it with this value.
40
+ * @throws {Error} If there was an error while updating the plist
41
+ */
42
+ async update(valuesMap: Record<string, any>): Promise<void> {
43
+ if (!isPlainObject(valuesMap)) {
44
+ throw new TypeError(`plist values must be a map. '${valuesMap}' is given instead`);
45
+ }
46
+ if (Object.keys(valuesMap).length === 0) {
47
+ return;
48
+ }
49
+
50
+ const commandArgs = generateDefaultsCommandArgs(valuesMap);
51
+ try {
52
+ await Promise.all(
53
+ commandArgs.map((args) => exec('defaults', ['write', this.plist, ...args])),
54
+ );
55
+ } catch (e: any) {
56
+ throw new Error(
57
+ `Could not write defaults into '${this.plist}'. Original error: ${e.stderr || e.message}`,
58
+ );
59
+ }
60
+ }
61
+ }
6
62
 
7
63
  /**
8
64
  * Serializes the given value to plist-compatible
@@ -19,10 +75,10 @@ import {log} from './logger';
19
75
  export function toXmlArg(value: any, serialize: boolean = true): string | Element {
20
76
  let xmlDoc: Document | null = null;
21
77
 
22
- if (_.isPlainObject(value)) {
78
+ if (isPlainObject(value)) {
23
79
  xmlDoc = new DOMParser().parseFromString('<dict></dict>', 'text/xml');
24
80
  const documentElement = requireDocumentElement(xmlDoc);
25
- for (const [subKey, subValue] of _.toPairs(value)) {
81
+ for (const [subKey, subValue] of Object.entries(value)) {
26
82
  const keyEl = xmlDoc.createElement('key');
27
83
  const keyTextEl = xmlDoc.createTextNode(subKey);
28
84
  keyEl.appendChild(keyTextEl);
@@ -30,20 +86,20 @@ export function toXmlArg(value: any, serialize: boolean = true): string | Elemen
30
86
  const subValueEl = xmlDoc.importNode(toXmlArg(subValue, false) as Element, true);
31
87
  documentElement.appendChild(subValueEl);
32
88
  }
33
- } else if (_.isArray(value)) {
89
+ } else if (Array.isArray(value)) {
34
90
  xmlDoc = new DOMParser().parseFromString('<array></array>', 'text/xml');
35
91
  const documentElement = requireDocumentElement(xmlDoc);
36
92
  for (const subValue of value) {
37
93
  const subValueEl = xmlDoc.importNode(toXmlArg(subValue, false) as Element, true);
38
94
  documentElement.appendChild(subValueEl);
39
95
  }
40
- } else if (_.isBoolean(value)) {
96
+ } else if (typeof value === 'boolean') {
41
97
  xmlDoc = new DOMParser().parseFromString(value ? '<true/>' : '<false/>', 'text/xml');
42
- } else if (_.isInteger(value)) {
98
+ } else if (Number.isInteger(value)) {
43
99
  xmlDoc = new DOMParser().parseFromString(`<integer>${value}</integer>`, 'text/xml');
44
- } else if (_.isNumber(value)) {
100
+ } else if (typeof value === 'number') {
45
101
  xmlDoc = new DOMParser().parseFromString(`<real>${value}</real>`, 'text/xml');
46
- } else if (_.isString(value)) {
102
+ } else if (typeof value === 'string') {
47
103
  xmlDoc = new DOMParser().parseFromString(`<string></string>`, 'text/xml');
48
104
  const valueTextEl = xmlDoc.createTextNode(value);
49
105
  requireDocumentElement(xmlDoc).appendChild(valueTextEl);
@@ -78,15 +134,15 @@ export function generateDefaultsCommandArgs(
78
134
  replace: boolean = false,
79
135
  ): string[][] {
80
136
  const resultArgs: string[][] = [];
81
- for (const [key, value] of _.toPairs(valuesMap)) {
137
+ for (const [key, value] of Object.entries(valuesMap)) {
82
138
  try {
83
- if (!replace && _.isPlainObject(value)) {
139
+ if (!replace && isPlainObject(value)) {
84
140
  const dictArgs = [key, '-dict-add'];
85
- for (const [subKey, subValue] of _.toPairs(value)) {
141
+ for (const [subKey, subValue] of Object.entries(value)) {
86
142
  dictArgs.push(subKey, toXmlArg(subValue) as string);
87
143
  }
88
144
  resultArgs.push(dictArgs);
89
- } else if (!replace && _.isArray(value)) {
145
+ } else if (!replace && Array.isArray(value)) {
90
146
  const arrayArgs = [key, '-array-add'];
91
147
  for (const subValue of value) {
92
148
  arrayArgs.push(toXmlArg(subValue) as string);
@@ -106,61 +162,6 @@ export function generateDefaultsCommandArgs(
106
162
  return resultArgs;
107
163
  }
108
164
 
109
- export class NSUserDefaults {
110
- plist: string;
111
-
112
- constructor(plist: string) {
113
- this.plist = plist;
114
- }
115
-
116
- /**
117
- * Reads the content of the given plist file using plutil command line tool
118
- * and serializes it to a JSON representation
119
- *
120
- * @returns The serialized plist content
121
- * @throws {Error} If there was an error during serialization
122
- */
123
- async asJson(): Promise<Record<string, any>> {
124
- try {
125
- const {stdout} = await exec('plutil', ['-convert', 'json', '-o', '-', this.plist]);
126
- return JSON.parse(stdout);
127
- } catch (e: any) {
128
- throw new Error(
129
- `'${this.plist}' cannot be converted to JSON. Original error: ${e.stderr || e.message}`,
130
- );
131
- }
132
- }
133
-
134
- /**
135
- * Updates the content of the given plist file.
136
- * If the plist does not exist yet then it is going to be created.
137
- *
138
- * @param valuesMap Mapping of preference values to update.
139
- * If any of item values are of dictionary type then only the first level dictionary gets
140
- * updated. Everything below this level will be replaced. This is the known limitation
141
- * of the `defaults` command line tool. A workaround for it would be to read the current
142
- * preferences mapping first and merge it with this value.
143
- * @throws {Error} If there was an error while updating the plist
144
- */
145
- async update(valuesMap: Record<string, any>): Promise<void> {
146
- if (!_.isPlainObject(valuesMap)) {
147
- throw new TypeError(`plist values must be a map. '${valuesMap}' is given instead`);
148
- }
149
- if (_.isEmpty(valuesMap)) {
150
- return;
151
- }
152
-
153
- const commandArgs = generateDefaultsCommandArgs(valuesMap);
154
- try {
155
- await B.all(commandArgs.map((args) => exec('defaults', ['write', this.plist, ...args])));
156
- } catch (e: any) {
157
- throw new Error(
158
- `Could not write defaults into '${this.plist}'. Original error: ${e.stderr || e.message}`,
159
- );
160
- }
161
- }
162
- }
163
-
164
165
  function requireDocumentElement(xmlDoc: Document): Element {
165
166
  const {documentElement} = xmlDoc;
166
167
  if (!documentElement) {
@@ -1,8 +1,7 @@
1
- import _ from 'lodash';
2
1
  import path from 'node:path';
3
2
  import {fs, plist, util} from '@appium/support';
4
- import B from 'bluebird';
5
3
  import {waitForCondition} from 'asyncbox';
4
+ import {isPlainObject} from '../utils';
6
5
  import type {CoreSimulator, InteractsWithApps, LaunchAppOptions} from '../types';
7
6
 
8
7
  type CoreSimulatorWithApps = CoreSimulator & InteractsWithApps;
@@ -32,7 +31,7 @@ export async function getUserInstalledBundleIdsByBundleName(
32
31
  cwd: appsRoot,
33
32
  absolute: true,
34
33
  });
35
- if (_.isEmpty(infoPlists)) {
34
+ if (infoPlists.length === 0) {
36
35
  return [];
37
36
  }
38
37
 
@@ -48,11 +47,11 @@ export async function getUserInstalledBundleIdsByBundleName(
48
47
  })(),
49
48
  );
50
49
  }
51
- const bundleInfos = (await B.all(bundleInfoPromises)).filter(_.isPlainObject);
50
+ const bundleInfos = (await Promise.all(bundleInfoPromises)).filter(isPlainObject);
52
51
  const bundleIds = bundleInfos
53
52
  .filter(({CFBundleName}) => CFBundleName === bundleName)
54
53
  .map(({CFBundleIdentifier}) => CFBundleIdentifier);
55
- if (_.isEmpty(bundleIds)) {
54
+ if (bundleIds.length === 0) {
56
55
  return [];
57
56
  }
58
57
 
@@ -166,12 +165,12 @@ export async function scrubApp(this: CoreSimulatorWithApps, bundleId: string): P
166
165
  this.log.info(
167
166
  `Found ${appFiles.length} ${bundleId} app ${util.pluralize('file', appFiles.length, false)} to scrub`,
168
167
  );
169
- if (_.isEmpty(appFiles)) {
168
+ if (appFiles.length === 0) {
170
169
  return;
171
170
  }
172
171
 
173
172
  try {
174
173
  await this.terminateApp(bundleId);
175
174
  } catch {}
176
- await B.all(appFiles.map((p) => fs.rimraf(p)));
175
+ await Promise.all(appFiles.map((p) => fs.rimraf(p)));
177
176
  }
@@ -1,5 +1,5 @@
1
- import _ from 'lodash';
2
1
  import type {CoreSimulator, SupportsBiometric} from '../types';
2
+ import {escapeRegExp} from '../utils';
3
3
 
4
4
  type CoreSimulatorWithBiometric = CoreSimulator & SupportsBiometric;
5
5
 
@@ -18,7 +18,7 @@ export async function isBiometricEnrolled(this: CoreSimulatorWithBiometric): Pro
18
18
  '-g',
19
19
  ENROLLMENT_NOTIFICATION_RECEIVER,
20
20
  ]);
21
- const match = new RegExp(`${_.escapeRegExp(ENROLLMENT_NOTIFICATION_RECEIVER)}\\s+([01])`).exec(
21
+ const match = new RegExp(`${escapeRegExp(ENROLLMENT_NOTIFICATION_RECEIVER)}\\s+([01])`).exec(
22
22
  stdout,
23
23
  );
24
24
  if (!match) {
@@ -80,7 +80,7 @@ export async function sendBiometricMatch(
80
80
  export function toBiometricDomainComponent(name: string): string {
81
81
  if (!BIOMETRICS[name]) {
82
82
  throw new Error(
83
- `'${name}' is not a valid biometric. Use one of: ${JSON.stringify(_.keys(BIOMETRICS))}`,
83
+ `'${name}' is not a valid biometric. Use one of: ${JSON.stringify(Object.keys(BIOMETRICS))}`,
84
84
  );
85
85
  }
86
86
  return BIOMETRICS[name];
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import path from 'node:path';
3
2
  import {fs, mkdirp, tempDir, util} from '@appium/support';
4
3
  import {exec} from 'teen_process';
@@ -16,13 +15,16 @@ type CoreSimulatorWithKeychain = CoreSimulator & InteractsWithKeychain;
16
15
  */
17
16
  export async function backupKeychains(this: CoreSimulatorWithKeychain): Promise<boolean> {
18
17
  const resetBackupPath = async (newPath: string | null | undefined) => {
19
- if (_.isString(this._keychainsBackupPath) && (await fs.exists(this._keychainsBackupPath))) {
18
+ if (
19
+ typeof this._keychainsBackupPath === 'string' &&
20
+ (await fs.exists(this._keychainsBackupPath))
21
+ ) {
20
22
  await fs.unlink(this._keychainsBackupPath);
21
23
  }
22
24
  this._keychainsBackupPath = newPath;
23
25
  };
24
26
 
25
- if (!(await fs.exists(this.keychainPath)) || _.isEmpty(await fs.readdir(this.keychainPath))) {
27
+ if (!(await fs.exists(this.keychainPath)) || (await fs.readdir(this.keychainPath)).length === 0) {
26
28
  this.log.info(`There is nothing to backup from '${this.keychainPath}'`);
27
29
  await resetBackupPath(null);
28
30
  return false;
@@ -64,14 +66,17 @@ export async function restoreKeychains(
64
66
  this: CoreSimulatorWithKeychain,
65
67
  excludePatterns: string[] | string = [],
66
68
  ): Promise<boolean> {
67
- if (!_.isString(this._keychainsBackupPath) || !(await fs.exists(this._keychainsBackupPath))) {
69
+ if (
70
+ typeof this._keychainsBackupPath !== 'string' ||
71
+ !(await fs.exists(this._keychainsBackupPath))
72
+ ) {
68
73
  throw new Error(
69
74
  `The keychains backup archive does not exist. ` + `Are you sure it was created before?`,
70
75
  );
71
76
  }
72
77
 
73
78
  let patterns: string[] = [];
74
- if (_.isString(excludePatterns)) {
79
+ if (typeof excludePatterns === 'string') {
75
80
  patterns = excludePatterns.split(',').map((x) => x.trim());
76
81
  } else {
77
82
  patterns = excludePatterns;
@@ -95,7 +100,7 @@ export async function restoreKeychains(
95
100
  const unzipArgs = [
96
101
  '-o',
97
102
  backupPath,
98
- ..._.flatMap(patterns.map((x) => ['-x', x])),
103
+ ...patterns.flatMap((x) => ['-x', x]),
99
104
  '-d',
100
105
  path.dirname(this.keychainPath),
101
106
  ];
@@ -1,8 +1,6 @@
1
- import _ from 'lodash';
2
1
  import {fs, timing, util} from '@appium/support';
3
2
  import {exec} from 'teen_process';
4
3
  import path from 'node:path';
5
- import B from 'bluebird';
6
4
  import {waitForCondition} from 'asyncbox';
7
5
  import type {CoreSimulator, SupportsAppPermissions} from '../types';
8
6
  import type {StringRecord} from '@appium/types';
@@ -99,17 +97,17 @@ export async function getPermission(
99
97
  }
100
98
 
101
99
  function toInternalServiceName(serviceName: string): string {
102
- const lowerName = _.toLower(serviceName);
103
- if (_.has(SERVICES, lowerName)) {
100
+ const lowerName = serviceName.toLowerCase();
101
+ if (lowerName in SERVICES) {
104
102
  return SERVICES[lowerName as keyof typeof SERVICES] as string;
105
103
  }
106
104
  throw new Error(
107
- `'${serviceName}' is unknown. Only the following service names are supported: ${JSON.stringify(_.keys(SERVICES))}`,
105
+ `'${serviceName}' is unknown. Only the following service names are supported: ${JSON.stringify(Object.keys(SERVICES))}`,
108
106
  );
109
107
  }
110
108
 
111
109
  function formatStatus(status: string): string {
112
- return status === STATUS.UNSET || status === STATUS.NO ? _.toUpper(status) : status;
110
+ return status === STATUS.UNSET || status === STATUS.NO ? status.toUpperCase() : status;
113
111
  }
114
112
 
115
113
  /**
@@ -190,7 +188,7 @@ async function setAccess(
190
188
  } else {
191
189
  // xcrun simctl privacy expects to be lower case while AppleSimulatorUtils is upper case.
192
190
  // To keep the compatibility, we should convert here to lower case explicitly.
193
- switch (_.toLower(permissionsMapping[serviceName])) {
191
+ switch (permissionsMapping[serviceName]?.toLowerCase()) {
194
192
  case STATUS.YES:
195
193
  grantPermissions.push(serviceName);
196
194
  break;
@@ -210,7 +208,7 @@ async function setAccess(
210
208
 
211
209
  const permissionPromises: Promise<any>[] = [];
212
210
 
213
- if (!_.isEmpty(grantPermissions)) {
211
+ if (grantPermissions.length > 0) {
214
212
  this.log.debug(
215
213
  `Granting ${util.pluralize('permission', grantPermissions.length, false)} for ${bundleId}: ${grantPermissions}`,
216
214
  );
@@ -219,7 +217,7 @@ async function setAccess(
219
217
  }
220
218
  }
221
219
 
222
- if (!_.isEmpty(revokePermissions)) {
220
+ if (revokePermissions.length > 0) {
223
221
  this.log.debug(
224
222
  `Revoking ${util.pluralize('permission', revokePermissions.length, false)} for ${bundleId}: ${revokePermissions}`,
225
223
  );
@@ -228,7 +226,7 @@ async function setAccess(
228
226
  }
229
227
  }
230
228
 
231
- if (!_.isEmpty(resetPermissions)) {
229
+ if (resetPermissions.length > 0) {
232
230
  this.log.debug(
233
231
  `Resetting ${util.pluralize('permission', resetPermissions.length, false)} for ${bundleId}: ${resetPermissions}`,
234
232
  );
@@ -237,16 +235,16 @@ async function setAccess(
237
235
  }
238
236
  }
239
237
 
240
- if (!_.isEmpty(permissionPromises)) {
241
- await B.all(permissionPromises);
238
+ if (permissionPromises.length > 0) {
239
+ await Promise.all(permissionPromises);
242
240
  }
243
241
 
244
- if (!_.isEmpty(wixPermissions)) {
242
+ if (Object.keys(wixPermissions).length > 0) {
245
243
  this.log.debug(
246
244
  `Setting permissions for ${bundleId} wit ${WIX_SIM_UTILS} as ${JSON.stringify(wixPermissions)}`,
247
245
  );
248
- const permissionsArg = _.toPairs(wixPermissions)
249
- .map((x) => `${x[0]}=${formatStatus(x[1])}`)
246
+ const permissionsArg = Object.entries(wixPermissions)
247
+ .map(([name, status]) => `${name}=${formatStatus(status)}`)
250
248
  .join(',');
251
249
  const execWixFn = async () =>
252
250
  await execWix.bind(this)([
@@ -257,8 +255,8 @@ async function setAccess(
257
255
  '--setPermissions',
258
256
  permissionsArg,
259
257
  ]);
260
- const shouldWaitForSystemReadiness = !_.isEmpty(
261
- _.intersection(SERVICES_NEED_SPRINGBOARD_RESTART, _.keys(wixPermissions)),
258
+ const shouldWaitForSystemReadiness = SERVICES_NEED_SPRINGBOARD_RESTART.some(
259
+ (service) => service in wixPermissions,
262
260
  );
263
261
  if (shouldWaitForSystemReadiness) {
264
262
  const [didTimeout] = await runAndWaitForSystemReadiness.bind(this)(
@@ -302,7 +300,7 @@ async function runAndWaitForSystemReadiness<T>(
302
300
  async () => {
303
301
  try {
304
302
  const pid = (await this.ps()).find(({name}) => bundleId === name)?.pid;
305
- return _.isInteger(pid) && initialPid !== pid;
303
+ return Number.isInteger(pid) && initialPid !== pid;
306
304
  } catch {
307
305
  return false;
308
306
  }
@@ -322,7 +320,7 @@ async function runAndWaitForSystemReadiness<T>(
322
320
  ].map((bundleId) => initialProcesses.find(({name}) => bundleId === name)?.pid);
323
321
 
324
322
  const result = await fn();
325
- if (!_.isInteger(initialSpringboardPid) || !_.isInteger(initialSpotlightPid)) {
323
+ if (!Number.isInteger(initialSpringboardPid) || !Number.isInteger(initialSpotlightPid)) {
326
324
  // there is no point to wait if relevant processes were not running before
327
325
  return [false, result];
328
326
  }
@@ -1,7 +1,5 @@
1
- import _ from 'lodash';
2
1
  import path from 'node:path';
3
2
  import {fs, timing} from '@appium/support';
4
- import B from 'bluebird';
5
3
  import {MOBILE_SAFARI_BUNDLE_ID, SAFARI_STARTUP_TIMEOUT_MS} from '../utils';
6
4
  import {waitForCondition} from 'asyncbox';
7
5
  import {exec} from 'teen_process';
@@ -103,7 +101,7 @@ export async function scrubSafari(
103
101
  if (!keepPrefs) {
104
102
  deletePromises.push(fs.rimraf(path.join(libraryDir, 'Preferences', '*.plist')));
105
103
  }
106
- await B.all(deletePromises);
104
+ await Promise.all(deletePromises);
107
105
  }
108
106
 
109
107
  /**
@@ -126,7 +124,7 @@ export async function updateSafariSettings(
126
124
  this: CoreSimulatorWithSafariBrowser,
127
125
  updates: StringRecord,
128
126
  ): Promise<boolean> {
129
- if (_.isEmpty(updates)) {
127
+ if (Object.keys(updates).length === 0) {
130
128
  return false;
131
129
  }
132
130