You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi! 👋
Firstly, thanks for your work on this project!
I wanted to bring to your attention an issue I encountered while using the appiumV2 flag in conjunction with BrowserStack.
Today I used patch-package to patch [email protected] for the project I'm working on.
Upon utilizing the appiumV2 flag with a value of 'true', it appears that there is an unexpected behavior when integrating with BrowserStack. Specifically, the integration erroneously adds an unnecessary "appium:" prefix to certain parameters, namely 'platFormName' and 'bstack:option'. This prefix is not required and seems to be causing undesirable effects in the integration process.
To ensure a seamless integration experience, I suggest investigating this matter further. By eliminating the extraneous "appium:" prefix for the aforementioned parameters, the integration with BrowserStack could be enhanced and made more efficient.
This helper should be configured in codecept.conf.ts or codecept.conf.js
appiumV2: set this to true if you want to run tests with Appiumv2. See more how to setup here
app: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage
host: (default: 'localhost') Appium host
port: (default: '4723') Appium port
platform: (Android or IOS), which mobile OS to use; alias to desiredCapabilities.platformName
restart: restart browser or app between tests (default: true), if set to false cookies will be cleaned but browser window will be kept and for apps nothing will be changed.
desiredCapabilities: [], Appium capabilities, see below
* `platformName` - Which mobile OS platform to use
* `appPackage` - Java package of the Android app you want to run
* `appActivity` - Activity name for the Android activity you want to launch from your package.
* `deviceName`: The kind of mobile device or emulator to use
* `platformVersion`: Mobile OS version
* `app` - The absolute local path or remote http URL to an .ipa or .apk file, or a .zip containing one of these. Appium will attempt to install this app binary on the appropriate device first.
* `browserName`: Name of mobile web browser to automate. Should be an empty string if automating an app instead.
Example Android App:
{
helpers: {
Appium: {
platform: "Android",
desiredCapabilities: {
appPackage: "com.example.android.myApp",
appActivity: "MainActivity",
deviceName: "OnePlus3",
platformVersion: "6.0.1"
}
}
}
}
Example Android App: (Appium 2.x)
{
helpers: {
Appium: {
appiumV2: true,
platform: "android",
deviceName: "Test",
app: "D:/Playground/android.apk",
path: "/",
desiredCapabilities: {
"appium:platformVersion": "10.0",
"appium:automationName": "UiAutomator2",
}
}
}
}
Example iOS Mobile Web with local Appium:
{
helpers: {
Appium: {
platform: "iOS",
url: "https://the-internet.herokuapp.com/",
desiredCapabilities: {
deviceName: "iPhone X",
platformVersion: "12.0",
browserName: "safari"
}
}
}
}
Example iOS Mobile Web on BrowserStack:
{
helpers: {
Appium: {
host: "hub-cloud.browserstack.com",
port: 4444,
user: process.env.BROWSERSTACK_USER,
key: process.env.BROWSERSTACK_KEY,
platform: "iOS",
url: "https://the-internet.herokuapp.com/",
desiredCapabilities: {
realMobile: "true",
device: "iPhone 8",
os_version: "12",
browserName: "safari"
}
}
}
}
Example iOS Mobile Web on BrowserStack (Appium 2.x):
_validateConfig(config) {
if (!(config.app || config.platform) && !config.browser) {
throw new Error(Appium requires either platform and app or a browser to be set. Check your codeceptjs config file to ensure these are set properly { "helpers": { "Appium": { "app": "/path/to/app/package" "platform": "MOBILE_OS", } } });
}
recorder.add('restore from Web session', () => recorder.session.restore(), true);
return recorder.promise();
}
/* eslint-enable */
async _runWithCaps(caps, fn) {
if (typeof caps === 'object') {
for (const key in caps) {
// skip if capabilities do not match
if (this.config.desiredCapabilities[key] !== caps[key]) {
return;
}
}
}
if (typeof caps === 'function') {
if (!fn) {
fn = caps;
} else {
// skip if capabilities are checked inside a function
const enabled = caps(this.config.desiredCapabilities);
if (!enabled) return;
}
}
Appium: support only Android
*/
async seeAppIsNotInstalled(bundleId) {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.isAppInstalled(bundleId);
return truth(app ${bundleId}, 'not to be installed').negate(res);
}
Appium: support only Android
*/
async seeCurrentActivityIs(currentActivity) {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.getCurrentActivity();
return truth('current activity', to be ${currentActivity}).assert(res === currentActivity);
}
if (context) return this._switchToContext(context);
const contexts = await this.grabAllContexts();
this.debugSection('Contexts', contexts.toString());
for (const idx in contexts) {
if (contexts[idx].match(/^WEBVIEW/)) return this._switchToContext(contexts[idx]);
}
throw new Error('No WEBVIEW could be guessed, please specify one in params');
}
/**
Switches to native context.
By default switches to NATIVE_APP context unless other specified.
Appium: support Android and iOS
*/
async swipeTo(searchableLocator, scrollLocator, direction, timeout, offset, speed) {
onlyForApps.call(this);
direction = direction || 'down';
switch (direction) {
case 'down':
direction = 'swipeDown';
break;
case 'up':
direction = 'swipeUp';
break;
case 'left':
direction = 'swipeLeft';
break;
case 'right':
direction = 'swipeRight';
break;
}
timeout = timeout || this.options.waitForTimeoutInSeconds;
const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds`;
const browser = this.browser;
let err = false;
let currentSource;
return browser.waitUntil(() => {
if (err) {
return new Error(`Scroll to the end and element ${searchableLocator} was not found`);
}
return browser.$$(parseLocator.call(this, searchableLocator))
.then(els => els.length && els[0].isDisplayed())
.then((res) => {
if (res) {
return true;
}
return this[direction](scrollLocator, offset, speed).getSource().then((source) => {
if (source === currentSource) {
err = true;
} else {
currentSource = source;
return false;
}
});
});
}, timeout * 1000, errorMsg)
.catch((e) => {
if (e.message.indexOf('timeout') && e.type !== 'NoSuchElement') {
throw new AssertionFailedError({ customMessage: `Scroll to the end and element ${searchableLocator} was not found` }, '');
} else {
throw e;
}
});
}
/**
Performs a specific touch action.
The action object need to contain the action name, x/y coordinates
I.touchPerform([{
action: 'press',
options: {
x: 100,
y: 200
}
}, {action: 'release'}])
I.touchPerform([{
action: 'tap',
options: {
element: '1', // json web element was queried before
Supported only for web testing
*/
async scrollIntoView(locator, scrollIntoViewOptions) {
if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions);
}
Supported only for web testing
*/
async selectOption(select, option) {
if (this.isWeb) return super.selectOption(select, option);
throw new Error('Should be used only in Web context. In native context use 'click' method instead');
}
function parseLocator(locator) {
if (!locator) return null;
if (typeof locator === 'object') {
if (locator.web && this.isWeb) {
return parseLocator.call(this, locator.web);
}
if (locator.android && this.platform === 'android') {
if (typeof locator.android === 'string') {
return parseLocator.call(this, locator.android);
}
// The locator is an Android DataMatcher or ViewMatcher locator so return as is
return locator.android;
}
if (locator.ios && this.platform === 'ios') {
return parseLocator.call(this, locator.ios);
}
}
if (typeof locator === 'string') {
if (locator[0] === '~') return locator;
if (locator.substr(0, 2) === '//') return locator;
if (locator[0] === '#' && !this.isWeb) {
// hook before webdriverio supports native # locators
return parseLocator.call(this, { id: locator.slice(1) });
}
locator = new Locator(locator, 'xpath');
if (locator.type === 'css' && !this.isWeb) throw new Error('Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id');
if (locator.type === 'name' && !this.isWeb) throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id");
if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return //*[@resource-id='${locator.value}'];
return locator.simplify();
}
// in the end of a file
function onlyForApps(expectedPlatform) {
const stack = new Error().stack || '';
const re = /Appium.(\w+)/g;
const caller = stack.split('\n')[2].trim();
const m = re.exec(caller);
if (!m) {
throw new Error(Invalid caller ${caller});
}
const callerName = m[1] || m[2];
if (!expectedPlatform) {
if (!this.platform) {
throw new Error(${callerName} method can be used only with apps);
}
} else if (this.platform !== expectedPlatform.toLowerCase()) {
throw new Error(${callerName} method can be used only with ${expectedPlatform} apps);
}
}
module.exports = Appium;
The text was updated successfully, but these errors were encountered:
What are you trying to achieve?
Hi! 👋
Firstly, thanks for your work on this project!
I wanted to bring to your attention an issue I encountered while using the appiumV2 flag in conjunction with BrowserStack.
Today I used patch-package to patch
[email protected]
for the project I'm working on.Upon utilizing the appiumV2 flag with a value of 'true', it appears that there is an unexpected behavior when integrating with BrowserStack. Specifically, the integration erroneously adds an unnecessary "appium:" prefix to certain parameters, namely 'platFormName' and 'bstack:option'. This prefix is not required and seems to be causing undesirable effects in the integration process.
To ensure a seamless integration experience, I suggest investigating this matter further. By eliminating the extraneous "appium:" prefix for the aforementioned parameters, the integration with BrowserStack could be enhanced and made more efficient.
Here is the diff that solved my problem:
Details
'\n' +
'
diff\n' + 'diff --git a/node_modules/codeceptjs/lib/helper/Appium.js b/node_modules/codeceptjs/lib/helper/Appium.js\n' + 'index 0595519..bfeb786 100644\n' + '--- a/node_modules/codeceptjs/lib/helper/Appium.js\n' + '+++ b/node_modules/codeceptjs/lib/helper/Appium.js\n' + '@@ -74,6 +74,24 @@ const vendorPrefix = {\n' + ' * }\n' + ' * }\n' + ' * }\n' + '+ * \n' + '+ * \n' + '+ * Example Android App: (Appium 2.x)\n' + '+ * {\n' + '+ * helpers: {\n' + '+ * Appium: {\n' + '+ * appiumV2: true,\n' + '+ * platform: "android",\n' + '+ * deviceName: "Test",\n' + '+ * app: "D:/Playground/android.apk",\n' + '+ * path: "/",\n' + '+ * desiredCapabilities: {\n' + '+ * "appium:platformVersion": "10.0",\n' + '+ * "appium:automationName": "UiAutomator2",\n' + '+ * }\n' + '+ * }\n' + '+ * }\n' + '+ * }\n' + ' *
\n' +' \n' +
' * Example iOS Mobile Web with local Appium:\n' +
'@@ -111,6 +129,37 @@ const vendorPrefix = {\n' +
' * device: "iPhone 8",\n' +
' * os_version: "12",\n' +
' * browserName: "safari"\n' +
'+ * }\n' +
'+ * }\n' +
'+ * }\n' +
'+ * }\n' +
'+ * \n' +
'+ * Example iOS Mobile Web on BrowserStack (Appium 2.x):\n' +
'+ * Appium: {\n' +
'+ * appiumV2: true,\n' +
'+ * host: "hub-cloud.browserstack.com",\n' +
'+ * port: 4444,\n' +
'+ * user: process.env.BROWSERSTACK_USER,\n' +
'+ * key: process.env.BROWSERSTACK_KEY,\n' +
'+ platform: "iOS",\n' +
'+* os_version: "16",\n' +
'+* device: "iPhone 14",\n' +
'+* app: "bs://xxxxxxxxxxxxxxxxxxxxxxx",\n' +
'+* desiredCapabilities: {\n' +
'+* "appium:platformVersion": "16",\n' +
'+* "appium:deviceName": "iPhone 14",\n' +
'+* "appium:app": "bs://xxxxxxxxxxxxxxxxxxxxxxx",\n' +
'+* "bstack:options": {\n' +
'+* projectName: "projectName",\n' +
'+* buildName: "Build 1.0",\n' +
'+* sessionName: "Smoke Test",\n' +
'+* appiumVersion: "2.0.1",\n' +
'+* debug: true,\n' +
'+* idleTimeout: "0",\n' +
'+* deviceLogs: "true",\n' +
'+* video: "true",\n' +
'+* networkLogs: "true",\n' +
'+* appiumLogs: "true",\n' +
' * }\n' +
' * }\n' +
' * }\n' +
'@@ -188,24 +237,26 @@ class Appium extends Webdriver {\n' +
' config = Object.assign(defaults, config);\n' +
' \n' +
' config.baseUrl = config.url || config.baseUrl;\n' +
'- if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {\n' +
'- config.capabilities = this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities;\n' +
'- }\n' +
' \n' +
'- if (this.appiumV2) {\n' +
'- config.capabilities[
${vendorPrefix.appium}:deviceName
] = config[${vendorPrefix.appium}:device
] || config.capabilities[${vendorPrefix.appium}:deviceName
];\n' +'- config.capabilities[
${vendorPrefix.appium}:browserName
] = config[${vendorPrefix.appium}:browser
] || config.capabilities[${vendorPrefix.appium}:browserName
];\n' +'- config.capabilities[
${vendorPrefix.appium}:app
] = config[${vendorPrefix.appium}:app
] || config.capabilities[${vendorPrefix.appium}:app
];\n' +'- config.capabilities[
${vendorPrefix.appium}:tunnelIdentifier
] = config[${vendorPrefix.appium}:tunnelIdentifier
] || config.capabilities[${vendorPrefix.appium}:tunnelIdentifier
]; // Adding the code to connect to sauce labs via sauce tunnel\n' +'+ if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {\n' +
'+ \n' +
'+ config.capabilities = config.desiredCapabilities;\n' +
'+ }\n' +
'+ \n' +
'+ if (config.appiumV2) {\n' +
'+ config.capabilities[
${vendorPrefix.appium}:deviceName
] = config[${vendorPrefix.appium}:device
] || config.capabilities[${vendorPrefix.appium}:deviceName
];\n' +'+ config.capabilities[
${vendorPrefix.appium}:browserName
] = config[${vendorPrefix.appium}:browser
] || config.capabilities[${vendorPrefix.appium}:browserName
];\n' +'+ config.capabilities[
${vendorPrefix.appium}:app
] = config[${vendorPrefix.appium}:app
] || config.capabilities[${vendorPrefix.appium}:app
];\n' +'+ config.capabilities[
${vendorPrefix.appium}:tunnelIdentifier
] = config[${vendorPrefix.appium}:tunnelIdentifier
] || config.capabilities[${vendorPrefix.appium}:tunnelIdentifier
]; // Adding the code to connect to sauce labs via sauce tunnel\n' +' } else {\n' +
'- config.capabilities.deviceName = config.device || config.capabilities.deviceName;\n' +
'- config.capabilities.browserName = config.browser || config.capabilities.browserName;\n' +
'- config.capabilities.app = config.app || config.capabilities.app;\n' +
'- config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel\n' +
'+ config.capabilities.deviceName = config.device || config.capabilities.deviceName;\n' +
'+ config.capabilities.browserName = config.browser || config.capabilities.browserName;\n' +
'+ config.capabilities.app = config.app || config.capabilities.app;\n' +
'+ config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel\n' +
' }\n' +
' \n' +
'- config.capabilities.platformName = config.platform || config.capabilities.platformName;\n' +
'- config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds\n' +
'+ config.capabilities.platformName = config.platform || config.capabilities.platformName;\n' +
'+ config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds\n' +
' \n' +
' // [CodeceptJS compatible] transform host to hostname\n' +
' config.hostname = config.host || config.hostname;\n' +
'@@ -218,7 +269,7 @@ class Appium extends Webdriver {\n' +
' this.root = mobileRoot;\n' +
' }\n' +
' \n' +
'- this.platform = null;\n' +
'+ this.platform = null;\n' +
' if (config.capabilities[
${vendorPrefix.appium}:platformName
]) {\n' +' this.platform = config.capabilities[
${vendorPrefix.appium}:platformName
].toLowerCase();\n' +' }\n' +
'@@ -226,22 +277,9 @@ class Appium extends Webdriver {\n' +
' if (config.capabilities.platformName) {\n' +
' this.platform = config.capabilities.platformName.toLowerCase();\n' +
' }\n' +
'-\n' +
' return config;\n' +
' }\n' +
' \n' +
'- _convertAppiumV2Caps(capabilities) {\n' +
'- const _convertedCaps = {};\n' +
'- for (const [key, value] of Object.entries(capabilities)) {\n' +
'- if (!key.startsWith(vendorPrefix.appium)) {\n' +
'- _convertedCaps[
${vendorPrefix.appium}:${key}
] = value;\n' +'- } else {\n' +
'- _convertedCaps[
${key}
] = value;\n' +'- }\n' +
'- }\n' +
'- return _convertedCaps;\n' +
'- }\n' +
'-\n' +
' static _config() {\n' +
' return [{\n' +
" name: 'app',\n" +
'@@ -261,11 +299,6 @@ class Appium extends Webdriver {\n' +
' }\n' +
' \n' +
' async _startBrowser() {\n' +
'- if (this.appiumV2 === true) {\n' +
'- this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities);\n' +
'- this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities);\n' +
'- }\n' +
'-\n' +
' try {\n' +
' if (this.options.multiremote) {\n' +
' this.browser = await webdriverio.multiremote(this.options.multiremote);\n' +
'```\n' +
'\n' +
'This issue body was partially generated by patch-package.\n'
Here is full Appium.js file that works. just in case if above details complicated to read.
let webdriverio;
const fs = require('fs');
const axios = require('axios').default;
const Webdriver = require('./WebDriver');
const AssertionFailedError = require('../assert/error');
const { truth } = require('../assert/truth');
const recorder = require('../recorder');
const Locator = require('../locator');
const ConnectionRefused = require('./errors/ConnectionRefused');
const mobileRoot = '//*';
const webRoot = 'body';
const supportedPlatform = {
android: 'Android',
iOS: 'iOS',
};
const vendorPrefix = {
appium: 'appium',
};
/**
Appium Installation
appium
Helper configuration
appiumV2
: set this to true if you want to run tests with Appiumv2. See more how to setup hereapp
: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackagehost
: (default: 'localhost') Appium hostport
: (default: '4723') Appium portplatform
: (Android or IOS), which mobile OS to use; alias to desiredCapabilities.platformNamerestart
: restart browser or app between tests (default: true), if set to false cookies will be cleaned but browser window will be kept and for apps nothing will be changed.desiredCapabilities
: [], Appium capabilities, see belowAccess From Helpers
browser
property:Methods
/
class Appium extends Webdriver {
/*
*/
// @ts-ignore
constructor(config) {
super(config);
}
_validateConfig(config) {
if (!(config.app || config.platform) && !config.browser) {
throw new Error(
Appium requires either platform and app or a browser to be set. Check your codeceptjs config file to ensure these are set properly { "helpers": { "Appium": { "app": "/path/to/app/package" "platform": "MOBILE_OS", } } }
);}
if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
}
}
static _config() {
return [{
name: 'app',
message: 'Application package. Path to file or url',
default: 'http://localhost',
}, {
name: 'platform',
message: 'Mobile Platform',
type: 'list',
choices: ['iOS', supportedPlatform.android],
default: supportedPlatform.android,
}, {
name: 'device',
message: 'Device to run tests on',
default: 'emulator',
}];
}
async _startBrowser() {
try {
if (this.options.multiremote) {
this.browser = await webdriverio.multiremote(this.options.multiremote);
} else {
this.browser = await webdriverio.remote(this.options);
}
} catch (err) {
if (err.toString().indexOf('ECONNREFUSED')) {
throw new ConnectionRefused(err);
}
throw err;
}
this.$$ = this.browser.$$.bind(this.browser);
}
async _after() {
if (!this.isRunning) return;
if (this.options.restart) {
this.isRunning = false;
return this.browser.deleteSession();
}
if (this.isWeb && !this.platform) {
return super._after();
}
}
async _withinBegin(context) {
if (this.isWeb) {
return super._withinBegin(context);
}
if (context === 'webview') {
return this.switchToWeb();
}
if (typeof context === 'object') {
if (context.web) return this.switchToWeb(context.web);
if (context.webview) return this.switchToWeb(context.webview);
}
return this._switchToContext(context);
}
_withinEnd() {
if (this.isWeb) {
return super._withinEnd();
}
return this.switchToNative();
}
_buildAppiumEndpoint() {
const {
protocol, port, hostname, path,
} = this.browser.options;
// Build path to Appium REST API endpoint
return
${protocol}://${hostname}:${port}${path}
;}
/**
*/
async runOnIOS(caps, fn) {
if (this.platform !== 'ios') return;
recorder.session.start('iOS-only actions');
this._runWithCaps(caps, fn);
recorder.add('restore from iOS session', () => recorder.session.restore());
return recorder.promise();
}
/**
*/
async runOnAndroid(caps, fn) {
if (this.platform !== 'android') return;
recorder.session.start('Android-only actions');
this._runWithCaps(caps, fn);
recorder.add('restore from Android session', () => recorder.session.restore());
return recorder.promise();
}
/**
/
/ eslint-disable */
async runInWeb(fn) {
if (!this.isWeb) return;
recorder.session.start('Web-only actions');
}
/* eslint-enable */
async _runWithCaps(caps, fn) {
if (typeof caps === 'object') {
for (const key in caps) {
// skip if capabilities do not match
if (this.config.desiredCapabilities[key] !== caps[key]) {
return;
}
}
}
if (typeof caps === 'function') {
if (!fn) {
fn = caps;
} else {
// skip if capabilities are checked inside a function
const enabled = caps(this.config.desiredCapabilities);
if (!enabled) return;
}
}
}
/**
*/
async checkIfAppIsInstalled(bundleId) {
onlyForApps.call(this, supportedPlatform.android);
}
/**
*/
async seeAppIsInstalled(bundleId) {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.isAppInstalled(bundleId);
return truth(
app ${bundleId}
, 'to be installed').assert(res);}
/**
*/
async seeAppIsNotInstalled(bundleId) {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.isAppInstalled(bundleId);
return truth(
app ${bundleId}
, 'not to be installed').negate(res);}
/**
*/
async installApp(path) {
onlyForApps.call(this, supportedPlatform.android);
return this.browser.installApp(path);
}
/**
*/
async removeApp(appId, bundleId) {
onlyForApps.call(this, supportedPlatform.android);
}
/**
*/
async resetApp() {
onlyForApps.call(this);
return this.axios({
method: 'post',
url:
${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/app/reset
,});
}
/**
*/
async seeCurrentActivityIs(currentActivity) {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.getCurrentActivity();
return truth('current activity',
to be ${currentActivity}
).assert(res === currentActivity);}
/**
*/
async seeDeviceIsLocked() {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.isLocked();
return truth('device', 'to be locked').assert(res);
}
/**
*/
async seeDeviceIsUnlocked() {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.isLocked();
return truth('device', 'to be locked').negate(res);
}
/**
*/
async seeOrientationIs(orientation) {
onlyForApps.call(this);
}
/**
*/
async setOrientation(orientation) {
onlyForApps.call(this);
}
/**
*/
async grabAllContexts() {
onlyForApps.call(this);
return this.browser.getContexts();
}
/**
*/
async grabContext() {
onlyForApps.call(this);
return this.browser.getContext();
}
/**
*/
async grabCurrentActivity() {
onlyForApps.call(this, supportedPlatform.android);
return this.browser.getCurrentActivity();
}
/**
*/
async grabNetworkConnection() {
onlyForApps.call(this, supportedPlatform.android);
const res = await this.browser.getNetworkConnection();
return {
value: res,
inAirplaneMode: res.inAirplaneMode,
hasWifi: res.hasWifi,
hasData: res.hasData,
};
}
/**
*/
async grabOrientation() {
onlyForApps.call(this);
const res = await this.browser.orientation();
this.debugSection('Orientation', res);
return res;
}
/**
*/
async grabSettings() {
onlyForApps.call(this);
const res = await this.browser.getSettings();
this.debugSection('Settings', JSON.stringify(res));
return res;
}
/**
*/
async _switchToContext(context) {
return this.browser.switchContext(context);
}
/**
*/
async switchToWeb(context) {
this.isWeb = true;
this.defaultContext = 'body';
}
/**
/
async switchToNative(context = null) {
this.isWeb = false;
this.defaultContext = '//';
}
/**
*/
async startActivity(appPackage, appActivity) {
onlyForApps.call(this, supportedPlatform.android);
return this.browser.startActivity(appPackage, appActivity);
}
/**
*/
async setNetworkConnection(value) {
onlyForApps.call(this, supportedPlatform.android);
return this.browser.setNetworkConnection(value);
}
/**
*/
async setSettings(settings) {
onlyForApps.call(this);
return this.browser.settings(settings);
}
/**
*/
async hideDeviceKeyboard(strategy, key) {
onlyForApps.call(this);
strategy = strategy || 'tapOutside';
return this.browser.hideKeyboard(strategy, key);
}
/**
*/
async sendDeviceKeyEvent(keyValue) {
onlyForApps.call(this, supportedPlatform.android);
return this.browser.pressKeyCode(keyValue);
}
/**
*/
async openNotifications() {
onlyForApps.call(this, supportedPlatform.android);
return this.browser.openNotifications();
}
/**
*/
async makeTouchAction(locator, action) {
onlyForApps.call(this);
const element = await this.browser.$(parseLocator.call(this, locator));
}
/**
makeTouchAction
*/
async tap(locator) {
return this.makeTouchAction(locator, 'tap');
}
/**
/
/ eslint-disable /
async swipe(locator, xoffset, yoffset, speed = 1000) {
onlyForApps.call(this);
const res = await this.browser.$(parseLocator.call(this, locator));
// if (!res.length) throw new ElementNotFound(locator, 'was not found in UI');
return this.performSwipe(await res.getLocation(), { x: (await res.getLocation()).x + xoffset, y: (await res.getLocation()).y + yoffset });
}
/ eslint-enable */
/**
*/
async performSwipe(from, to) {
await this.browser.touchPerform([{
action: 'press',
options: from,
}, {
action: 'wait',
options: { ms: 1000 },
}, {
action: 'moveTo',
options: to,
}, {
action: 'release',
}]);
await this.browser.pause(1000);
}
/**
*/
async swipeDown(locator, yoffset = 1000, speed) {
onlyForApps.call(this);
}
/**
*
*/
async swipeLeft(locator, xoffset = 1000, speed) {
onlyForApps.call(this);
if (!speed) {
speed = xoffset;
xoffset = 100;
}
}
/**
*/
async swipeRight(locator, xoffset = 1000, speed) {
onlyForApps.call(this);
if (!speed) {
speed = xoffset;
xoffset = 100;
}
}
/**
*/
async swipeUp(locator, yoffset = 1000, speed) {
onlyForApps.call(this);
}
/**
*/
async swipeTo(searchableLocator, scrollLocator, direction, timeout, offset, speed) {
onlyForApps.call(this);
direction = direction || 'down';
switch (direction) {
case 'down':
direction = 'swipeDown';
break;
case 'up':
direction = 'swipeUp';
break;
case 'left':
direction = 'swipeLeft';
break;
case 'right':
direction = 'swipeRight';
break;
}
timeout = timeout || this.options.waitForTimeoutInSeconds;
}
/**
*/
async touchPerform(actions) {
onlyForApps.call(this);
return this.browser.touchPerform(actions);
}
/**
*/
async pullFile(path, dest) {
onlyForApps.call(this);
return this.browser.pullFile(path).then(res => fs.writeFile(dest, Buffer.from(res, 'base64'), (err) => {
if (err) {
return false;
}
return true;
}));
}
/**
*/
async shakeDevice() {
onlyForApps.call(this, 'iOS');
return this.browser.shake();
}
/**
*/
async rotate(x, y, duration, radius, rotation, touchCount) {
onlyForApps.call(this, 'iOS');
return this.browser.rotate(x, y, duration, radius, rotation, touchCount);
}
/**
*/
async setImmediateValue(id, value) {
onlyForApps.call(this, 'iOS');
return this.browser.setImmediateValue(id, value);
}
/**
*/
async simulateTouchId(match) {
onlyForApps.call(this, 'iOS');
match = match || true;
return this.browser.touchId(match);
}
/**
*/
async closeApp() {
onlyForApps.call(this, 'iOS');
return this.browser.closeApp();
}
/**
*/
async appendField(field, value) {
if (this.isWeb) return super.appendField(field, value);
return super.appendField(parseLocator.call(this, field), value);
}
/**
*/
async checkOption(field) {
if (this.isWeb) return super.checkOption(field);
return super.checkOption(parseLocator.call(this, field));
}
/**
*/
async click(locator, context) {
if (this.isWeb) return super.click(locator, context);
return super.click(parseLocator.call(this, locator), parseLocator.call(this, context));
}
/**
*/
async dontSeeCheckboxIsChecked(field) {
if (this.isWeb) return super.dontSeeCheckboxIsChecked(field);
return super.dontSeeCheckboxIsChecked(parseLocator.call(this, field));
}
/**
*/
async dontSeeElement(locator) {
if (this.isWeb) return super.dontSeeElement(locator);
return super.dontSeeElement(parseLocator.call(this, locator));
}
/**
*/
async dontSeeInField(field, value) {
if (this.isWeb) return super.dontSeeInField(field, value);
return super.dontSeeInField(parseLocator.call(this, field), value);
}
/**
*/
async dontSee(text, context = null) {
if (this.isWeb) return super.dontSee(text, context);
return super.dontSee(text, parseLocator.call(this, context));
}
/**
*/
async fillField(field, value) {
value = value.toString();
if (this.isWeb) return super.fillField(field, value);
return super.fillField(parseLocator.call(this, field), value);
}
/**
*/
async grabTextFromAll(locator) {
if (this.isWeb) return super.grabTextFromAll(locator);
return super.grabTextFromAll(parseLocator.call(this, locator));
}
/**
*/
async grabTextFrom(locator) {
if (this.isWeb) return super.grabTextFrom(locator);
return super.grabTextFrom(parseLocator.call(this, locator));
}
/**
*/
async grabNumberOfVisibleElements(locator) {
if (this.isWeb) return super.grabNumberOfVisibleElements(locator);
return super.grabNumberOfVisibleElements(parseLocator.call(this, locator));
}
/**
*/
async grabAttributeFrom(locator, attr) {
if (this.isWeb) return super.grabAttributeFrom(locator, attr);
return super.grabAttributeFrom(parseLocator.call(this, locator), attr);
}
/**
*/
async grabAttributeFromAll(locator, attr) {
if (this.isWeb) return super.grabAttributeFromAll(locator, attr);
return super.grabAttributeFromAll(parseLocator.call(this, locator), attr);
}
/**
*/
async grabValueFromAll(locator) {
if (this.isWeb) return super.grabValueFromAll(locator);
return super.grabValueFromAll(parseLocator.call(this, locator));
}
/**
*/
async grabValueFrom(locator) {
if (this.isWeb) return super.grabValueFrom(locator);
return super.grabValueFrom(parseLocator.call(this, locator));
}
/**
*/
async saveScreenshot(fileName) {
return super.saveScreenshot(fileName, false);
}
/**
*/
async scrollIntoView(locator, scrollIntoViewOptions) {
if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions);
}
/**
*/
async seeCheckboxIsChecked(field) {
if (this.isWeb) return super.seeCheckboxIsChecked(field);
return super.seeCheckboxIsChecked(parseLocator.call(this, field));
}
/**
*/
async seeElement(locator) {
if (this.isWeb) return super.seeElement(locator);
return super.seeElement(parseLocator.call(this, locator));
}
/**
*/
async seeInField(field, value) {
if (this.isWeb) return super.seeInField(field, value);
return super.seeInField(parseLocator.call(this, field), value);
}
/**
*/
async see(text, context) {
if (this.isWeb) return super.see(text, context);
return super.see(text, parseLocator.call(this, context));
}
/**
*/
async selectOption(select, option) {
if (this.isWeb) return super.selectOption(select, option);
throw new Error('Should be used only in Web context. In native context use 'click' method instead');
}
/**
*/
async waitForElement(locator, sec = null) {
if (this.isWeb) return super.waitForElement(locator, sec);
return super.waitForElement(parseLocator.call(this, locator), sec);
}
/**
*/
async waitForVisible(locator, sec = null) {
if (this.isWeb) return super.waitForVisible(locator, sec);
return super.waitForVisible(parseLocator.call(this, locator), sec);
}
/**
*/
async waitForInvisible(locator, sec = null) {
if (this.isWeb) return super.waitForInvisible(locator, sec);
return super.waitForInvisible(parseLocator.call(this, locator), sec);
}
/**
*/
async waitForText(text, sec = null, context = null) {
if (this.isWeb) return super.waitForText(text, sec, context);
return super.waitForText(text, sec, parseLocator.call(this, context));
}
}
function parseLocator(locator) {
if (!locator) return null;
if (typeof locator === 'object') {
if (locator.web && this.isWeb) {
return parseLocator.call(this, locator.web);
}
}
if (typeof locator === 'string') {
if (locator[0] === '~') return locator;
if (locator.substr(0, 2) === '//') return locator;
if (locator[0] === '#' && !this.isWeb) {
// hook before webdriverio supports native # locators
return parseLocator.call(this, { id: locator.slice(1) });
}
}
locator = new Locator(locator, 'xpath');
if (locator.type === 'css' && !this.isWeb) throw new Error('Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id');
if (locator.type === 'name' && !this.isWeb) throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id");
if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return
//*[@resource-id='${locator.value}']
;return locator.simplify();
}
// in the end of a file
function onlyForApps(expectedPlatform) {
const stack = new Error().stack || '';
const re = /Appium.(\w+)/g;
const caller = stack.split('\n')[2].trim();
const m = re.exec(caller);
if (!m) {
throw new Error(
Invalid caller ${caller}
);}
const callerName = m[1] || m[2];
if (!expectedPlatform) {
if (!this.platform) {
throw new Error(
${callerName} method can be used only with apps
);}
} else if (this.platform !== expectedPlatform.toLowerCase()) {
throw new Error(
${callerName} method can be used only with ${expectedPlatform} apps
);}
}
module.exports = Appium;
The text was updated successfully, but these errors were encountered: