-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Internal changes to artifacts subsystem in Detox and bugfixes #848
Conversation
Let's consider moving all events to DeviceDriverBase, instead of spreading them in Device and multiple deviceDrivers |
ed45740
to
dbd127c
Compare
@rotemmiz , it is not really easy to move things inside DeviceDriver itself. I think it needs a bigger refactor together with a Device class. But, at least I managed to do emitting only on drivers level - base class creates and possess a protected emitter instance which derived classes can use. It is a compromise, but I don't think we can do better without a heavier rewriting right now. Currently, some unit tests are not passing, but the code is almost ready to the merge from my point of view, so I'd ask you to take a look again. I updated the description of the PR, btw. |
Tests are fixed, btw. |
Also resolves: #856 . |
if (this.enabled && event.coldBoot) { | ||
await this.appleSimUtils.takeScreenshot(event.deviceId, '/dev/null').catch(() => { | ||
log.debug({}, ` | ||
NOTE: For an unknown reason, when you boot an iOS Simulator in a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try to make the note less personal, and more like documentation, something like:
NOTE: For an unknown reason, when booting an iOS Simulator in a hidden window mode (or on CI), the first screenshot always fails. Detox tries to overcome this by taking a dummy screenshot to ensure that future ones are going to work fine. This dummy screenshot is not being saved to filesystem and the error is suppressed (except for debugging log levels).
} | ||
|
||
log.error({ event: 'SPAWN_FAIL', error: true, err }, err.message); | ||
log.error({ event: 'SPAWN_FAIL', stderr: true }, childProcessOutput); | ||
throw err; | ||
}).then(() => { | ||
}).then((isAlreadyRunning) => { | ||
const coldBoot = isAlreadyRunning !== true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't const coldBoot = !isAlreadyRunning;
enough ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or even better, return true only if the device was booted (aka, cold boot), and return false if the device is already booted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (childProcessOutput.includes(`There's another emulator instance running with the current AVD`)) {
return false;
}
if (this.instrumentationProcess) { | ||
await interruptProcess(this.instrumentationProcess); | ||
this.instrumentationProcess = null; | ||
} | ||
} | ||
|
||
async cleanup(deviceId, bundleId) { | ||
await this.terminateInstrumentation(); | ||
await super.cleanup(deviceId, bundleId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't await super.cleanup(deviceId, bundleId);
be the last line in this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
@@ -129,7 +144,7 @@ class DeviceDriverBase { | |||
} | |||
|
|||
async cleanup(deviceId, bundleId) { | |||
return await Promise.resolve(''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we agreed on creating base wrappers in DeviceDriverBase that will emit all events, something like:
DeviceDriverBase.js
async install(deviceId, bundleId) {
await this.emitter.emit('beforeInstall', { anObject });
await this.onInstall();
await this.emitter.emit('afterInstall', { anObject });
}
*Driver.js
async onInstall() {
//actual code
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I checked, but I can't implement it so easily because of the coldBoot complication.
I clearly see across the project that .boot()
methods in --DeviceDriver classes are obligated to return string
which semantically means a real device id (a concept which is contrary to device name, e.g.: Nexus_5X_API_26 -> emulator-5556
or iPhone 6s -> 7483CE6E-01CF-4B95-82DA-11E22AA94729
.
That would mean that if I start returning a complex object {deviceId, coldBoot}
from a child driver, then signatures between DriverBase and ChildDriver methods will start to diverge, and I don't like this idea very much.
Also, we've got around 10-14 methods like that on DeviceDriverBase.js. It is going to pollute the class with x2 signatures count. Also, if you check the code of Device and DeviceDriverBase, you will see that the good half of methods are very dumb and repetitive there:
async sendToHome() {
await this.deviceDriver.sendToHome(this._deviceId);
async shake() {
await this.deviceDriver.shake(this._deviceId);
async terminateApp(bundleId) {
const _bundleId = bundleId || this._bundleId;
await this.deviceDriver.terminate(this._deviceId, _bundleId);
async installApp(binaryPath) {
const _binaryPath = binaryPath || this._binaryPath;
await this.deviceDriver.installApp(this._deviceId, _binaryPath);
async uninstallApp(bundleId) {
const _bundleId = bundleId || this._bundleId;
await this.deviceDriver.uninstallApp(this._deviceId, _bundleId);
async reloadReactNative() { await this.deviceDriver.reloadReactNative();
I think that architecture in all these classes is quite ad-hocish. and can (and is worth to) be rethought and improved, but since the emitter physically lives inside DeviceDriverBase class right, I think we can postpone a heavier rewrite. I'm afraid if I really start touch it, I won't be lucky to finish it till the next week.
jest.mock('../utils/environment'); | ||
environment = require('../utils/environment'); | ||
originalHome = process.env.HOME; | ||
process.env.HOME = '/Users/detox'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do we need to control process.env.HOME
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm returning it back, normally 😄
And that's to simplify tests below. I do them with snapshots. There's too much effort with manual editing the tests code after generic changes to exec calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I don't "freeze" somehow $HOME between CI and my computer, snapshots will break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably, we could do that via environment
module (get $HOME variable). Shall I rewrite this way?
7eac951
to
a9fd29f
Compare
Marked as WIP, since there is an issue with the suggested SimulatorLogPlugin implementation on high loads. |
Removed [WIP] label since the iOS simulator log plugin was stabilized. |
Resolves #841, #856.
device.on
anddevice.off
methods.launch()
tolaunchApp
in *DeviceDriver classes for more consistency.beforeResetContentAndSettings
andresetContentAndSettings
in favor ofshutdownDevice
andbootDevice
, because that are the actual events that we are interested in.true/false
(true - we had a cold boot, false - we did not have to boot, rather reuse a ready-to-go device). This is used ascoldBoot
flag in event objects forbootDevice
event.silent: boolean
flag toexecWithRetriesAndLogs
method, so that if we bump into an error - we can output the error to thelog.debug
instead oflog.error
./dev/null
, the silent flag is true (sort of convention). Also, we print a notice about the fact it is an expected error tolog.debug
.ADB.acquireFreeDevice
into two smaller functions.artifacts/log/ios/SimulatorLogPlugin.js
.