From 3302a69a01e4f2c30ea9666e0a379cf52149c5d0 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Thu, 20 Sep 2018 11:33:26 +0300 Subject: [PATCH 1/9] fix: using adb logcat options compatible with API level 19 --- detox/src/devices/android/ADB.js | 110 ++++++++++---------- detox/src/devices/android/ADB.test.js | 30 +++--- detox/src/devices/android/EmulatorTelnet.js | 16 +-- detox/src/utils/exec.js | 8 ++ detox/src/utils/pipeCommands.js | 32 +++--- 5 files changed, 101 insertions(+), 95 deletions(-) diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index d1bb1daa2d..4e9ef00926 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -1,18 +1,19 @@ const _ = require('lodash'); const path = require('path'); const {execWithRetriesAndLogs, spawnAndLog} = require('../../utils/exec'); -const pipeCommands = require('../../utils/pipeCommands'); +const {escape} = require('../../utils/pipeCommands'); const EmulatorTelnet = require('./EmulatorTelnet'); const Environment = require('../../utils/environment'); class ADB { constructor() { + this._cachedApiLevels = new Map(); this.adbBin = path.join(Environment.getAndroidSDKPath(), 'platform-tools', 'adb'); } async devices() { const output = (await this.adbCmd('', 'devices')).stdout; - return await this.parseAdbDevicesConsoleOutput(output); + return this.parseAdbDevicesConsoleOutput(output); } async unlockScreen(deviceId) { @@ -31,8 +32,7 @@ class ADB { } async _getPowerStatus(deviceId) { - const grep = pipeCommands.search.regexp; - const stdout = await this.shell(deviceId, `dumpsys power | ${grep('^[ ]*m[UW].*=')}`); + const stdout = await this.shell(deviceId, `dumpsys power | grep "^[ ]*m[UW].*="`); return stdout .split('\n') @@ -89,7 +89,7 @@ class ADB { } async now(deviceId) { - return this.shell(deviceId, `date "+\\"%Y-%m-%d %T.000\\""`); + return this.shell(deviceId, `date +"%Y-%m-%d %T.000"`); } async install(deviceId, apkPath) { @@ -110,10 +110,9 @@ class ADB { } async pidof(deviceId, bundleId) { - const bundleIdRegex = pipeCommands.escape.inQuotedRegexp(bundleId) + '[ ]*$'; - const grep = pipeCommands.search.regexp; + const bundleIdRegex = escape.inQuotedRegexp(bundleId) + '$'; - const processes = await this.shell(deviceId, `ps | ${grep(bundleIdRegex)}`).catch(() => ''); + const processes = await this.shell(deviceId, `ps | grep "${bundleIdRegex}"`).catch(() => ''); if (!processes) { return NaN; } @@ -121,12 +120,8 @@ class ADB { return parseInt(processes.split(' ').filter(Boolean)[1], 10); } - async shell(deviceId, cmd, options) { - return (await this.adbCmd(deviceId, `shell ${cmd}`, options)).stdout.trim(); - } - async getFileSize(deviceId, filename) { - const { stdout, stderr } = await this.adbCmd(deviceId, 'shell wc -c ' + filename).catch(e => e); + const { stdout, stderr } = await this.adbCmd(deviceId, 'shell du ' + filename).catch(e => e); if (stderr.includes('No such file or directory')) { return -1; @@ -136,7 +131,7 @@ class ADB { } async isFileOpen(deviceId, filename) { - const openedByProcesses = await this.shell(deviceId, 'lsof ' + filename); + const openedByProcesses = await this.shell(deviceId, `lsof | grep -e "${escape.inQuotedString(filename)}" || true`); return openedByProcesses.length > 0; } @@ -150,12 +145,14 @@ class ADB { } async apiLevel(deviceId) { - const lvl = await this.shell(deviceId, `getprop ro.build.version.sdk`); - return Number(lvl); + const lvl = Number(await this.shell(deviceId, `getprop ro.build.version.sdk`)); + this._cachedApiLevels.set(deviceId, lvl); + + return lvl; } async screencap(deviceId, path) { - return this.adbCmd(deviceId, `shell screencap ${path}`); + await this.shell(deviceId, `screencap ${path}`); } /*** @@ -185,37 +182,57 @@ class ADB { /*** * @returns ChildProcessPromise */ - logcat(deviceId, { expression, file, pid, time }) { - const logcatArgs = []; - - if (expression) { - logcatArgs.push('-e'); - logcatArgs.push(expression); - } - - if (file) { - logcatArgs.push('-f'); - logcatArgs.push(file); + logcat(deviceId, { file, pid, time }) { + let shellCommand = 'logcat -v brief'; + + // HACK: cannot make this function async, otherwise ChildProcessPromise.childProcess field will get lost, + // and this will break interruptProcess() call for any logcat promise. + const apiLevel = this._cachedApiLevels.get(deviceId); + if (time && apiLevel >= 21) { + shellCommand += ` -T "${time}"`; } if (pid > 0) { - logcatArgs.push(`--pid=${pid}`); - } + const __pid = String(pid).padStart(5); + shellCommand += ` | grep "(${__pid}):"`; + } - if (time) { - logcatArgs.push('-T'); - logcatArgs.push(time); + if (file) { + shellCommand += ` >> ${file}`; } - return this.spawn(deviceId, ['logcat', ...logcatArgs]); + return this.spawn(deviceId, ['shell', shellCommand]); } async pull(deviceId, src, dst = '') { - return this.adbCmd(deviceId, `pull "${src}" "${dst}"`); + await this.adbCmd(deviceId, `pull "${src}" "${dst}"`); } async rm(deviceId, path, force = false) { - return this.adbCmd(deviceId, `shell rm ${force ? '-f' : ''} "${path}"`); + await this.shell(deviceId, `rm ${force ? '-f' : ''} "${path}"`); + } + + async listInstrumentation(deviceId) { + return this.shell(deviceId, 'pm list instrumentation'); + } + + async getInstrumentationRunner(deviceId, bundleId) { + const instrumentationRunners = await this.listInstrumentation(deviceId); + const instrumentationRunner = this._instrumentationRunnerForBundleId(instrumentationRunners, bundleId); + if (instrumentationRunner === 'undefined') { + throw new Error(`No instrumentation runner found on device ${deviceId} for package ${bundleId}`); + } + + return instrumentationRunner; + } + + _instrumentationRunnerForBundleId(instrumentationRunners, bundleId) { + const runnerForBundleRegEx = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm'); + return _.get(runnerForBundleRegEx.exec(instrumentationRunners), [1], 'undefined'); + } + + async shell(deviceId, cmd, options) { + return (await this.adbCmd(deviceId, `shell "${escape.inQuotedString(cmd)}"`, options)).stdout.trim(); } async adbCmd(deviceId, params, options) { @@ -224,7 +241,7 @@ class ADB { const retries = _.get(options, 'retries', 1); _.unset(options, 'retries'); - return await execWithRetriesAndLogs(cmd, options, undefined, retries); + return execWithRetriesAndLogs(cmd, options, undefined, retries); } /*** @@ -234,25 +251,6 @@ class ADB { const serial = deviceId ? ['-s', deviceId] : []; return spawnAndLog(this.adbBin, [...serial, ...params]); } - - async listInstrumentation(deviceId) { - return await this.shell(deviceId, 'pm list instrumentation'); - } - - instrumentationRunnerForBundleId(instrumentationRunners, bundleId) { - const runnerForBundleRegEx = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm'); - return _.get(runnerForBundleRegEx.exec(instrumentationRunners), [1], 'undefined'); - } - - async getInstrumentationRunner(deviceId, bundleId) { - const instrumentationRunners = await this.listInstrumentation(deviceId); - const instrumentationRunner = this.instrumentationRunnerForBundleId(instrumentationRunners, bundleId); - if (instrumentationRunner === 'undefined') { - throw new Error(`No instrumentation runner found on device ${deviceId} for package ${bundleId}`); - } - - return instrumentationRunner; - } } module.exports = ADB; diff --git a/detox/src/devices/android/ADB.test.js b/detox/src/devices/android/ADB.test.js index 16c5da82cb..abd7f230db 100644 --- a/detox/src/devices/android/ADB.test.js +++ b/detox/src/devices/android/ADB.test.js @@ -46,12 +46,14 @@ describe('ADB', () => { }); it(`pidof (success)`, async () => { - adb.shell = async () => `u0_a19 2199 1701 3554600 70264 0 0 s com.google.android.ext.services `; + jest.spyOn(adb, 'shell').mockImplementation(async () => + `u0_a19 2199 1701 3554600 70264 0 0 s com.google.android.ext.services `); + expect(await adb.pidof('', 'com.google.android.ext.services')).toBe(2199); }); it(`pidof (failure)`, async () => { - adb.shell = async () => ``; + jest.spyOn(adb, 'shell').mockImplementation(async () => ''); expect(await adb.pidof('', 'com.google.android.ext.services')).toBe(NaN); }); @@ -59,7 +61,7 @@ describe('ADB', () => { const deviceId = 'mockEmulator'; async function unlockScreenWithPowerStatus(mWakefulness, mUserActivityTimeoutOverrideFromWindowManager) { - adb.shell = jest.fn().mockReturnValue(` + jest.spyOn(adb, 'shell').mockImplementation(async () => ` mWakefulness=${mWakefulness} mWakefulnessChanging=false mWakeLockSummary=0x0 @@ -116,11 +118,11 @@ describe('ADB', () => { it(`listInstrumentation passes the right deviceId`, async () => { const deviceId = 'aDeviceId'; - const spyShell = jest.spyOn(adb, 'shell'); + jest.spyOn(adb, 'shell'); await adb.listInstrumentation(deviceId); - expect(spyShell).toBeCalledWith(deviceId, expect.any(String)); + expect(adb.shell).toBeCalledWith(deviceId, 'pm list instrumentation'); }); it(`Parse 'adb device' output`, async () => { @@ -144,18 +146,7 @@ describe('ADB', () => { expect(actual).toEqual(parsedDevices); }); - it(`getInstrumentationRunner passes the right deviceId`, async () => { - const deviceId = 'aDeviceId'; - const spyRunnerForBundle = jest.spyOn(adb, 'instrumentationRunnerForBundleId'); - spyRunnerForBundle.mockReturnValue(''); - const spyShell = jest.spyOn(adb, 'shell'); - - await adb.getInstrumentationRunner(deviceId, 'com.whatever.package'); - - expect(spyShell).toBeCalledWith(deviceId, expect.any(String)); - }); - - it(`instrumentationRunnerForBundleId parses the correct runner for the package`, async () => { + it(`getInstrumentationRunner parses the correct runner for the package`, async () => { const expectedRunner = "com.example.android.apis/.app.LocalSampleInstrumentation"; const expectedPackage = "com.example.android.apis"; const instrumentationRunnersShellOutput = @@ -164,8 +155,11 @@ describe('ADB', () => { `instrumentation:${expectedRunner} (target=${expectedPackage})\n` + "instrumentation:org.chromium.webview_shell/.WebViewLayoutTestRunner (target=org.chromium.webview_shell)\n"; - const result = await adb.instrumentationRunnerForBundleId(instrumentationRunnersShellOutput, expectedPackage); + jest.spyOn(adb, 'shell').mockImplementation(async () => instrumentationRunnersShellOutput); + + const result = await adb.getInstrumentationRunner('aDeviceId', expectedPackage); + expect(adb.shell).toBeCalledWith('aDeviceId', 'pm list instrumentation'); expect(result).toEqual(expectedRunner); }); }); diff --git a/detox/src/devices/android/EmulatorTelnet.js b/detox/src/devices/android/EmulatorTelnet.js index 94765eb688..b5af8c8766 100644 --- a/detox/src/devices/android/EmulatorTelnet.js +++ b/detox/src/devices/android/EmulatorTelnet.js @@ -2,10 +2,13 @@ const Telnet = require('telnet-client'); const path = require('path'); const os = require('os'); const fs = require('fs-extra'); +const log = require('../../utils/logger').child({ __filename }); class EmulatorTelnet { constructor() { this.connection = new Telnet(); + this.connection.on('timeout', () => log.error({ event: 'TELNET_TIMEOUT' })); + this.connection.on('error', (err) => log.error({ event: 'TELNET_ERROR', err })); } async connect(port) { @@ -20,6 +23,7 @@ class EmulatorTelnet { stripShellPrompt: true }; + log.trace({ event: 'TELNET_CONNECTING' }, `port: ${port}, host: ${params.host}`); await this.connection.connect(params); const auth = await fs.readFile(path.join(os.homedir(), '.emulator_console_auth_token'), 'utf8'); await this.exec(`auth ${auth}`); @@ -36,18 +40,18 @@ class EmulatorTelnet { this.connection.shell((error, stream) => { stream.write(`${command}\n`); stream.on('data', (data) => { - const result = data.toString(); - if (result.includes('\n')) { - resolve(result); + const result = data.toString(); + if (result.includes('\n')) { + resolve(result); + } } - } ); }); }); } async avdName() { - return await this.exec('avd name'); + return this.exec('avd name'); } async kill() { @@ -61,7 +65,7 @@ class EmulatorTelnet { } async rotate() { - return await this.shell('rotate'); + return this.shell('rotate'); } } diff --git a/detox/src/utils/exec.js b/detox/src/utils/exec.js index 9b38f6ffe7..3a662544e9 100644 --- a/detox/src/utils/exec.js +++ b/detox/src/utils/exec.js @@ -54,6 +54,14 @@ async function execWithRetriesAndLogs(bin, options, statusLogs, retries = 10, in // log.error(`${_operationCounter}: stderr:`, result.stderr); //} + if (typeof result.stdout === 'string') { + result.stdout = result.stdout.replace(/\r\n/g, '\n'); + } + + if (typeof result.stderr === 'string') { + result.stderr = result.stderr.replace(/\r\n/g, '\n'); + } + return result; } diff --git a/detox/src/utils/pipeCommands.js b/detox/src/utils/pipeCommands.js index 0653c7f1dc..c0be14988d 100644 --- a/detox/src/utils/pipeCommands.js +++ b/detox/src/utils/pipeCommands.js @@ -1,13 +1,17 @@ +const SPECIAL_CHARS = /([\^\$\[\]\*\.\\])/g; + +const escapeInQuotedString = (fragment) => fragment.replace(/"/g, '\\"'); +const escapeInQuotedRegexp = (fragment) => fragment.replace(SPECIAL_CHARS, "\\$1"); + function win32Implementation() { - const escapeInQuotedStringWin32 = (fragment) => fragment.replace(/"/g, '""'); - const escapeInQuotedRegexpWin32 = escapeInQuotedStringWin32; - const searchRegexpWin32 = (pattern) => `findstr /R /C:"${escapeInQuotedStringWin32(pattern)}"`; - const searchFragmentWin32 = (fragment) => `findstr /C:"${escapeInQuotedStringWin32(fragment)}"`; + const addCRLF = 'find /v ""'; + const searchRegexpWin32 = (pattern) => `${addCRLF} | findstr /R /C:"${escapeInQuotedString(pattern)}"`; + const searchFragmentWin32 = (fragment) => `${addCRLF} | findstr /C:"${escapeInQuotedString(fragment)}"`; return { escape: { - inQuotedString: escapeInQuotedStringWin32, - inQuotedRegexp: escapeInQuotedRegexpWin32, + inQuotedString: escapeInQuotedString, + inQuotedRegexp: escapeInQuotedRegexp, }, search: { regexp: searchRegexpWin32, @@ -17,17 +21,13 @@ function win32Implementation() { } function nixImplementation() { - const SPECIAL_CHARS = /(["\^\$\[\]\*\.\\])/g; - - const escapeInQuotedStringNix = (fragment) => fragment.replace(/"/g, '\\"'); - const escapeInQuotedRegexpNix = (fragment) => fragment.replace(SPECIAL_CHARS, "\\$1"); - const searchRegexpNix = (pattern) => `grep "${escapeInQuotedStringNix(pattern)}"`; - const searchFragmentNix = (fragment) => `grep -e "${escapeInQuotedStringNix(fragment)}"`; + const searchRegexpNix = (pattern) => `grep "${escapeInQuotedString(pattern)}"`; + const searchFragmentNix = (fragment) => `grep -e "${escapeInQuotedString(fragment)}"`; return { escape: { - inQuotedString: escapeInQuotedStringNix, - inQuotedRegexp: escapeInQuotedRegexpNix, + inQuotedString: escapeInQuotedString, + inQuotedRegexp: escapeInQuotedRegexp, }, search: { regexp: searchRegexpNix, @@ -36,6 +36,8 @@ function nixImplementation() { }; } -module.exports = process.platform === 'win32' +const isRunningInCMDEXE = process.platform === 'win32' && !process.env['SHELL']; + +module.exports = isRunningInCMDEXE ? win32Implementation() : nixImplementation(); From 45445e5813c058321b6678862c81e8f46092874b Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Thu, 20 Sep 2018 19:11:47 +0300 Subject: [PATCH 2/9] fix: added basic error handling for ADB.install --- detox/src/devices/android/ADB.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index 4e9ef00926..23e42afbda 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const path = require('path'); const {execWithRetriesAndLogs, spawnAndLog} = require('../../utils/exec'); const {escape} = require('../../utils/pipeCommands'); +const DetoxRuntimeError = require('../../errors/DetoxRuntimeError'); const EmulatorTelnet = require('./EmulatorTelnet'); const Environment = require('../../utils/environment'); @@ -89,15 +90,25 @@ class ADB { } async now(deviceId) { - return this.shell(deviceId, `date +"%Y-%m-%d %T.000"`); + return this.shell(deviceId, `date +"%m-%d %T.000"`); } async install(deviceId, apkPath) { const apiLvl = await this.apiLevel(deviceId); + + let childProcess; if (apiLvl >= 24) { - await this.adbCmd(deviceId, `install -r -g ${apkPath}`); + childProcess = await this.adbCmd(deviceId, `install -r -g ${apkPath}`); } else { - await this.adbCmd(deviceId, `install -rg ${apkPath}`); + childProcess = await this.adbCmd(deviceId, `install -rg ${apkPath}`); + } + + const [failure] = (childProcess.stdout || '').match(/^Failure \[.*\]$/m) || []; + if (failure) { + throw new DetoxRuntimeError({ + message: `Failed to install app on ${this.devices()}: ${apkPath}`, + debugInfo: failure, + }); } } From 289c1f935de1ff5fce2d78564ef3152dcfb5f948 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Fri, 21 Sep 2018 16:47:48 +0300 Subject: [PATCH 3/9] fix: lsof, api level caching --- .../src/artifacts/log/android/ADBLogcatRecording.js | 12 +++++++++--- detox/src/devices/android/ADB.js | 9 +++++++-- detox/src/devices/drivers/EmulatorDriver.js | 1 + detox/src/utils/pipeCommands.js | 5 ++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/detox/src/artifacts/log/android/ADBLogcatRecording.js b/detox/src/artifacts/log/android/ADBLogcatRecording.js index 6226d949c5..1b172cff35 100644 --- a/detox/src/artifacts/log/android/ADBLogcatRecording.js +++ b/detox/src/artifacts/log/android/ADBLogcatRecording.js @@ -48,11 +48,13 @@ class ADBLogcatRecording extends Artifact { await this._waitUntilLogFileIsCreated; } finally { if (this.processPromise) { + const pid = this.processPromise.childProcess.pid; await interruptProcess(this.processPromise); this.processPromise = null; this._waitWhileLogIsOpenedByLogcat = sleep(300).then(() => { - return retry(() => this._assertLogIsNotOpenedByApps()); + // NOTE: see if we really need this check + return retry(() => this._assertLogIsNotOpenedByProcess(pid)); }); } } @@ -79,8 +81,12 @@ class ADBLogcatRecording extends Artifact { } } - async _assertLogIsNotOpenedByApps() { - const isFileOpen = await this.adb.isFileOpen(this.deviceId, this.pathToLogOnDevice); + async _assertLogIsNotOpenedByProcess(pid) { + if (!pid) { + return; + } + + const isFileOpen = await this.adb.lsof(this.deviceId, pid); if (isFileOpen) { throw new DetoxRuntimeError({ diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index 23e42afbda..f6a895057d 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -141,8 +141,9 @@ class ADB { return Number(stdout.slice(0, stdout.indexOf(' '))); } - async isFileOpen(deviceId, filename) { - const openedByProcesses = await this.shell(deviceId, `lsof | grep -e "${escape.inQuotedString(filename)}" || true`); + async lsof(deviceId, pid) { + const pidParam = ((await this.apiLevel(deviceId)) >= 24) ? `-p ${pid}` : `${pid}`; + const openedByProcesses = await this.shell(deviceId, `lsof ${pidParam} || true`); return openedByProcesses.length > 0; } @@ -156,6 +157,10 @@ class ADB { } async apiLevel(deviceId) { + if (this._cachedApiLevels.has(deviceId)) { + return this._cachedApiLevels.get(deviceId); + } + const lvl = Number(await this.shell(deviceId, `getprop ro.build.version.sdk`)); this._cachedApiLevels.set(deviceId, lvl); diff --git a/detox/src/devices/drivers/EmulatorDriver.js b/detox/src/devices/drivers/EmulatorDriver.js index ddb22c8984..57ccccdcab 100644 --- a/detox/src/devices/drivers/EmulatorDriver.js +++ b/detox/src/devices/drivers/EmulatorDriver.js @@ -91,6 +91,7 @@ class EmulatorDriver extends AndroidDriver { await this._fixEmulatorConfigIniSkinName(avdName); const adbName = await this.boot(avdName); + await this.adb.apiLevel(adbName); await this.adb.unlockScreen(adbName); return adbName; diff --git a/detox/src/utils/pipeCommands.js b/detox/src/utils/pipeCommands.js index c0be14988d..30014edb5b 100644 --- a/detox/src/utils/pipeCommands.js +++ b/detox/src/utils/pipeCommands.js @@ -4,9 +4,8 @@ const escapeInQuotedString = (fragment) => fragment.replace(/"/g, '\\"'); const escapeInQuotedRegexp = (fragment) => fragment.replace(SPECIAL_CHARS, "\\$1"); function win32Implementation() { - const addCRLF = 'find /v ""'; - const searchRegexpWin32 = (pattern) => `${addCRLF} | findstr /R /C:"${escapeInQuotedString(pattern)}"`; - const searchFragmentWin32 = (fragment) => `${addCRLF} | findstr /C:"${escapeInQuotedString(fragment)}"`; + const searchRegexpWin32 = (pattern) => `findstr /R /C:"${escapeInQuotedString(pattern)}"`; + const searchFragmentWin32 = (fragment) => `findstr /C:"${escapeInQuotedString(fragment)}"`; return { escape: { From f760a1800550e4dea1136cb6708d327774dd7db3 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Tue, 25 Sep 2018 19:35:38 +0300 Subject: [PATCH 4/9] fix: redundant lsof, using a dedicated module for sanitizing filenames --- detox/package.json | 1 + .../log/android/ADBLogcatRecording.js | 25 +++---------------- detox/src/devices/android/ADB.js | 6 ----- detox/src/utils/constructSafeFilename.js | 7 +++++- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/detox/package.json b/detox/package.json index 9df05f5b0c..dd0bf6f4cc 100644 --- a/detox/package.json +++ b/detox/package.json @@ -49,6 +49,7 @@ "lodash": "^4.17.5", "minimist": "^1.2.0", "proper-lockfile": "^3.0.2", + "sanitize-filename": "^1.6.1", "shell-utils": "^1.0.9", "tail": "^1.2.3", "telnet-client": "0.15.3", diff --git a/detox/src/artifacts/log/android/ADBLogcatRecording.js b/detox/src/artifacts/log/android/ADBLogcatRecording.js index 1b172cff35..3444c1dabf 100644 --- a/detox/src/artifacts/log/android/ADBLogcatRecording.js +++ b/detox/src/artifacts/log/android/ADBLogcatRecording.js @@ -48,14 +48,11 @@ class ADBLogcatRecording extends Artifact { await this._waitUntilLogFileIsCreated; } finally { if (this.processPromise) { - const pid = this.processPromise.childProcess.pid; - await interruptProcess(this.processPromise); - this.processPromise = null; + const processPromise = this.processPromise; - this._waitWhileLogIsOpenedByLogcat = sleep(300).then(() => { - // NOTE: see if we really need this check - return retry(() => this._assertLogIsNotOpenedByProcess(pid)); - }); + this._waitWhileLogIsOpenedByLogcat = sleep(300) + .then(() => interruptProcess(processPromise)) + .then(() => { this.processPromise = null; }); } } } @@ -80,20 +77,6 @@ class ADBLogcatRecording extends Artifact { }); } } - - async _assertLogIsNotOpenedByProcess(pid) { - if (!pid) { - return; - } - - const isFileOpen = await this.adb.lsof(this.deviceId, pid); - - if (isFileOpen) { - throw new DetoxRuntimeError({ - message: `The log is still being opened on device (${this.deviceId}) at path: ${this.pathToLogOnDevice}`, - }); - } - } } module.exports = ADBLogcatRecording; \ No newline at end of file diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index f6a895057d..9c1543d1db 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -141,12 +141,6 @@ class ADB { return Number(stdout.slice(0, stdout.indexOf(' '))); } - async lsof(deviceId, pid) { - const pidParam = ((await this.apiLevel(deviceId)) >= 24) ? `-p ${pid}` : `${pid}`; - const openedByProcesses = await this.shell(deviceId, `lsof ${pidParam} || true`); - return openedByProcesses.length > 0; - } - async isBootComplete(deviceId) { try { const bootComplete = await this.shell(deviceId, `getprop dev.bootcomplete`); diff --git a/detox/src/utils/constructSafeFilename.js b/detox/src/utils/constructSafeFilename.js index f7f54af2c5..ee3cdd890c 100644 --- a/detox/src/utils/constructSafeFilename.js +++ b/detox/src/utils/constructSafeFilename.js @@ -1,6 +1,11 @@ +const sanitize = require("sanitize-filename"); const DetoxRuntimeError = require('../errors/DetoxRuntimeError'); const MAX_FILE_LENGTH = 255; +const sanitizeOptions = { + replacement: '_', +}; + /* Escape filename and trim it to match filesystem limits (usually, not longer than 255 chars) */ @@ -35,7 +40,7 @@ function constructSafeFilename(prefix = '', trimmable = '', suffix = '') { const trimmed = trimmable.slice(-MAX_FILE_LENGTH + nonTrimmableLength); const unsafe = prefix + trimmed + suffix; - const sanitized = unsafe.replace(/[\\\/]/g, '_'); + const sanitized = sanitize(unsafe, sanitizeOptions); return sanitized; } From b0b65fcd8348dcde871de0ffd4a009abc968ca27 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Tue, 25 Sep 2018 20:30:38 +0300 Subject: [PATCH 5/9] test: updated e2e artifact filename snapshots --- ...facts_are_not_missing.android.test.js.snap | 104 +++---- ...artifacts_are_not_missing.ios.test.js.snap | 280 +++++++++--------- 2 files changed, 192 insertions(+), 192 deletions(-) diff --git a/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.android.test.js.snap b/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.android.test.js.snap index 8997c124ea..b9bd047751 100644 --- a/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.android.test.js.snap +++ b/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.android.test.js.snap @@ -16,10 +16,10 @@ Array [ "./✓ Actions should multi tap on an element/afterEach.png", "./✓ Actions should multi tap on an element/beforeEach.png", "./✓ Actions should multi tap on an element/test.log", - "./✓ Actions should not wait for long timeout (>1.5s)", - "./✓ Actions should not wait for long timeout (>1.5s)/afterEach.png", - "./✓ Actions should not wait for long timeout (>1.5s)/beforeEach.png", - "./✓ Actions should not wait for long timeout (>1.5s)/test.log", + "./✓ Actions should not wait for long timeout (_1.5s)", + "./✓ Actions should not wait for long timeout (_1.5s)/afterEach.png", + "./✓ Actions should not wait for long timeout (_1.5s)/beforeEach.png", + "./✓ Actions should not wait for long timeout (_1.5s)/test.log", "./✓ Actions should replace text in an element", "./✓ Actions should replace text in an element/afterEach.png", "./✓ Actions should replace text in an element/beforeEach.png", @@ -48,30 +48,30 @@ Array [ "./✓ Actions should type in an element/afterEach.png", "./✓ Actions should type in an element/beforeEach.png", "./✓ Actions should type in an element/test.log", - "./✓ Animations should detect loops with final number of iterations (driver: JS)", - "./✓ Animations should detect loops with final number of iterations (driver: JS)/afterEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: JS)/beforeEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: JS)/test.log", - "./✓ Animations should detect loops with final number of iterations (driver: Native)", - "./✓ Animations should detect loops with final number of iterations (driver: Native)/afterEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: Native)/beforeEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: Native)/test.log", - "./✓ Animations should find element (driver: JS)", - "./✓ Animations should find element (driver: JS)/afterEach.png", - "./✓ Animations should find element (driver: JS)/beforeEach.png", - "./✓ Animations should find element (driver: JS)/test.log", - "./✓ Animations should find element (driver: Native)", - "./✓ Animations should find element (driver: Native)/afterEach.png", - "./✓ Animations should find element (driver: Native)/beforeEach.png", - "./✓ Animations should find element (driver: Native)/test.log", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)/afterEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)/beforeEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)/test.log", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)/afterEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)/beforeEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)/test.log", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)/afterEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)/beforeEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)/test.log", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)/afterEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)/beforeEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)/test.log", + "./✓ Animations should find element (driver_ JS)", + "./✓ Animations should find element (driver_ JS)/afterEach.png", + "./✓ Animations should find element (driver_ JS)/beforeEach.png", + "./✓ Animations should find element (driver_ JS)/test.log", + "./✓ Animations should find element (driver_ Native)", + "./✓ Animations should find element (driver_ Native)/afterEach.png", + "./✓ Animations should find element (driver_ Native)/beforeEach.png", + "./✓ Animations should find element (driver_ Native)/test.log", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)/afterEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)/beforeEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)/test.log", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)/afterEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)/beforeEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)/test.log", "./✓ Assertions should assert an element does not exist", "./✓ Assertions should assert an element does not exist/afterEach.png", "./✓ Assertions should assert an element does not exist/beforeEach.png", @@ -108,10 +108,6 @@ Array [ "./✓ Async and Callbacks should handle done() callback/afterEach.png", "./✓ Async and Callbacks should handle done() callback/beforeEach.png", "./✓ Async and Callbacks should handle done() callback/test.log", - "./✓ Device :android: device back button :android: should show popup back pressed when back button is pressed", - "./✓ Device :android: device back button :android: should show popup back pressed when back button is pressed/afterEach.png", - "./✓ Device :android: device back button :android: should show popup back pressed when back button is pressed/beforeEach.png", - "./✓ Device :android: device back button :android: should show popup back pressed when back button is pressed/test.log", "./✓ Device Orientation OrientationLandscape", "./✓ Device Orientation OrientationLandscape/afterEach.png", "./✓ Device Orientation OrientationLandscape/beforeEach.png", @@ -120,18 +116,22 @@ Array [ "./✓ Device Orientation OrientationPortrait/afterEach.png", "./✓ Device Orientation OrientationPortrait/beforeEach.png", "./✓ Device Orientation OrientationPortrait/test.log", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance/afterEach.png", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance/beforeEach.png", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance/test.log", + "./✓ Device _android_ device back button _android_ should show popup back pressed when back button is pressed", + "./✓ Device _android_ device back button _android_ should show popup back pressed when back button is pressed/afterEach.png", + "./✓ Device _android_ device back button _android_ should show popup back pressed when back button is pressed/beforeEach.png", + "./✓ Device _android_ device back button _android_ should show popup back pressed when back button is pressed/test.log", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance/afterEach.png", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance/beforeEach.png", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance/test.log", "./✓ Device relaunchApp - should tap successfully", "./✓ Device relaunchApp - should tap successfully/afterEach.png", "./✓ Device relaunchApp - should tap successfully/beforeEach.png", "./✓ Device relaunchApp - should tap successfully/test.log", - "./✓ Device relaunchApp({delete: true}) - should tap successfully", - "./✓ Device relaunchApp({delete: true}) - should tap successfully/afterEach.png", - "./✓ Device relaunchApp({delete: true}) - should tap successfully/beforeEach.png", - "./✓ Device relaunchApp({delete: true}) - should tap successfully/test.log", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully/afterEach.png", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully/beforeEach.png", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully/test.log", "./✓ Device reloadReactNative - should tap successfully", "./✓ Device reloadReactNative - should tap successfully/afterEach.png", "./✓ Device reloadReactNative - should tap successfully/beforeEach.png", @@ -188,18 +188,18 @@ Array [ "./✓ Network Synchronization setURLBlacklist() should disable synchronization for given endpoint/afterEach.png", "./✓ Network Synchronization setURLBlacklist() should disable synchronization for given endpoint/beforeEach.png", "./✓ Network Synchronization setURLBlacklist() should disable synchronization for given endpoint/test.log", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background/afterEach.png", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background/beforeEach.png", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background/test.log", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app/afterEach.png", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app/beforeEach.png", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app/test.log", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground/afterEach.png", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground/beforeEach.png", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground/test.log", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background/afterEach.png", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background/beforeEach.png", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background/test.log", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app/afterEach.png", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app/beforeEach.png", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app/test.log", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground/afterEach.png", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground/beforeEach.png", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground/test.log", "./✓ Sanity should have welcome screen", "./✓ Sanity should have welcome screen/afterEach.png", "./✓ Sanity should have welcome screen/beforeEach.png", diff --git a/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.ios.test.js.snap b/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.ios.test.js.snap index 215ba9e788..43ff20dffa 100644 --- a/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.ios.test.js.snap +++ b/detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.ios.test.js.snap @@ -4,70 +4,10 @@ exports[`artifacts health check should have all the iOS artifacts in ./artifacts Array [ "", ".", - "./✓ :ios: Crash Handling Should recover from app crash", - "./✓ :ios: Crash Handling Should recover from app crash/afterEach.png", - "./✓ :ios: Crash Handling Should recover from app crash/beforeEach.png", - "./✓ :ios: Crash Handling Should recover from app crash/test.log", - "./✓ :ios: Crash Handling Should throw error upon app crash", - "./✓ :ios: Crash Handling Should throw error upon app crash/afterEach.png", - "./✓ :ios: Crash Handling Should throw error upon app crash/beforeEach.png", - "./✓ :ios: Crash Handling Should throw error upon app crash/test.log", - "./✓ :ios: DatePicker datePicker should trigger change handler correctly", - "./✓ :ios: DatePicker datePicker should trigger change handler correctly/afterEach.png", - "./✓ :ios: DatePicker datePicker should trigger change handler correctly/beforeEach.png", - "./✓ :ios: DatePicker datePicker should trigger change handler correctly/test.log", - "./✓ :ios: Permissions Permissions denied", - "./✓ :ios: Permissions Permissions denied/afterEach.png", - "./✓ :ios: Permissions Permissions denied/beforeEach.png", - "./✓ :ios: Permissions Permissions denied/test.log", - "./✓ :ios: Permissions Permissions is granted", - "./✓ :ios: Permissions Permissions is granted/afterEach.png", - "./✓ :ios: Permissions Permissions is granted/beforeEach.png", - "./✓ :ios: Permissions Permissions is granted/test.log", - "./✓ :ios: User Activity Background searchable item", - "./✓ :ios: User Activity Background searchable item/afterEach.png", - "./✓ :ios: User Activity Background searchable item/beforeEach.png", - "./✓ :ios: User Activity Background searchable item/test.log", - "./✓ :ios: User Activity Foreground browsing web", - "./✓ :ios: User Activity Foreground browsing web/afterEach.png", - "./✓ :ios: User Activity Foreground browsing web/beforeEach.png", - "./✓ :ios: User Activity Foreground browsing web/test.log", - "./✓ :ios: User Activity Init from browsing web", - "./✓ :ios: User Activity Init from browsing web/afterEach.png", - "./✓ :ios: User Activity Init from browsing web/beforeEach.png", - "./✓ :ios: User Activity Init from browsing web/test.log", - "./✓ :ios: User Notifications Background calendar notification", - "./✓ :ios: User Notifications Background calendar notification/afterEach.png", - "./✓ :ios: User Notifications Background calendar notification/beforeEach.png", - "./✓ :ios: User Notifications Background calendar notification/test.log", - "./✓ :ios: User Notifications Background push notification", - "./✓ :ios: User Notifications Background push notification/afterEach.png", - "./✓ :ios: User Notifications Background push notification/beforeEach.png", - "./✓ :ios: User Notifications Background push notification/test.log", - "./✓ :ios: User Notifications Foreground calendar notifications", - "./✓ :ios: User Notifications Foreground calendar notifications/afterEach.png", - "./✓ :ios: User Notifications Foreground calendar notifications/beforeEach.png", - "./✓ :ios: User Notifications Foreground calendar notifications/test.log", - "./✓ :ios: User Notifications Foreground push notifications", - "./✓ :ios: User Notifications Foreground push notifications/afterEach.png", - "./✓ :ios: User Notifications Foreground push notifications/beforeEach.png", - "./✓ :ios: User Notifications Foreground push notifications/test.log", - "./✓ :ios: User Notifications Init from push notification", - "./✓ :ios: User Notifications Init from push notification/afterEach.png", - "./✓ :ios: User Notifications Init from push notification/beforeEach.png", - "./✓ :ios: User Notifications Init from push notification/test.log", - "./✓ :ios: location Location should be unavabilable", - "./✓ :ios: location Location should be unavabilable/afterEach.png", - "./✓ :ios: location Location should be unavabilable/beforeEach.png", - "./✓ :ios: location Location should be unavabilable/test.log", - "./✓ :ios: location Should receive location (20,20)", - "./✓ :ios: location Should receive location (20,20)/afterEach.png", - "./✓ :ios: location Should receive location (20,20)/beforeEach.png", - "./✓ :ios: location Should receive location (20,20)/test.log", - "./✓ Actions :ios: should long press with duration on an element", - "./✓ Actions :ios: should long press with duration on an element/afterEach.png", - "./✓ Actions :ios: should long press with duration on an element/beforeEach.png", - "./✓ Actions :ios: should long press with duration on an element/test.log", + "./✓ Actions _ios_ should long press with duration on an element", + "./✓ Actions _ios_ should long press with duration on an element/afterEach.png", + "./✓ Actions _ios_ should long press with duration on an element/beforeEach.png", + "./✓ Actions _ios_ should long press with duration on an element/test.log", "./✓ Actions should clear text in an element", "./✓ Actions should clear text in an element/afterEach.png", "./✓ Actions should clear text in an element/beforeEach.png", @@ -80,10 +20,10 @@ Array [ "./✓ Actions should multi tap on an element/afterEach.png", "./✓ Actions should multi tap on an element/beforeEach.png", "./✓ Actions should multi tap on an element/test.log", - "./✓ Actions should not wait for long timeout (>1.5s)", - "./✓ Actions should not wait for long timeout (>1.5s)/afterEach.png", - "./✓ Actions should not wait for long timeout (>1.5s)/beforeEach.png", - "./✓ Actions should not wait for long timeout (>1.5s)/test.log", + "./✓ Actions should not wait for long timeout (_1.5s)", + "./✓ Actions should not wait for long timeout (_1.5s)/afterEach.png", + "./✓ Actions should not wait for long timeout (_1.5s)/beforeEach.png", + "./✓ Actions should not wait for long timeout (_1.5s)/test.log", "./✓ Actions should replace text in an element", "./✓ Actions should replace text in an element/afterEach.png", "./✓ Actions should replace text in an element/beforeEach.png", @@ -112,42 +52,42 @@ Array [ "./✓ Actions should type in an element/afterEach.png", "./✓ Actions should type in an element/beforeEach.png", "./✓ Actions should type in an element/test.log", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: JS)", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: JS)/afterEach.png", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: JS)/beforeEach.png", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: JS)/test.log", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: Native)", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: Native)/afterEach.png", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: Native)/beforeEach.png", - "./✓ Animations :ios: should wait during delays shorter than 1.5s (driver: Native)/test.log", - "./✓ Animations should detect loops with final number of iterations (driver: JS)", - "./✓ Animations should detect loops with final number of iterations (driver: JS)/afterEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: JS)/beforeEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: JS)/test.log", - "./✓ Animations should detect loops with final number of iterations (driver: Native)", - "./✓ Animations should detect loops with final number of iterations (driver: Native)/afterEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: Native)/beforeEach.png", - "./✓ Animations should detect loops with final number of iterations (driver: Native)/test.log", - "./✓ Animations should find element (driver: JS)", - "./✓ Animations should find element (driver: JS)/afterEach.png", - "./✓ Animations should find element (driver: JS)/beforeEach.png", - "./✓ Animations should find element (driver: JS)/test.log", - "./✓ Animations should find element (driver: Native)", - "./✓ Animations should find element (driver: Native)/afterEach.png", - "./✓ Animations should find element (driver: Native)/beforeEach.png", - "./✓ Animations should find element (driver: Native)/test.log", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)/afterEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)/beforeEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: JS)/test.log", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)/afterEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)/beforeEach.png", - "./✓ Animations should not wait during delays longer than 1.5s (driver: Native)/test.log", - "./✓ Assertions :ios: should assert an element has (accessibility) value", - "./✓ Assertions :ios: should assert an element has (accessibility) value/afterEach.png", - "./✓ Assertions :ios: should assert an element has (accessibility) value/beforeEach.png", - "./✓ Assertions :ios: should assert an element has (accessibility) value/test.log", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ JS)", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ JS)/afterEach.png", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ JS)/beforeEach.png", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ JS)/test.log", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ Native)", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ Native)/afterEach.png", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ Native)/beforeEach.png", + "./✓ Animations _ios_ should wait during delays shorter than 1.5s (driver_ Native)/test.log", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)/afterEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)/beforeEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ JS)/test.log", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)/afterEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)/beforeEach.png", + "./✓ Animations should detect loops with final number of iterations (driver_ Native)/test.log", + "./✓ Animations should find element (driver_ JS)", + "./✓ Animations should find element (driver_ JS)/afterEach.png", + "./✓ Animations should find element (driver_ JS)/beforeEach.png", + "./✓ Animations should find element (driver_ JS)/test.log", + "./✓ Animations should find element (driver_ Native)", + "./✓ Animations should find element (driver_ Native)/afterEach.png", + "./✓ Animations should find element (driver_ Native)/beforeEach.png", + "./✓ Animations should find element (driver_ Native)/test.log", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)/afterEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)/beforeEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ JS)/test.log", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)/afterEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)/beforeEach.png", + "./✓ Animations should not wait during delays longer than 1.5s (driver_ Native)/test.log", + "./✓ Assertions _ios_ should assert an element has (accessibility) value", + "./✓ Assertions _ios_ should assert an element has (accessibility) value/afterEach.png", + "./✓ Assertions _ios_ should assert an element has (accessibility) value/beforeEach.png", + "./✓ Assertions _ios_ should assert an element has (accessibility) value/test.log", "./✓ Assertions should assert an element does not exist", "./✓ Assertions should assert an element does not exist/afterEach.png", "./✓ Assertions should assert an element does not exist/beforeEach.png", @@ -184,10 +124,6 @@ Array [ "./✓ Async and Callbacks should handle done() callback/afterEach.png", "./✓ Async and Callbacks should handle done() callback/beforeEach.png", "./✓ Async and Callbacks should handle done() callback/test.log", - "./✓ Device :ios: shake() should shake screen", - "./✓ Device :ios: shake() should shake screen/afterEach.png", - "./✓ Device :ios: shake() should shake screen/beforeEach.png", - "./✓ Device :ios: shake() should shake screen/test.log", "./✓ Device Orientation OrientationLandscape", "./✓ Device Orientation OrientationLandscape/afterEach.png", "./✓ Device Orientation OrientationLandscape/beforeEach.png", @@ -196,18 +132,22 @@ Array [ "./✓ Device Orientation OrientationPortrait/afterEach.png", "./✓ Device Orientation OrientationPortrait/beforeEach.png", "./✓ Device Orientation OrientationPortrait/test.log", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance/afterEach.png", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance/beforeEach.png", - "./✓ Device launchApp({newInstance: true}) + sendToHome() + launchApp() - should bring up previous instance/test.log", + "./✓ Device _ios_ shake() should shake screen", + "./✓ Device _ios_ shake() should shake screen/afterEach.png", + "./✓ Device _ios_ shake() should shake screen/beforeEach.png", + "./✓ Device _ios_ shake() should shake screen/test.log", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance/afterEach.png", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance/beforeEach.png", + "./✓ Device launchApp({newInstance_ true}) + sendToHome() + launchApp() - should bring up previous instance/test.log", "./✓ Device relaunchApp - should tap successfully", "./✓ Device relaunchApp - should tap successfully/afterEach.png", "./✓ Device relaunchApp - should tap successfully/beforeEach.png", "./✓ Device relaunchApp - should tap successfully/test.log", - "./✓ Device relaunchApp({delete: true}) - should tap successfully", - "./✓ Device relaunchApp({delete: true}) - should tap successfully/afterEach.png", - "./✓ Device relaunchApp({delete: true}) - should tap successfully/beforeEach.png", - "./✓ Device relaunchApp({delete: true}) - should tap successfully/test.log", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully/afterEach.png", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully/beforeEach.png", + "./✓ Device relaunchApp({delete_ true}) - should tap successfully/test.log", "./✓ Device reloadReactNative - should tap successfully", "./✓ Device reloadReactNative - should tap successfully/afterEach.png", "./✓ Device reloadReactNative - should tap successfully/beforeEach.png", @@ -220,10 +160,10 @@ Array [ "./✓ Device uninstall() + install() + relaunch() - should tap successfully/afterEach.png", "./✓ Device uninstall() + install() + relaunch() - should tap successfully/beforeEach.png", "./✓ Device uninstall() + install() + relaunch() - should tap successfully/test.log", - "./✓ Matchers :ios: should match elements by accesibility trait", - "./✓ Matchers :ios: should match elements by accesibility trait/afterEach.png", - "./✓ Matchers :ios: should match elements by accesibility trait/beforeEach.png", - "./✓ Matchers :ios: should match elements by accesibility trait/test.log", + "./✓ Matchers _ios_ should match elements by accesibility trait", + "./✓ Matchers _ios_ should match elements by accesibility trait/afterEach.png", + "./✓ Matchers _ios_ should match elements by accesibility trait/beforeEach.png", + "./✓ Matchers _ios_ should match elements by accesibility trait/test.log", "./✓ Matchers should match elements by (accesibility) id", "./✓ Matchers should match elements by (accesibility) id/afterEach.png", "./✓ Matchers should match elements by (accesibility) id/beforeEach.png", @@ -268,18 +208,18 @@ Array [ "./✓ Network Synchronization setURLBlacklist() should disable synchronization for given endpoint/afterEach.png", "./✓ Network Synchronization setURLBlacklist() should disable synchronization for given endpoint/beforeEach.png", "./✓ Network Synchronization setURLBlacklist() should disable synchronization for given endpoint/test.log", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background/afterEach.png", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background/beforeEach.png", - "./✓ Open URLs device.launchApp({url: url}) should trigger open url handling in app when app is in background/test.log", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app/afterEach.png", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app/beforeEach.png", - "./✓ Open URLs device.launchApp({{newInstance: true, url: url}) should launch app and trigger handling open url handling in app/test.log", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground/afterEach.png", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground/beforeEach.png", - "./✓ Open URLs device.openURL({url: url}) should trigger open url handling in app when app is in foreground/test.log", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background/afterEach.png", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background/beforeEach.png", + "./✓ Open URLs device.launchApp({url_ url}) should trigger open url handling in app when app is in background/test.log", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app/afterEach.png", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app/beforeEach.png", + "./✓ Open URLs device.launchApp({{newInstance_ true, url_ url}) should launch app and trigger handling open url handling in app/test.log", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground/afterEach.png", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground/beforeEach.png", + "./✓ Open URLs device.openURL({url_ url}) should trigger open url handling in app when app is in foreground/test.log", "./✓ Sanity should have welcome screen", "./✓ Sanity should have welcome screen/afterEach.png", "./✓ Sanity should have welcome screen/beforeEach.png", @@ -292,10 +232,10 @@ Array [ "./✓ Sanity should show world screen after tap/afterEach.png", "./✓ Sanity should show world screen after tap/beforeEach.png", "./✓ Sanity should show world screen after tap/test.log", - "./✓ StressRoot :ios: should switch root view controller from RN to RN", - "./✓ StressRoot :ios: should switch root view controller from RN to RN/afterEach.png", - "./✓ StressRoot :ios: should switch root view controller from RN to RN/beforeEach.png", - "./✓ StressRoot :ios: should switch root view controller from RN to RN/test.log", + "./✓ StressRoot _ios_ should switch root view controller from RN to RN", + "./✓ StressRoot _ios_ should switch root view controller from RN to RN/afterEach.png", + "./✓ StressRoot _ios_ should switch root view controller from RN to RN/beforeEach.png", + "./✓ StressRoot _ios_ should switch root view controller from RN to RN/test.log", "./✓ StressRoot should switch root view controller from RN to native", "./✓ StressRoot should switch root view controller from RN to native/afterEach.png", "./✓ StressRoot should switch root view controller from RN to native/beforeEach.png", @@ -320,10 +260,10 @@ Array [ "./✓ StressTests should handle tap during busy bridge (two way)/afterEach.png", "./✓ StressTests should handle tap during busy bridge (two way)/beforeEach.png", "./✓ StressTests should handle tap during busy bridge (two way)/test.log", - "./✓ StressTimeouts :ios: should handle a short timeout", - "./✓ StressTimeouts :ios: should handle a short timeout/afterEach.png", - "./✓ StressTimeouts :ios: should handle a short timeout/beforeEach.png", - "./✓ StressTimeouts :ios: should handle a short timeout/test.log", + "./✓ StressTimeouts _ios_ should handle a short timeout", + "./✓ StressTimeouts _ios_ should handle a short timeout/afterEach.png", + "./✓ StressTimeouts _ios_ should handle a short timeout/beforeEach.png", + "./✓ StressTimeouts _ios_ should handle a short timeout/test.log", "./✓ StressTimeouts should handle setImmediate", "./✓ StressTimeouts should handle setImmediate/afterEach.png", "./✓ StressTimeouts should handle setImmediate/beforeEach.png", @@ -356,6 +296,66 @@ Array [ "./✓ WaitFor should wait until an element is removed/afterEach.png", "./✓ WaitFor should wait until an element is removed/beforeEach.png", "./✓ WaitFor should wait until an element is removed/test.log", + "./✓ _ios_ Crash Handling Should recover from app crash", + "./✓ _ios_ Crash Handling Should recover from app crash/afterEach.png", + "./✓ _ios_ Crash Handling Should recover from app crash/beforeEach.png", + "./✓ _ios_ Crash Handling Should recover from app crash/test.log", + "./✓ _ios_ Crash Handling Should throw error upon app crash", + "./✓ _ios_ Crash Handling Should throw error upon app crash/afterEach.png", + "./✓ _ios_ Crash Handling Should throw error upon app crash/beforeEach.png", + "./✓ _ios_ Crash Handling Should throw error upon app crash/test.log", + "./✓ _ios_ DatePicker datePicker should trigger change handler correctly", + "./✓ _ios_ DatePicker datePicker should trigger change handler correctly/afterEach.png", + "./✓ _ios_ DatePicker datePicker should trigger change handler correctly/beforeEach.png", + "./✓ _ios_ DatePicker datePicker should trigger change handler correctly/test.log", + "./✓ _ios_ Permissions Permissions denied", + "./✓ _ios_ Permissions Permissions denied/afterEach.png", + "./✓ _ios_ Permissions Permissions denied/beforeEach.png", + "./✓ _ios_ Permissions Permissions denied/test.log", + "./✓ _ios_ Permissions Permissions is granted", + "./✓ _ios_ Permissions Permissions is granted/afterEach.png", + "./✓ _ios_ Permissions Permissions is granted/beforeEach.png", + "./✓ _ios_ Permissions Permissions is granted/test.log", + "./✓ _ios_ User Activity Background searchable item", + "./✓ _ios_ User Activity Background searchable item/afterEach.png", + "./✓ _ios_ User Activity Background searchable item/beforeEach.png", + "./✓ _ios_ User Activity Background searchable item/test.log", + "./✓ _ios_ User Activity Foreground browsing web", + "./✓ _ios_ User Activity Foreground browsing web/afterEach.png", + "./✓ _ios_ User Activity Foreground browsing web/beforeEach.png", + "./✓ _ios_ User Activity Foreground browsing web/test.log", + "./✓ _ios_ User Activity Init from browsing web", + "./✓ _ios_ User Activity Init from browsing web/afterEach.png", + "./✓ _ios_ User Activity Init from browsing web/beforeEach.png", + "./✓ _ios_ User Activity Init from browsing web/test.log", + "./✓ _ios_ User Notifications Background calendar notification", + "./✓ _ios_ User Notifications Background calendar notification/afterEach.png", + "./✓ _ios_ User Notifications Background calendar notification/beforeEach.png", + "./✓ _ios_ User Notifications Background calendar notification/test.log", + "./✓ _ios_ User Notifications Background push notification", + "./✓ _ios_ User Notifications Background push notification/afterEach.png", + "./✓ _ios_ User Notifications Background push notification/beforeEach.png", + "./✓ _ios_ User Notifications Background push notification/test.log", + "./✓ _ios_ User Notifications Foreground calendar notifications", + "./✓ _ios_ User Notifications Foreground calendar notifications/afterEach.png", + "./✓ _ios_ User Notifications Foreground calendar notifications/beforeEach.png", + "./✓ _ios_ User Notifications Foreground calendar notifications/test.log", + "./✓ _ios_ User Notifications Foreground push notifications", + "./✓ _ios_ User Notifications Foreground push notifications/afterEach.png", + "./✓ _ios_ User Notifications Foreground push notifications/beforeEach.png", + "./✓ _ios_ User Notifications Foreground push notifications/test.log", + "./✓ _ios_ User Notifications Init from push notification", + "./✓ _ios_ User Notifications Init from push notification/afterEach.png", + "./✓ _ios_ User Notifications Init from push notification/beforeEach.png", + "./✓ _ios_ User Notifications Init from push notification/test.log", + "./✓ _ios_ location Location should be unavabilable", + "./✓ _ios_ location Location should be unavabilable/afterEach.png", + "./✓ _ios_ location Location should be unavabilable/beforeEach.png", + "./✓ _ios_ location Location should be unavabilable/test.log", + "./✓ _ios_ location Should receive location (20,20)", + "./✓ _ios_ location Should receive location (20,20)/afterEach.png", + "./✓ _ios_ location Should receive location (20,20)/beforeEach.png", + "./✓ _ios_ location Should receive location (20,20)/test.log", "1.detox.json.log", "1.detox.log", "1.startup.log", From 82dc04a58d2dab2a1391ed14dd46b34e80f54c23 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Tue, 25 Sep 2018 20:34:16 +0300 Subject: [PATCH 6/9] fix: a stricter way to check if uninstall is required --- detox/src/devices/android/ADB.js | 8 ++++++++ detox/src/devices/drivers/AndroidDriver.js | 11 ++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index 9c1543d1db..dacd013b7b 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -93,6 +93,14 @@ class ADB { return this.shell(deviceId, `date +"%m-%d %T.000"`); } + async isPackageInstalled(deviceId, packageId) { + const output = await this.shell(deviceId, `pm list packages ${packageId}`); + const packageRegexp = new RegExp(`^package:${escape.inQuotedRegexp(packageId)}$`, 'm'); + const isInstalled = packageRegexp.test(output); + + return isInstalled; + } + async install(deviceId, apkPath) { const apiLvl = await this.apiLevel(deviceId); diff --git a/detox/src/devices/drivers/AndroidDriver.js b/detox/src/devices/drivers/AndroidDriver.js index 9a7fd05318..32af22b7d7 100644 --- a/detox/src/devices/drivers/AndroidDriver.js +++ b/detox/src/devices/drivers/AndroidDriver.js @@ -73,16 +73,13 @@ class AndroidDriver extends DeviceDriverBase { } async uninstallApp(deviceId, bundleId) { - try { + if (await this.adb.isPackageInstalled(deviceId, bundleId)) { await this.adb.uninstall(deviceId, bundleId); - } catch (ex) { - //this is fine } - try { - await this.adb.uninstall(deviceId, `${bundleId}.test`); - } catch (ex) { - //this is fine + const testBundle = `${bundleId}.test`; + if (await this.adb.isPackageInstalled(deviceId, testBundle)) { + await this.adb.uninstall(deviceId, testBundle); } } From 73079a2bfb5e0d773377a70d4b08699ba9d843aa Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Tue, 25 Sep 2018 20:40:47 +0300 Subject: [PATCH 7/9] fix: querying api level for attached Android devices --- detox/src/devices/drivers/AttachedAndroidDriver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/detox/src/devices/drivers/AttachedAndroidDriver.js b/detox/src/devices/drivers/AttachedAndroidDriver.js index f69485d96a..60389166be 100644 --- a/detox/src/devices/drivers/AttachedAndroidDriver.js +++ b/detox/src/devices/drivers/AttachedAndroidDriver.js @@ -11,6 +11,7 @@ class AttachedAndroidDriver extends AndroidDriver { async acquireFreeDevice(name) { const deviceId = await this.findDeviceId({name: name}); + await this.adb.apiLevel(name); await this.adb.unlockScreen(deviceId); return deviceId; } From cd822862eff075d5e9404fa687a4798aa0ee4e91 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Tue, 25 Sep 2018 22:39:39 +0300 Subject: [PATCH 8/9] Delete regexEscape.js --- detox/src/utils/regexEscape.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 detox/src/utils/regexEscape.js diff --git a/detox/src/utils/regexEscape.js b/detox/src/utils/regexEscape.js deleted file mode 100644 index e69de29bb2..0000000000 From 4237b66e921f83f8be5ef6ad38d9fd872b160730 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Wed, 26 Sep 2018 06:31:19 +0300 Subject: [PATCH 9/9] fix: typo in ADB.install error handler --- detox/src/devices/android/ADB.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index dacd013b7b..c18fbc2d73 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -114,7 +114,7 @@ class ADB { const [failure] = (childProcess.stdout || '').match(/^Failure \[.*\]$/m) || []; if (failure) { throw new DetoxRuntimeError({ - message: `Failed to install app on ${this.devices()}: ${apkPath}`, + message: `Failed to install app on ${deviceId}: ${apkPath}`, debugInfo: failure, }); }