appium-android-driver 4.51.0 → 4.54.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.
- package/build/lib/commands/context.js +4 -1
- package/build/lib/commands/execute.js +3 -2
- package/build/lib/commands/file-actions.js +16 -9
- package/build/lib/commands/network.js +8 -1
- package/build/lib/commands/performance.js +106 -88
- package/lib/commands/context.js +3 -0
- package/lib/commands/execute.js +2 -0
- package/lib/commands/file-actions.js +27 -7
- package/lib/commands/network.js +21 -0
- package/lib/commands/performance.js +146 -115
- package/package.json +8 -8
|
@@ -2,72 +2,149 @@ import _ from 'lodash';
|
|
|
2
2
|
import { retryInterval } from 'asyncbox';
|
|
3
3
|
import log from '../logger';
|
|
4
4
|
|
|
5
|
+
const commands = {}, helpers = {}, extensions = {};
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const NETWORK_KEYS = [
|
|
8
|
+
['bucketStart', 'activeTime', 'rxBytes', 'rxPackets', 'txBytes', 'txPackets', 'operations', 'bucketDuration'],
|
|
9
|
+
['st', 'activeTime', 'rb', 'rp', 'tb', 'tp', 'op', 'bucketDuration']
|
|
10
|
+
];
|
|
9
11
|
const CPU_KEYS = ['user', 'kernel'];
|
|
10
12
|
const BATTERY_KEYS = ['power'];
|
|
11
|
-
const MEMORY_KEYS = [
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const MEMORY_KEYS = [
|
|
14
|
+
'totalPrivateDirty', 'nativePrivateDirty', 'dalvikPrivateDirty',
|
|
15
|
+
'eglPrivateDirty', 'glPrivateDirty',
|
|
16
|
+
'totalPss', 'nativePss', 'dalvikPss', 'eglPss', 'glPss',
|
|
17
|
+
'nativeHeapAllocatedSize', 'nativeHeapSize',
|
|
18
|
+
'nativeRss', 'dalvikRss', 'totalRss'
|
|
19
|
+
];
|
|
20
|
+
const SUPPORTED_PERFORMANCE_DATA_TYPES = Object.freeze({
|
|
14
21
|
cpuinfo: 'the amount of cpu by user and kernel process - cpu information for applications on real devices and simulators',
|
|
15
22
|
memoryinfo: 'the amount of memory used by the process - memory information for applications on real devices and simulators',
|
|
16
23
|
batteryinfo: 'the remaining battery power - battery power information for applications on real devices and simulators',
|
|
17
24
|
networkinfo: 'the network statistics - network rx/tx information for applications on real devices and simulators'
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
});
|
|
26
|
+
const MEMINFO_TITLES = Object.freeze({
|
|
27
|
+
NATIVE: 'Native',
|
|
28
|
+
DALVIK: 'Dalvik',
|
|
29
|
+
EGL: 'EGL',
|
|
30
|
+
GL: 'GL',
|
|
31
|
+
MTRACK: 'mtrack',
|
|
32
|
+
TOTAL: 'TOTAL',
|
|
33
|
+
HEAP: 'Heap'
|
|
34
|
+
});
|
|
35
|
+
const RETRY_PAUSE_MS = 1000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* API level between 18 and 30
|
|
39
|
+
* ['<System Type>', '<Memory Type>', <pss total>, <private dirty>, <private clean>, <swapPss dirty>, <heap size>, <heap alloc>, <heap free>]
|
|
40
|
+
* except 'TOTAL', which skips the second type name
|
|
41
|
+
* !!! valDict gets mutated
|
|
42
|
+
*/
|
|
43
|
+
function parseMeminfoForApi19To29 (entries, valDict) {
|
|
44
|
+
const [type, subType] = entries;
|
|
45
|
+
if (type === MEMINFO_TITLES.NATIVE && subType === MEMINFO_TITLES.HEAP) {
|
|
46
|
+
[,, valDict.nativePss, valDict.nativePrivateDirty,,, valDict.nativeHeapSize, valDict.nativeHeapAllocatedSize] = entries;
|
|
47
|
+
} else if (type === MEMINFO_TITLES.DALVIK && subType === MEMINFO_TITLES.HEAP) {
|
|
48
|
+
[,, valDict.dalvikPss, valDict.dalvikPrivateDirty] = entries;
|
|
49
|
+
} else if (type === MEMINFO_TITLES.EGL && subType === MEMINFO_TITLES.MTRACK) {
|
|
50
|
+
[,, valDict.eglPss, valDict.eglPrivateDirty] = entries;
|
|
51
|
+
} else if (type === MEMINFO_TITLES.GL && subType === MEMINFO_TITLES.MTRACK) {
|
|
52
|
+
[,, valDict.glPss, valDict.glPrivateDirty] = entries;
|
|
53
|
+
} else if (type === MEMINFO_TITLES.TOTAL && entries.length === 8) {
|
|
54
|
+
// there are two totals, and we only want the full listing, which has 8 entries
|
|
55
|
+
[, valDict.totalPss, valDict.totalPrivateDirty] = entries;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* ['<System Type', '<pps>', '<shared dirty>', '<private dirty>', '<heap size>', '<heap alloc>', '<heap free>']
|
|
61
|
+
* !!! valDict gets mutated
|
|
62
|
+
*/
|
|
63
|
+
function parseMeminfoForApiBelow19 (entries, valDict) {
|
|
64
|
+
const type = entries[0];
|
|
65
|
+
if (type === MEMINFO_TITLES.NATIVE) {
|
|
66
|
+
[, valDict.nativePss,, valDict.nativePrivateDirty, valDict.nativeHeapSize, valDict.nativeHeapAllocatedSize] = entries;
|
|
67
|
+
} else if (type === MEMINFO_TITLES.DALVIK) {
|
|
68
|
+
[, valDict.dalvikPss,, valDict.dalvikPrivateDirty] = entries;
|
|
69
|
+
} else if (type === MEMINFO_TITLES.EGL) {
|
|
70
|
+
[, valDict.eglPss,, valDict.eglPrivateDirty] = entries;
|
|
71
|
+
} else if (type === MEMINFO_TITLES.GL) {
|
|
72
|
+
[, valDict.glPss,, valDict.glPrivateDirty] = entries;
|
|
73
|
+
} else if (type === MEMINFO_TITLES.TOTAL) {
|
|
74
|
+
[, valDict.totalPss,, valDict.totalPrivateDirty] = entries;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* API level 30 and above
|
|
80
|
+
* ['<System Type>', '<Memory Type>', <pss total>, <private dirty>, <private clean>, <swapPss dirty>, <rss total>, <heap size>, <heap alloc>, <heap free>]
|
|
81
|
+
* !!! valDict gets mutated
|
|
82
|
+
*/
|
|
83
|
+
function parseMeminfoForApiAbove29 (entries, valDict) {
|
|
84
|
+
const [type, subType] = entries;
|
|
85
|
+
if (type === MEMINFO_TITLES.NATIVE && subType === MEMINFO_TITLES.HEAP) {
|
|
86
|
+
[,, valDict.nativePss, valDict.nativePrivateDirty,,, valDict.nativeRss, valDict.nativeHeapSize, valDict.nativeHeapAllocatedSize] = entries;
|
|
87
|
+
} else if (type === MEMINFO_TITLES.DALVIK && subType === MEMINFO_TITLES.HEAP) {
|
|
88
|
+
[,, valDict.dalvikPss, valDict.dalvikPrivateDirty,,, valDict.dalvikRss] = entries;
|
|
89
|
+
} else if (type === MEMINFO_TITLES.EGL && subType === MEMINFO_TITLES.MTRACK) {
|
|
90
|
+
[,, valDict.eglPss, valDict.eglPrivateDirty] = entries;
|
|
91
|
+
} else if (type === MEMINFO_TITLES.GL && subType === MEMINFO_TITLES.MTRACK) {
|
|
92
|
+
[,, valDict.glPss, valDict.glPrivateDirty] = entries;
|
|
93
|
+
} else if (type === MEMINFO_TITLES.TOTAL && entries.length === 9) {
|
|
94
|
+
// has 9 entries
|
|
95
|
+
[, valDict.totalPss, valDict.totalPrivateDirty,,, valDict.totalRss] = entries;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
21
98
|
|
|
22
99
|
//
|
|
23
100
|
// returns the information type of the system state which is supported to read as like cpu, memory, network traffic, and battery.
|
|
24
101
|
// output - array like below
|
|
25
|
-
//[cpuinfo, batteryinfo, networkinfo, memoryinfo]
|
|
102
|
+
// [cpuinfo, batteryinfo, networkinfo, memoryinfo]
|
|
26
103
|
//
|
|
27
104
|
commands.getPerformanceDataTypes = function getPerformanceDataTypes () {
|
|
28
105
|
return _.keys(SUPPORTED_PERFORMANCE_DATA_TYPES);
|
|
29
106
|
};
|
|
30
107
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
108
|
+
/**
|
|
109
|
+
* @returns The information type of the system state which is supported to read as like cpu, memory, network traffic, and battery.
|
|
110
|
+
* input - (packageName) the package name of the application
|
|
111
|
+
* (dataType) the type of system state which wants to read. It should be one of the keys of the SUPPORTED_PERFORMANCE_DATA_TYPES
|
|
112
|
+
* (dataReadTimeout) the number of attempts to read
|
|
113
|
+
* output - table of the performance data, The first line of the table represents the type of data. The remaining lines represent the values of the data.
|
|
114
|
+
*
|
|
115
|
+
* in case of battery info : [[power], [23]]
|
|
116
|
+
* in case of memory info : [[totalPrivateDirty, nativePrivateDirty, dalvikPrivateDirty, eglPrivateDirty, glPrivateDirty, totalPss,
|
|
117
|
+
* nativePss, dalvikPss, eglPss, glPss, nativeHeapAllocatedSize, nativeHeapSize], [18360, 8296, 6132, null, null, 42588, 8406, 7024, null, null, 26519, 10344]]
|
|
118
|
+
* in case of network info : [[bucketStart, activeTime, rxBytes, rxPackets, txBytes, txPackets, operations, bucketDuration,],
|
|
119
|
+
* [1478091600000, null, 1099075, 610947, 928, 114362, 769, 0, 3600000], [1478095200000, null, 1306300, 405997, 509, 46359, 370, 0, 3600000]]
|
|
120
|
+
* in case of network info : [[st, activeTime, rb, rp, tb, tp, op, bucketDuration], [1478088000, null, null, 32115296, 34291, 2956805, 25705, 0, 3600],
|
|
121
|
+
* [1478091600, null, null, 2714683, 11821, 1420564, 12650, 0, 3600], [1478095200, null, null, 10079213, 19962, 2487705, 20015, 0, 3600],
|
|
122
|
+
* [1478098800, null, null, 4444433, 10227, 1430356, 10493, 0, 3600]]
|
|
123
|
+
* in case of cpu info : [[user, kernel], [0.9, 1.3]]
|
|
124
|
+
*/
|
|
125
|
+
commands.getPerformanceData = async function getPerformanceData (packageName, dataType, retries = 2) {
|
|
126
|
+
switch (_.toLower(dataType)) {
|
|
46
127
|
case 'batteryinfo':
|
|
47
|
-
|
|
48
|
-
break;
|
|
128
|
+
return await this.getBatteryInfo(retries);
|
|
49
129
|
case 'cpuinfo':
|
|
50
|
-
|
|
51
|
-
break;
|
|
130
|
+
return await this.getCPUInfo(packageName, retries);
|
|
52
131
|
case 'memoryinfo':
|
|
53
|
-
|
|
54
|
-
break;
|
|
132
|
+
return await this.getMemoryInfo(packageName, retries);
|
|
55
133
|
case 'networkinfo':
|
|
56
|
-
|
|
57
|
-
break;
|
|
134
|
+
return await this.getNetworkTrafficInfo(retries);
|
|
58
135
|
default:
|
|
59
|
-
throw new Error(`No performance data of type '${dataType}' found
|
|
136
|
+
throw new Error(`No performance data of type '${dataType}' found. ` +
|
|
137
|
+
`Only the following values are supported: ${JSON.stringify(SUPPORTED_PERFORMANCE_DATA_TYPES, ' ', 2)}`);
|
|
60
138
|
}
|
|
61
|
-
return data;
|
|
62
139
|
};
|
|
63
140
|
|
|
64
|
-
helpers.getCPUInfo = async function getCPUInfo (packageName,
|
|
141
|
+
helpers.getCPUInfo = async function getCPUInfo (packageName, retries = 2) {
|
|
65
142
|
// TODO: figure out why this is
|
|
66
143
|
// sometimes, the function of 'adb.shell' fails. when I tested this function on the target of 'Galaxy Note5',
|
|
67
144
|
// adb.shell(dumpsys cpuinfo) returns cpu datas for other application packages, but I can't find the data for packageName.
|
|
68
145
|
// It usually fails 30 times and success for the next time,
|
|
69
146
|
// Since then, he has continued to succeed.
|
|
70
|
-
return await retryInterval(
|
|
147
|
+
return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
|
|
71
148
|
let output;
|
|
72
149
|
try {
|
|
73
150
|
output = await this.adb.shell(['dumpsys', 'cpuinfo']);
|
|
@@ -90,8 +167,8 @@ helpers.getCPUInfo = async function getCPUInfo (packageName, dataReadTimeout = 2
|
|
|
90
167
|
});
|
|
91
168
|
};
|
|
92
169
|
|
|
93
|
-
helpers.getBatteryInfo = async function getBatteryInfo (
|
|
94
|
-
return await retryInterval(
|
|
170
|
+
helpers.getBatteryInfo = async function getBatteryInfo (retries = 2) {
|
|
171
|
+
return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
|
|
95
172
|
let cmd = ['dumpsys', 'battery', '|', 'grep', 'level'];
|
|
96
173
|
let data = await this.adb.shell(cmd);
|
|
97
174
|
if (!data) throw new Error('No data from dumpsys'); //eslint-disable-line curly
|
|
@@ -104,91 +181,44 @@ helpers.getBatteryInfo = async function getBatteryInfo (dataReadTimeout = 2) {
|
|
|
104
181
|
throw new Error(`Unable to parse battery data: '${data}'`);
|
|
105
182
|
}
|
|
106
183
|
});
|
|
107
|
-
|
|
108
184
|
};
|
|
109
185
|
|
|
110
|
-
helpers.getMemoryInfo = async function getMemoryInfo (packageName,
|
|
111
|
-
return await retryInterval(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (apilevel > 18) {
|
|
132
|
-
let type = entries[0];
|
|
133
|
-
let subType = entries[1];
|
|
134
|
-
if (type === 'Native' && subType === 'Heap') {
|
|
135
|
-
// native heap
|
|
136
|
-
nativePss = entries[2];
|
|
137
|
-
nativePrivateDirty = entries[3];
|
|
138
|
-
nativeHeapSize = entries[6];
|
|
139
|
-
nativeHeapAllocatedSize = entries[7];
|
|
140
|
-
} else if (type === 'Dalvik' && subType === 'Heap') {
|
|
141
|
-
// dalvik heap
|
|
142
|
-
dalvikPss = entries[2];
|
|
143
|
-
dalvikPrivateDirty = entries[3];
|
|
144
|
-
} else if (type === 'EGL' && subType === 'mtrack') {
|
|
145
|
-
// egl
|
|
146
|
-
eglPss = entries[2];
|
|
147
|
-
eglPrivateDirty = entries[3];
|
|
148
|
-
} else if (type === 'GL' && subType === 'mtrack') {
|
|
149
|
-
// gl
|
|
150
|
-
glPss = entries[2];
|
|
151
|
-
glPrivateDirty = entries[3];
|
|
152
|
-
} else if (type === 'TOTAL' && entries.length === 8) {
|
|
153
|
-
// there are two totals, and we only want the full listing, which has 8 entries
|
|
154
|
-
totalPss = entries[1];
|
|
155
|
-
totalPrivateDirty = entries[2];
|
|
156
|
-
}
|
|
186
|
+
helpers.getMemoryInfo = async function getMemoryInfo (packageName, retries = 2) {
|
|
187
|
+
return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
|
|
188
|
+
const cmd = [
|
|
189
|
+
'dumpsys', 'meminfo', `'${packageName}'`,
|
|
190
|
+
'|', 'grep', '-E',
|
|
191
|
+
`'${MEMINFO_TITLES.NATIVE}|${MEMINFO_TITLES.DALVIK}|${MEMINFO_TITLES.EGL}` +
|
|
192
|
+
`|${MEMINFO_TITLES.GL}|${MEMINFO_TITLES.TOTAL}'`
|
|
193
|
+
];
|
|
194
|
+
const data = await this.adb.shell(cmd);
|
|
195
|
+
if (!data) {
|
|
196
|
+
throw new Error('No data from dumpsys');
|
|
197
|
+
}
|
|
198
|
+
const valDict = {totalPrivateDirty: ''};
|
|
199
|
+
const apiLevel = await this.adb.getApiLevel();
|
|
200
|
+
for (const line of data.split('\n')) {
|
|
201
|
+
const entries = line.trim().split(/\s+/).filter(Boolean);
|
|
202
|
+
if (apiLevel >= 30) {
|
|
203
|
+
parseMeminfoForApiAbove29(entries, valDict);
|
|
204
|
+
} else if (apiLevel > 18 && apiLevel < 30) {
|
|
205
|
+
parseMeminfoForApi19To29(entries, valDict);
|
|
157
206
|
} else {
|
|
158
|
-
|
|
159
|
-
if (type === 'Native') {
|
|
160
|
-
nativePss = entries[1];
|
|
161
|
-
nativePrivateDirty = entries[3];
|
|
162
|
-
nativeHeapSize = entries[4];
|
|
163
|
-
nativeHeapAllocatedSize = entries[5];
|
|
164
|
-
} else if (type === 'Dalvik') {
|
|
165
|
-
dalvikPss = entries[1];
|
|
166
|
-
dalvikPrivateDirty = entries[3];
|
|
167
|
-
} else if (type === 'EGL') {
|
|
168
|
-
eglPss = entries[1];
|
|
169
|
-
eglPrivateDirty = entries[3];
|
|
170
|
-
} else if (type === 'GL') {
|
|
171
|
-
glPss = entries[1];
|
|
172
|
-
glPrivateDirty = entries[3];
|
|
173
|
-
} else if (type === 'TOTAL') {
|
|
174
|
-
totalPss = entries[1];
|
|
175
|
-
totalPrivateDirty = entries[3];
|
|
176
|
-
}
|
|
207
|
+
parseMeminfoForApiBelow19(entries, valDict);
|
|
177
208
|
}
|
|
178
209
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return [headers, data];
|
|
184
|
-
} else {
|
|
185
|
-
throw new Error(`Unable to parse memory data: '${data}'`);
|
|
210
|
+
if (valDict.totalPrivateDirty && valDict.totalPrivateDirty !== 'nodex') {
|
|
211
|
+
const headers = _.clone(MEMORY_KEYS);
|
|
212
|
+
const values = headers.map((header) => valDict[header]);
|
|
213
|
+
return [headers, values];
|
|
186
214
|
}
|
|
215
|
+
|
|
216
|
+
throw new Error(`Unable to parse memory data: '${data}'`);
|
|
187
217
|
});
|
|
188
218
|
};
|
|
189
219
|
|
|
190
|
-
helpers.getNetworkTrafficInfo = async function getNetworkTrafficInfo (
|
|
191
|
-
return await retryInterval(
|
|
220
|
+
helpers.getNetworkTrafficInfo = async function getNetworkTrafficInfo (retries = 2) {
|
|
221
|
+
return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
|
|
192
222
|
let returnValue = [];
|
|
193
223
|
let bucketDuration, bucketStart, activeTime, rxBytes, rxPackets, txBytes, txPackets, operations;
|
|
194
224
|
|
|
@@ -346,3 +376,4 @@ export {
|
|
|
346
376
|
BATTERY_KEYS, NETWORK_KEYS,
|
|
347
377
|
};
|
|
348
378
|
export default extensions;
|
|
379
|
+
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"mobile",
|
|
10
10
|
"mobile testing"
|
|
11
11
|
],
|
|
12
|
-
"version": "4.
|
|
12
|
+
"version": "4.54.0",
|
|
13
13
|
"author": "appium",
|
|
14
14
|
"license": "Apache-2.0",
|
|
15
15
|
"repository": {
|
|
@@ -36,24 +36,24 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@babel/runtime": "^7.0.0",
|
|
39
|
-
"appium-adb": "^8.
|
|
39
|
+
"appium-adb": "^8.18.0",
|
|
40
40
|
"appium-base-driver": "^7.0.0",
|
|
41
41
|
"appium-chromedriver": "^4.13.0",
|
|
42
42
|
"appium-support": "^2.47.1",
|
|
43
43
|
"asyncbox": "^2.8.0",
|
|
44
44
|
"axios": "^0.x",
|
|
45
45
|
"bluebird": "^3.4.7",
|
|
46
|
-
"io.appium.settings": "^3.
|
|
47
|
-
"jimp": "^0.
|
|
46
|
+
"io.appium.settings": "^3.6.0",
|
|
47
|
+
"jimp": "^0.x",
|
|
48
48
|
"lodash": "^4.17.4",
|
|
49
49
|
"lru-cache": "^6.0.0",
|
|
50
50
|
"moment": "^2.24.0",
|
|
51
51
|
"moment-timezone": "^0.5.26",
|
|
52
52
|
"portfinder": "^1.0.6",
|
|
53
53
|
"portscanner": "2.2.0",
|
|
54
|
-
"shared-preferences-builder": "^0.
|
|
54
|
+
"shared-preferences-builder": "^0.x",
|
|
55
55
|
"semver": "^7.0.0",
|
|
56
|
-
"source-map-support": "^0.
|
|
56
|
+
"source-map-support": "^0.x",
|
|
57
57
|
"teen_process": "^1.9.0",
|
|
58
58
|
"ws": "^8.0.0"
|
|
59
59
|
},
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"precommit-test"
|
|
78
78
|
],
|
|
79
79
|
"devDependencies": {
|
|
80
|
+
"@xmldom/xmldom": "^0.x",
|
|
80
81
|
"android-apidemos": "^3.0.0",
|
|
81
82
|
"appium-gulp-plugins": "^5.4.0",
|
|
82
83
|
"appium-test-support": "^1.0.0",
|
|
@@ -87,8 +88,7 @@
|
|
|
87
88
|
"mocha": "^9.0.0",
|
|
88
89
|
"mock-fs": "^5.0.0",
|
|
89
90
|
"pre-commit": "^1.1.3",
|
|
90
|
-
"sinon": "^
|
|
91
|
-
"xmldom": "^0.x",
|
|
91
|
+
"sinon": "^12.0.0",
|
|
92
92
|
"xpath": "^0.x"
|
|
93
93
|
}
|
|
94
94
|
}
|