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

feat: add autoFillPasswords capability #1972

Merged
merged 6 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Capability | Description
|`appium:keychainsExcludePatterns`|This capability accepts comma-separated path patterns, which are going to be excluded from keychains restore while full reset is being performed on Simulator. It might be useful if you want to exclude only particular keychain types from being restored, like the applications keychain. This feature has no effect on real devices.|e.g. `*keychain*.db*` to exclude applications keychain from being restored|
|`appium:reduceMotion`| It allows to turn on/off reduce motion accessibility preference. Setting reduceMotion `on` helps to reduce flakiness during tests. Only on simulators | e.g `true` |
|`appium:reduceTransparency`| It allows you to turn on/off reduce transparency accessibility preference. Setting reduceTransparency `on` helps to reduce screenshot image distortion during tests. Only on simulators | e.g `true` |
|`appium:autoFillPasswords`| It allows you to turn on/off autofill passwords function when text field is foccused. Works only with iOS16.4+ simulators | e.g `true` |
|`appium:permissions`| Allows to set permissions for the specified application bundle on Simulator only. The capability value is expected to be a valid JSON string with `{"<bundleId1>": {"<serviceName1>": "<serviceStatus1>", ...}, ...}` format. Since Xcode SDK 11.4 Apple provides native APIs to interact with application settings. Check the output of `xcrun simctl privacy booted` command to get the list of available permission names. Use `yes`, `no` and `unset` as values in order to `grant`, `revoke` or `reset` the corresponding permission. Below Xcode SDK 11.4 it is required that `applesimutils` package is installed and available in PATH. The list of available service names and statuses can be found at https://github.com/wix/AppleSimulatorUtils. | e. g. `{"com.apple.mobilecal": {"calendar": "YES"}}` |
|`appium:iosSimulatorLogsPredicate`|Set the `--predicate` flag in the ios simulator logs|e.g.: `'process != "locationd" AND process != "DTServiceHub"' AND process != "mobileassetd"`|
|`appium:simulatorPasteboardAutomaticSync`| Handle the `-PasteboardAutomaticSync` flag when simulator process launches. It could improve launching simulator performance not to sync pasteboard with the system when this value is `off`. `on` forces the flag enabled. `system` does not provide the flag to the launching command. `on`, `off`, or `system` is available. They are case insensitive. Defaults to `off` | e.g. `system` |
Expand Down
3 changes: 3 additions & 0 deletions lib/desired-caps.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ const desiredCapConstraints = /** @type {const} */ ({
reduceTransparency: {
isBoolean: true,
},
autoFillPasswords: {
isBoolean: true,
},
mjpegScreenshotUrl: {
isString: true,
},
Expand Down
103 changes: 51 additions & 52 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,58 +585,7 @@ class XCUITestDriver extends BaseDriver {
this.log.info(`Setting up ${this.isRealDevice() ? 'real device' : 'simulator'}`);

if (this.isSimulator()) {
if (this.opts.shutdownOtherSimulators) {
this.ensureFeatureEnabled(SHUTDOWN_OTHER_FEAT_NAME);
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await shutdownOtherSimulators(this.opts.device);
}

await this.startSim();

if (this.opts.customSSLCert) {
// Simulator must be booted in order to call this helper
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await this.opts.device.addCertificate(this.opts.customSSLCert);
this.logEvent('customCertInstalled');
}

// @ts-expect-error - do not assign arbitrary properties to `this.opts`
if (await setSafariPrefs(this.opts.device, this.opts)) {
this.log.debug('Safari preferences have been updated');
}

// @ts-expect-error - do not assign arbitrary properties to `this.opts`
if (await setLocalizationPrefs(this.opts.device, this.opts)) {
this.log.debug('Localization preferences have been updated');
}

if (_.isBoolean(this.opts.reduceMotion)) {
this.log.info(`Setting reduceMotion to ${this.opts.reduceMotion}`);
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await this.opts.device.setReduceMotion(this.opts.reduceMotion);
}

if (_.isBoolean(this.opts.reduceTransparency)) {
this.log.info(`Setting reduceTransparency to ${this.opts.reduceTransparency}`);
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await this.opts.device.setReduceTransparency(this.opts.reduceTransparency);
}

if (this.opts.launchWithIDB) {
try {
const idb = new IDB({udid});
await idb.connect();
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
this.opts.device.idb = idb;
} catch (e) {
this.log.debug(e.stack);
this.log.warn(
`idb will not be used for Simulator interaction. Original error: ${e.message}`,
);
}
}

this.logEvent('simStarted');
await this.initSimulator();
if (!isLogCaptureStarted) {
// Retry log capture if Simulator was not running before
await startLogCapture();
Expand Down Expand Up @@ -711,6 +660,56 @@ class XCUITestDriver extends BaseDriver {
}
}

/**
* Start the simulator and initialize based on capabilities
*/
async initSimulator() {
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
const device = this.opts.device;

if (this.opts.shutdownOtherSimulators) {
this.ensureFeatureEnabled(SHUTDOWN_OTHER_FEAT_NAME);
await shutdownOtherSimulators(device);
}

await this.startSim();

if (this.opts.customSSLCert) {
// Simulator must be booted in order to call this helper
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await this.opts.device.addCertificate(this.opts.customSSLCert);
mykola-mokhnach marked this conversation as resolved.
Show resolved Hide resolved
this.logEvent('customCertInstalled');
}

if (await setSafariPrefs(device, this.opts)) {
this.log.debug('Safari preferences have been updated');
}

if (await setLocalizationPrefs(device, this.opts)) {
this.log.debug('Localization preferences have been updated');
}

const promises = ['reduceMotion', 'reduceTransparency', 'autoFillPasswords']
.filter((optName) => _.isBoolean(this.opts[optName]))
.map((optName) => device[`set${_.upperFirst(optName)}`](this.opts[optName]));
await B.all(promises);

if (this.opts.launchWithIDB) {
try {
const idb = new IDB({udid: this.opts.udid});
await idb.connect();
device.idb = idb;
} catch (e) {
this.log.debug(e.stack);
this.log.warn(
`idb will not be used for Simulator interaction. Original error: ${e.message}`,
);
}
}

this.logEvent('simStarted');
}

/**
* Start WebDriverAgentRunner
* @param {string} sessionId - The id of the target session to launch WDA with.
Expand Down
60 changes: 60 additions & 0 deletions test/functional/device/passwords-e2e-specs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {MOCHA_TIMEOUT, initSession, deleteSession, hasDefaultPrebuiltWDA} from '../helpers/session';
import {UICATALOG_CAPS, amendCapabilities, extractCapabilityValue} from '../desired';
import {util} from 'appium/support';

chai.should();
chai.use(chaiAsPromised);

describe('Passwords', function () {
this.timeout(MOCHA_TIMEOUT);

let driver, caps;

beforeEach(function () {
caps = amendCapabilities(UICATALOG_CAPS, {
'appium:usePrebuiltWDA': hasDefaultPrebuiltWDA(),
});

if (util.compareVersions(extractCapabilityValue(caps, 'appium:platformVersion'), '<', '16.4')) {
return this.skip();
}
});

afterEach(async function () {
// try to get rid of the driver, so if a test fails the rest of the
// tests aren't compromised
try {
await deleteSession();
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
});

describe('AutoFillPasswords', function () {
async function isPasswordsMenuShown(driver) {
const el = await driver.$('~Text Fields');
await el.click();

const textField = await driver.$('XCUIElementTypeSecureTextField');
await textField.waitForExist({timeout: 500});
await textField.click();

const passwordsMenu = await driver.$('~Passwords');
KazuCocoa marked this conversation as resolved.
Show resolved Hide resolved
return await passwordsMenu.isExisting();
}

it('should enable password autofill menu in the keyboard', async function () {
caps = amendCapabilities(caps, {'appium:autoFillPasswords': true});
driver = await initSession(caps);
await isPasswordsMenuShown(driver).should.eventually.eql(true);
});
it('should disable password autofill menu in the keyboard', async function () {
caps = amendCapabilities(caps, {'appium:autoFillPasswords': false});
driver = await initSession(caps);
await isPasswordsMenuShown(driver).should.eventually.eql(false);
});
});
});
29 changes: 29 additions & 0 deletions test/unit/driver-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ describe('XCUITestDriver', function () {
return '/path/to/uds.socket';
},
setReduceTransparency: _.noop,
setAutoFillPasswords: _.noop,
};
realDevice = null;
sandbox
Expand Down Expand Up @@ -201,6 +202,34 @@ describe('XCUITestDriver', function () {
);
spy.notCalled.should.be.true;
});

it('should call setAutoFillPasswords for a simulator', async function () {
this.timeout(MOCHA_LONG_TIMEOUT);
realDevice = false;
const spy = sandbox.stub(device, 'setAutoFillPasswords').resolves({device, realDevice});
await driver.createSession(
null,
null,
_.merge({}, caps, {
alwaysMatch: {'appium:autoFillPasswords': true},
}),
);
spy.calledOnce.should.be.true;
spy.firstCall.args[0].should.eql(true);
});
it('should not call setAutoFillPasswords for a real device', async function () {
this.timeout(MOCHA_LONG_TIMEOUT);
realDevice = true;
const spy = sandbox.stub(device, 'setAutoFillPasswords').resolves({device, realDevice});
await driver.createSession(
null,
null,
_.merge({}, caps, {
alwaysMatch: {'appium:setAutoFillPasswords': true},
}),
);
spy.notCalled.should.be.true;
});
});

describe('execute', function () {
Expand Down
Loading