appium-espresso-driver 2.1.1 → 2.2.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.
- package/build/lib/commands/general.js +9 -15
- package/build/lib/driver.js +80 -46
- package/build/lib/espresso-runner.js +41 -66
- package/build/lib/server-builder.js +12 -21
- package/espresso-server/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk +0 -0
- package/espresso-server/app/build.gradle.kts +12 -10
- package/espresso-server/build.gradle.kts +9 -8
- package/lib/commands/general.js +6 -9
- package/lib/driver.js +96 -36
- package/lib/espresso-runner.js +40 -39
- package/lib/server-builder.js +12 -13
- package/npm-shrinkwrap.json +1221 -1533
- package/package.json +2 -2
package/lib/espresso-runner.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { JWProxy, errors } from '@appium/base-driver';
|
|
2
2
|
import { waitForCondition } from 'asyncbox';
|
|
3
|
-
import logger from './logger';
|
|
4
3
|
import { ServerBuilder, buildServerSigningConfig } from './server-builder';
|
|
5
4
|
import path from 'path';
|
|
6
5
|
import { fs, util, mkdirp, timing } from '@appium/support';
|
|
@@ -38,14 +37,16 @@ class EspressoProxy extends JWProxy {
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
class EspressoRunner {
|
|
41
|
-
constructor (opts = {}) {
|
|
40
|
+
constructor (log, opts = {}) {
|
|
42
41
|
for (let req of REQUIRED_PARAMS) {
|
|
43
42
|
if (!opts || !util.hasValue(opts[req])) {
|
|
44
43
|
throw new Error(`Option '${req}' is required!`);
|
|
45
44
|
}
|
|
46
45
|
this[req] = opts[req];
|
|
47
46
|
}
|
|
47
|
+
this.log = log;
|
|
48
48
|
this.jwproxy = new EspressoProxy({
|
|
49
|
+
log,
|
|
49
50
|
server: this.host,
|
|
50
51
|
port: this.systemPort,
|
|
51
52
|
base: '',
|
|
@@ -85,11 +86,11 @@ class EspressoRunner {
|
|
|
85
86
|
|
|
86
87
|
async isAppPackageChanged () {
|
|
87
88
|
if (!await this.adb.fileExists(TARGET_PACKAGE_CONTAINER)) {
|
|
88
|
-
|
|
89
|
+
this.log.debug('The previous target application package is unknown');
|
|
89
90
|
return true;
|
|
90
91
|
}
|
|
91
92
|
const previousAppPackage = (await this.adb.shell(['cat', TARGET_PACKAGE_CONTAINER])).trim();
|
|
92
|
-
|
|
93
|
+
this.log.debug(`The previous target application package was '${previousAppPackage}'. ` +
|
|
93
94
|
`The current package is '${this.appPackage}'`);
|
|
94
95
|
return previousAppPackage !== this.appPackage;
|
|
95
96
|
}
|
|
@@ -110,21 +111,21 @@ class EspressoRunner {
|
|
|
110
111
|
].includes(appState);
|
|
111
112
|
|
|
112
113
|
if (shouldUninstallApp) {
|
|
113
|
-
|
|
114
|
+
this.log.info(`Uninstalling Espresso Test Server apk from the target device (pkg: '${TEST_APK_PKG}')`);
|
|
114
115
|
try {
|
|
115
116
|
await this.adb.uninstallApk(TEST_APK_PKG);
|
|
116
117
|
} catch (err) {
|
|
117
|
-
|
|
118
|
+
this.log.warn(`Error uninstalling '${TEST_APK_PKG}': ${err.message}`);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
if (shouldInstallApp) {
|
|
122
|
-
|
|
123
|
+
this.log.info(`Installing Espresso Test Server apk from the target device (path: '${this.modServerPath}')`);
|
|
123
124
|
try {
|
|
124
125
|
await this.adb.install(this.modServerPath, { replace: false, timeout: this.androidInstallTimeout });
|
|
125
|
-
|
|
126
|
+
this.log.info(`Installed Espresso Test Server apk '${this.modServerPath}' (pkg: '${TEST_APK_PKG}')`);
|
|
126
127
|
} catch (err) {
|
|
127
|
-
|
|
128
|
+
this.log.errorAndThrow(`Cannot install '${this.modServerPath}' because of '${err.message}'`);
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
}
|
|
@@ -132,14 +133,14 @@ class EspressoRunner {
|
|
|
132
133
|
async installTestApk () {
|
|
133
134
|
let rebuild = this.forceEspressoRebuild;
|
|
134
135
|
if (rebuild) {
|
|
135
|
-
|
|
136
|
+
this.log.debug(`'forceEspressoRebuild' capability is enabled`);
|
|
136
137
|
} else if (await this.isAppPackageChanged()) {
|
|
137
|
-
|
|
138
|
+
this.log.info(`Forcing Espresso server rebuild because of changed application package`);
|
|
138
139
|
rebuild = true;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
if (rebuild && await fs.exists(this.modServerPath)) {
|
|
142
|
-
|
|
143
|
+
this.log.debug(`Deleting the obsolete Espresso server package '${this.modServerPath}'`);
|
|
143
144
|
await fs.unlink(this.modServerPath);
|
|
144
145
|
}
|
|
145
146
|
if (!(await fs.exists(this.modServerPath))) {
|
|
@@ -150,7 +151,7 @@ class EspressoRunner {
|
|
|
150
151
|
await this.adb.sign(this.modServerPath);
|
|
151
152
|
}
|
|
152
153
|
if ((rebuild || !isSigned) && await this.adb.uninstallApk(TEST_APK_PKG)) {
|
|
153
|
-
|
|
154
|
+
this.log.info('Uninstalled the obsolete Espresso server package from the device under test');
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
await this.installServer();
|
|
@@ -161,16 +162,16 @@ class EspressoRunner {
|
|
|
161
162
|
if (this.espressoBuildConfig) {
|
|
162
163
|
let buildConfigurationStr;
|
|
163
164
|
if (await fs.exists(this.espressoBuildConfig)) {
|
|
164
|
-
|
|
165
|
+
this.log.info(`Loading the build configuration from '${this.espressoBuildConfig}'`);
|
|
165
166
|
buildConfigurationStr = await fs.readFile(this.espressoBuildConfig, 'utf8');
|
|
166
167
|
} else {
|
|
167
|
-
|
|
168
|
+
this.log.info(`Loading the build configuration from 'espressoBuildConfig' capability`);
|
|
168
169
|
buildConfigurationStr = this.espressoBuildConfig;
|
|
169
170
|
}
|
|
170
171
|
try {
|
|
171
172
|
buildConfiguration = JSON.parse(buildConfigurationStr);
|
|
172
173
|
} catch (e) {
|
|
173
|
-
|
|
174
|
+
this.log.error('Cannot parse the build configuration JSON', e);
|
|
174
175
|
throw e;
|
|
175
176
|
}
|
|
176
177
|
}
|
|
@@ -178,26 +179,26 @@ class EspressoRunner {
|
|
|
178
179
|
replacement: '-',
|
|
179
180
|
});
|
|
180
181
|
const serverPath = path.resolve(this.tmpDir, dirName);
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
this.log.info(`Building espresso server in '${serverPath}'`);
|
|
183
|
+
this.log.debug(`The build folder root could be customized by changing the 'tmpDir' capability`);
|
|
183
184
|
await fs.rimraf(serverPath);
|
|
184
185
|
await mkdirp(serverPath);
|
|
185
|
-
|
|
186
|
+
this.log.debug(`Copying espresso server template from ('${TEST_SERVER_ROOT}' to '${serverPath}')`);
|
|
186
187
|
await copyGradleProjectRecursively(TEST_SERVER_ROOT, serverPath);
|
|
187
|
-
|
|
188
|
-
await new ServerBuilder({
|
|
188
|
+
this.log.debug('Bulding espresso server');
|
|
189
|
+
await new ServerBuilder(this.log, {
|
|
189
190
|
serverPath, buildConfiguration,
|
|
190
191
|
showGradleLog: this.showGradleLog,
|
|
191
192
|
testAppPackage: this.appPackage,
|
|
192
193
|
signingConfig: this.signingConfig
|
|
193
194
|
}).build();
|
|
194
195
|
const apkPath = path.resolve(serverPath, 'app', 'build', 'outputs', 'apk', 'androidTest', 'debug', 'app-debug-androidTest.apk');
|
|
195
|
-
|
|
196
|
+
this.log.debug(`Copying built apk from '${apkPath}' to '${this.modServerPath}'`);
|
|
196
197
|
await fs.copyFile(apkPath, this.modServerPath);
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
async cleanupSessionLeftovers () {
|
|
200
|
-
|
|
201
|
+
this.log.debug('Performing cleanup of automation leftovers');
|
|
201
202
|
|
|
202
203
|
try {
|
|
203
204
|
const {value} = (await axios({
|
|
@@ -206,8 +207,8 @@ class EspressoRunner {
|
|
|
206
207
|
})).data;
|
|
207
208
|
const activeSessionIds = value.map((sess) => sess.id);
|
|
208
209
|
if (activeSessionIds.length) {
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
this.log.debug(`The following obsolete sessions are still running: ${JSON.stringify(activeSessionIds)}`);
|
|
211
|
+
this.log.debug('Cleaning up the obsolete sessions');
|
|
211
212
|
await B.all(activeSessionIds.map((id) =>
|
|
212
213
|
axios({
|
|
213
214
|
url: `http://${this.host}:${this.systemPort}/session/${id}`,
|
|
@@ -217,10 +218,10 @@ class EspressoRunner {
|
|
|
217
218
|
// Let all sessions to be properly terminated before continuing
|
|
218
219
|
await B.delay(1000);
|
|
219
220
|
} else {
|
|
220
|
-
|
|
221
|
+
this.log.debug('No obsolete sessions have been detected');
|
|
221
222
|
}
|
|
222
223
|
} catch (e) {
|
|
223
|
-
|
|
224
|
+
this.log.debug(`No obsolete sessions have been detected (${e.message})`);
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -241,7 +242,7 @@ class EspressoRunner {
|
|
|
241
242
|
|
|
242
243
|
cmd.push(`${TEST_APK_PKG}/androidx.test.runner.AndroidJUnitRunner`);
|
|
243
244
|
|
|
244
|
-
|
|
245
|
+
this.log.info(`Starting Espresso Server v${version} with cmd: adb ${cmd.join(' ')}`);
|
|
245
246
|
|
|
246
247
|
let hasSocketError = false;
|
|
247
248
|
// start the instrumentation process
|
|
@@ -251,7 +252,7 @@ class EspressoRunner {
|
|
|
251
252
|
};
|
|
252
253
|
this.instProcess = this.adb.createSubProcess(cmd);
|
|
253
254
|
this.instProcess.on('exit', (code, signal) => {
|
|
254
|
-
|
|
255
|
+
this.log.info(`Instrumentation process exited with code ${code} from signal ${signal}`);
|
|
255
256
|
this.jwproxy.instrumentationState.exited = true;
|
|
256
257
|
});
|
|
257
258
|
this.instProcess.on('output', (stdout, stderr) => {
|
|
@@ -261,7 +262,7 @@ class EspressoRunner {
|
|
|
261
262
|
return;
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
|
|
265
|
+
this.log.debug(`[Instrumentation] ${line.trim()}`);
|
|
265
266
|
// A 'SocketException' indicates that we couldn't connect to the Espresso server,
|
|
266
267
|
// because the INTERNET permission is not set
|
|
267
268
|
if (line.toLowerCase().includes('java.net.socketexception')) {
|
|
@@ -273,15 +274,15 @@ class EspressoRunner {
|
|
|
273
274
|
|
|
274
275
|
const timer = new timing.Timer().start();
|
|
275
276
|
await this.instProcess.start(0);
|
|
276
|
-
|
|
277
|
+
this.log.info(`Waiting up to ${this.serverLaunchTimeout}ms for Espresso server to be online`);
|
|
277
278
|
try {
|
|
278
279
|
await waitForCondition(async () => {
|
|
279
280
|
if (hasSocketError) {
|
|
280
|
-
|
|
281
|
+
this.log.errorAndThrow(`Espresso server has failed to start due to an unexpected exception. ` +
|
|
281
282
|
`Make sure the 'INTERNET' permission is requested in the Android manifest of your ` +
|
|
282
283
|
`application under test (<uses-permission android:name="android.permission.INTERNET" />)`);
|
|
283
284
|
} else if (this.jwproxy.instrumentationState.exited) {
|
|
284
|
-
|
|
285
|
+
this.log.errorAndThrow(`Espresso server process has been unexpectedly terminated. ` +
|
|
285
286
|
`Check the Appium server log and the logcat output for more details`);
|
|
286
287
|
}
|
|
287
288
|
try {
|
|
@@ -296,15 +297,15 @@ class EspressoRunner {
|
|
|
296
297
|
});
|
|
297
298
|
} catch (e) {
|
|
298
299
|
if (/Condition unmet/.test(e.message)) {
|
|
299
|
-
|
|
300
|
+
this.log.errorAndThrow(`Timed out waiting for Espresso server to be ` +
|
|
300
301
|
`online within ${this.serverLaunchTimeout}ms. The timeout value could be ` +
|
|
301
302
|
`customized using 'espressoServerLaunchTimeout' capability`);
|
|
302
303
|
}
|
|
303
304
|
throw e;
|
|
304
305
|
}
|
|
305
|
-
|
|
306
|
+
this.log.info(`Espresso server is online. ` +
|
|
306
307
|
`The initialization process took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
|
|
307
|
-
|
|
308
|
+
this.log.info('Starting the session');
|
|
308
309
|
|
|
309
310
|
await this.jwproxy.command('/session', 'POST', {
|
|
310
311
|
capabilities: {
|
|
@@ -317,17 +318,17 @@ class EspressoRunner {
|
|
|
317
318
|
|
|
318
319
|
async recordTargetAppPackage () {
|
|
319
320
|
await this.adb.shell([`echo "${this.appPackage}" > "${TARGET_PACKAGE_CONTAINER}"`]);
|
|
320
|
-
|
|
321
|
+
this.log.info(`Recorded the target application package '${this.appPackage}' to ${TARGET_PACKAGE_CONTAINER}`);
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
async deleteSession () {
|
|
324
|
-
|
|
325
|
+
this.log.debug('Deleting Espresso server session');
|
|
325
326
|
// rely on jwproxy's intelligence to know what we're talking about and
|
|
326
327
|
// delete the current session
|
|
327
328
|
try {
|
|
328
329
|
await this.jwproxy.command('/', 'DELETE');
|
|
329
330
|
} catch (err) {
|
|
330
|
-
|
|
331
|
+
this.log.warn(`Did not get confirmation Espresso deleteSession worked; ` +
|
|
331
332
|
`Error was: ${err}`);
|
|
332
333
|
}
|
|
333
334
|
|
package/lib/server-builder.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { SubProcess } from 'teen_process';
|
|
2
|
-
import { fs,
|
|
2
|
+
import { fs, system } from '@appium/support';
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
-
import log from './logger';
|
|
5
4
|
import path from 'path';
|
|
6
5
|
import { EOL } from 'os';
|
|
7
6
|
import { updateDependencyLines } from './utils';
|
|
@@ -22,11 +21,10 @@ const VERSION_KEYS = [
|
|
|
22
21
|
'kotlin',
|
|
23
22
|
'sourceCompatibility',
|
|
24
23
|
'targetCompatibility',
|
|
25
|
-
'jvmTarget'
|
|
24
|
+
'jvmTarget',
|
|
25
|
+
'composeVersion'
|
|
26
26
|
];
|
|
27
27
|
|
|
28
|
-
const gradleLog = logger.getLogger('Gradle');
|
|
29
|
-
|
|
30
28
|
function buildServerSigningConfig (args) {
|
|
31
29
|
return {
|
|
32
30
|
zipAlign: true,
|
|
@@ -38,7 +36,8 @@ function buildServerSigningConfig (args) {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
class ServerBuilder {
|
|
41
|
-
constructor (args = {}) {
|
|
39
|
+
constructor (log, args = {}) {
|
|
40
|
+
this.log = log;
|
|
42
41
|
this.serverPath = args.serverPath;
|
|
43
42
|
this.showGradleLog = args.showGradleLog;
|
|
44
43
|
|
|
@@ -73,7 +72,7 @@ class ServerBuilder {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
getCommand () {
|
|
76
|
-
const cmd = system.isWindows() ? 'gradlew.bat' : '
|
|
75
|
+
const cmd = system.isWindows() ? 'gradlew.bat' : path.resolve(this.serverPath, 'gradlew');
|
|
77
76
|
const buildProperty = (key, value) => value ? `-P${key}=${value}` : null;
|
|
78
77
|
let args = VERSION_KEYS
|
|
79
78
|
.filter((key) => key !== GRADLE_VERSION_KEY)
|
|
@@ -150,7 +149,7 @@ class ServerBuilder {
|
|
|
150
149
|
continue;
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
log.info(`Adding the following ${propName} to build.gradle.kts: ${deps}`);
|
|
152
|
+
this.log.info(`Adding the following ${propName} to build.gradle.kts: ${deps}`);
|
|
154
153
|
configuration = updateDependencyLines(configuration, propName, deps);
|
|
155
154
|
}
|
|
156
155
|
await fs.writeFile(buildPath, configuration, 'utf8');
|
|
@@ -158,7 +157,7 @@ class ServerBuilder {
|
|
|
158
157
|
|
|
159
158
|
async runBuildProcess () {
|
|
160
159
|
const {cmd, args} = this.getCommand();
|
|
161
|
-
log.debug(`Beginning build with command '${cmd} ${args.join(' ')}' ` +
|
|
160
|
+
this.log.debug(`Beginning build with command '${cmd} ${args.join(' ')}' ` +
|
|
162
161
|
`in directory '${this.serverPath}'`);
|
|
163
162
|
const gradlebuild = new SubProcess(cmd, args, {
|
|
164
163
|
cwd: this.serverPath,
|
|
@@ -168,13 +167,13 @@ class ServerBuilder {
|
|
|
168
167
|
let buildLastLines = [];
|
|
169
168
|
|
|
170
169
|
const logMsg = `Output from Gradle ${this.showGradleLog ? 'will' : 'will not'} be logged`;
|
|
171
|
-
log.debug(`${logMsg}. To change this, use 'showGradleLog' desired capability`);
|
|
170
|
+
this.log.debug(`${logMsg}. To change this, use 'showGradleLog' desired capability`);
|
|
172
171
|
gradlebuild.on('stream-line', (line) => {
|
|
173
172
|
if (this.showGradleLog) {
|
|
174
173
|
if (line.startsWith('[STDERR]')) {
|
|
175
|
-
|
|
174
|
+
this.log.warn(`[Gradle] ${line}`);
|
|
176
175
|
} else {
|
|
177
|
-
|
|
176
|
+
this.log.info(`[Gradle] ${line}`);
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
buildLastLines.push(`${EOL}${line}`);
|
|
@@ -189,7 +188,7 @@ class ServerBuilder {
|
|
|
189
188
|
} catch (err) {
|
|
190
189
|
let msg = `Unable to build Espresso server - ${err.message}\n` +
|
|
191
190
|
`Gradle error message:${EOL}${buildLastLines}`;
|
|
192
|
-
log.errorAndThrow(msg);
|
|
191
|
+
this.log.errorAndThrow(msg);
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
}
|