Skip to content
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

fix: uninstall installed uia2 servers if they were greater than on-going session #796

Merged
merged 14 commits into from
Jun 22, 2024
56 changes: 49 additions & 7 deletions lib/uiautomator2.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,51 @@ class UiAutomator2Server {
return resultInfo;
}

/**
* @typedef {Object} PackageInfo
* @property {string} installState
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for the installState we can set the type to typeof this.adb.APP_INSTALL_STATE and thus avoid uncessary conversions to string

* @property {string} appPath
* @property {string} appId
*/

/**
* Checks if server components must be installed from the device under test
* in scope of the current driver session.
*
* For example, if one of servers on the device under test was newer than servers current UIA2 driver wants to
* use for the session, the UIA2 driver should uninstall the installed ones in order to avoid
* version mismatch between the UIA2 drier and servers on the device under test.
* Also, if the device under test has missing servers, current UIA2 driver should uninstall all
* servers once in order to install proper servers freshly.
*
* @param {PackageInfo[]} packagesInfo
* @returns {boolean} true if any of components is already installed and the other is not installed
* or the installed one has a newer version.
*/
shouldUninstallServerPackages(packagesInfo = []) {
const isAnyComponentInstalled = packagesInfo.some(
({installState}) => installState !== this.adb.APP_INSTALL_STATE.NOT_INSTALLED);
const isAnyComponentNotInstalledOrNewer = packagesInfo.some(({installState}) => [
/** @type {string} */ (this.adb.APP_INSTALL_STATE.NOT_INSTALLED),
/** @type {string} */ (this.adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED),
].includes(installState));
return isAnyComponentInstalled && isAnyComponentNotInstalledOrNewer;
}

/**
* Checks if server components should be installed on the device under test in scope of the current driver session.
*
* @param {PackageInfo[]} packagesInfo
* @returns {boolean} true if any of components is not installed or older than currently installed in order to
* install or upgrade the servers on the device under test.
*/
shouldInstallServerPackages(packagesInfo = []) {
return packagesInfo.some(({installState}) => [
/** @type {string} */ (this.adb.APP_INSTALL_STATE.NOT_INSTALLED),
/** @type {string} */ (this.adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED),
].includes(installState));
}

/**
* Installs the apks on to the device or emulator.
*
Expand All @@ -116,15 +161,12 @@ class UiAutomator2Server {
);

this.log.debug(`Server packages status: ${JSON.stringify(packagesInfo)}`);
// Enforce server packages reinstall if any of the packages is not installed, while the other is
const shouldUninstallServerPackages = (packagesInfo.some(({installState}) => installState === this.adb.APP_INSTALL_STATE.NOT_INSTALLED)
&& !packagesInfo.every(({installState}) => installState === this.adb.APP_INSTALL_STATE.NOT_INSTALLED));
// To ensure if the UIA2 driver should uninstall UI2 server packages from the device
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is now not really needed since we have the method itself properly documented

// to use proper UIA2 server versions current UIA2 server wants to use.
const shouldUninstallServerPackages = this.shouldUninstallServerPackages(packagesInfo);
// Install must always follow uninstall. Also, perform the install if
// any of server packages is not installed or is outdated
const shouldInstallServerPackages = shouldUninstallServerPackages || packagesInfo.some(({installState}) => [
this.adb.APP_INSTALL_STATE.NOT_INSTALLED,
this.adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
].includes(installState));
const shouldInstallServerPackages = shouldUninstallServerPackages || this.shouldInstallServerPackages(packagesInfo);
this.log.info(`Server packages are ${shouldInstallServerPackages ? '' : 'not '}going to be (re)installed`);
if (shouldInstallServerPackages && shouldUninstallServerPackages) {
this.log.info('Full packages reinstall is going to be performed');
Expand Down
172 changes: 161 additions & 11 deletions test/unit/uiautomator2-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,169 @@ chai.should();
chai.use(chaiAsPromised);

describe('UiAutomator2', function () {
let uiautomator2;
const adb = new ADB();
const serverApk = {
'appPath': 'path/to/appium-uiautomator2-server.apk',
'appId': 'io.appium.uiautomator2.server'
};
const serverTestApk = {
'appPath': 'path/to/appium-uiautomator2-server-test.apk',
'appId': 'io.appium.uiautomator2.server.test'
};
const defaultUIA2ServerOptions = {
tmpDir: 'tmp',
systemPort: 4724,
host: 'localhost',
devicePort: 6790,
disableWindowAnimation: false
};

describe('shouldUninstallServerPackages', function () {
beforeEach(function () {
uiautomator2 = new UiAutomator2Server(log, {
adb, ...defaultUIA2ServerOptions
});
});
it('with newer servers are installed', function () {
uiautomator2.shouldUninstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.true;
}),
it('with newer server is installed but the other could be old one', function () {
// Then, enforce to uninstall all apks
uiautomator2.shouldUninstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.true;
}),
it('with newer server is installed', function () {
uiautomator2.shouldUninstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.SAME_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.SAME_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.false;
}),
it('with older servers are installed', function () {
// then, installing newer serves are sufficient.
uiautomator2.shouldUninstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.false;
}),
it('with no server are installed', function () {
uiautomator2.shouldUninstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.NOT_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.NOT_INSTALLED,
...serverTestApk
}
]).should.be.false;
});
});

describe('shouldInstallServerPackages', function () {
beforeEach(function () {
uiautomator2 = new UiAutomator2Server(log, {
adb, ...defaultUIA2ServerOptions
});
});
it('with newer servers are installed', function () {
uiautomator2.shouldInstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED,
...serverTestApk
}
// since installation may fail
]).should.be.false;
}),
it('with newer server is installed but the other could be old one', function () {
// Then, enforce to uninstall all apks
uiautomator2.shouldInstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.true;
}),
it('with newer server is installed', function () {
uiautomator2.shouldInstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.SAME_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.SAME_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.false;
}),
it('with older servers are installed', function () {
// then, installing newer serves are sufficient.
uiautomator2.shouldInstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED,
...serverTestApk
}
]).should.be.true;
}),
it('with no server are installed', function () {
uiautomator2.shouldInstallServerPackages([
{
'installState': adb.APP_INSTALL_STATE.NOT_INSTALLED,
...serverApk
},
{
'installState': adb.APP_INSTALL_STATE.NOT_INSTALLED,
...serverTestApk
}
]).should.be.true;
});
});

describe('installServerApk', withMocks({adb, helpers}, (mocks) => {
let uiautomator2;
beforeEach(function () {
uiautomator2 = new UiAutomator2Server(log, {
adb,
tmpDir: 'tmp',
systemPort: 4724,
host: 'localhost',
devicePort: 6790,
disableWindowAnimation: false,
disableSuppressAccessibilityService: false
adb, ...defaultUIA2ServerOptions
});
});
afterEach(function () {
Expand All @@ -35,7 +186,7 @@ describe('UiAutomator2', function () {

// SERVER_PACKAGE_ID
mocks.adb.expects('getApplicationInstallState').once()
.returns(adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED);
.returns(adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED);

// SERVER_PACKAGE_ID and SERVER_TEST_PACKAGE_ID
mocks.adb.expects('checkApkCert').twice().returns(true);
Expand All @@ -58,7 +209,7 @@ describe('UiAutomator2', function () {

// SERVER_PACKAGE_ID
mocks.adb.expects('getApplicationInstallState').once()
.returns(adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED);
.returns(adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED);

// SERVER_PACKAGE_ID and SERVER_TEST_PACKAGE_ID
mocks.adb.expects('checkApkCert').twice().returns(true);
Expand Down Expand Up @@ -147,7 +298,6 @@ describe('UiAutomator2', function () {
await uiautomator2.installServerApk();
});


it('a server is installed but server.test is not', async function () {
mocks.helpers.expects('isWriteable').never();

Expand Down
Loading