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

Improve Appium support in Nightwatch #3519

Merged
merged 26 commits into from Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0531906
Automatically start appium server in Nightwatch.
garg3133 Dec 13, 2022
e6846e9
Create separate startServer method in AppiumServer.
garg3133 Dec 15, 2022
752812a
Convert relative paths to absolute paths for Appium.
garg3133 Dec 21, 2022
124b46e
Remove appium_version, for now.
garg3133 Dec 21, 2022
f9d4c14
Use chromedriver npm package if path not explicitely passed.
garg3133 Dec 22, 2022
39eca9f
Add tests for appium session and service builder.
garg3133 Dec 23, 2022
f058e1f
Add more tests for changes.
garg3133 Dec 25, 2022
9d56c82
Fix testAppiumOptions tests.
garg3133 Dec 25, 2022
a8d2c11
Add backward compatibility for browserName=null.
garg3133 Dec 26, 2022
8916448
Skip Appium's createSessionOptions for BrowserStack.
garg3133 Dec 26, 2022
709fd41
Fix tests.
garg3133 Dec 26, 2022
7b40016
Fix client.click() test.
garg3133 Dec 26, 2022
c76e481
isAppium -> use_appium
garg3133 Dec 26, 2022
e6c85aa
Remove the logic for converting relative paths to absolute for Appium.
garg3133 Dec 29, 2022
e000786
Resolve BrowserStack related issues.
garg3133 Dec 29, 2022
1167538
Create separate classes for Automate and AppAutomate.
garg3133 Dec 30, 2022
d7a996e
Modify BrowserStack's folder structure.
garg3133 Jan 3, 2023
bf62d81
Fix appium server tests.
garg3133 Jan 3, 2023
48bb1b9
Fix import paths.
garg3133 Jan 3, 2023
273af20
Add browserstack transport and createSession tests.
garg3133 Jan 3, 2023
27ba564
Increase appium server startup timeout to 15sec.
garg3133 Jan 4, 2023
54587b1
Create a factory method to create service.
garg3133 Jan 4, 2023
0706064
AppiumMixin -> AppiumBaseServer.
garg3133 Jan 4, 2023
79bebbb
Pass udid using --deviceId flag.
garg3133 Jan 4, 2023
019b395
Fix failing tests.
garg3133 Jan 4, 2023
e7fac99
Remove unused function from testAppiumServer.
garg3133 Jan 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/runner/cli/cli.js
Expand Up @@ -469,8 +469,11 @@ class CliRunner {
let promise = Promise.resolve();

if (this.test_settings.selenium && this.test_settings.selenium.start_process) {
const SeleniumServer = require('../../transport/selenium-webdriver/selenium.js');
this.seleniumService = SeleniumServer.startServer(this.test_settings);
const Server = this.test_settings.selenium.isAppium
? require('../../transport/selenium-webdriver/appium.js')
: require('../../transport/selenium-webdriver/selenium.js');

this.seleniumService = Server.startServer(this.test_settings);
promise = this.seleniumService.init();
this.test_settings.selenium['[_started]'] = true;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/settings/defaults.js
Expand Up @@ -146,7 +146,7 @@ module.exports = {
start_process: false,
cli_args: {},
server_path: null,
log_path: '',
log_path: './logs',
port: undefined,
check_process_delay: 500,
max_status_poll_tries: 15,
Expand Down
13 changes: 6 additions & 7 deletions lib/transport/factory.js
Expand Up @@ -49,7 +49,7 @@ module.exports = class TransportFactory {
let {browserName} = settings.desiredCapabilities;

// Better support for Appium, if the browserName has explicitly been set to null we can skip all further checks
if (browserName === null) {
if (nightwatchInstance.settings.selenium.isAppium && !browserName) {
return browserName;
}

Expand Down Expand Up @@ -92,15 +92,14 @@ module.exports = class TransportFactory {
}

if (usingSeleniumServer) {
const Selenium = require('./selenium-webdriver/selenium.js');

// support for mobile native apps
if (browserName === null) {
const Mobile = require('./selenium-webdriver/mobile-webdriver.js');
if (nightwatchInstance.settings.selenium.isAppium) {
const Appium = require('./selenium-webdriver/appium.js');

return new Mobile(nightwatchInstance);
return new Appium(nightwatchInstance, browserName);
}

const Selenium = require('./selenium-webdriver/selenium.js');

return new Selenium(nightwatchInstance, browserName);
}

Expand Down
59 changes: 59 additions & 0 deletions lib/transport/selenium-webdriver/appium.js
@@ -0,0 +1,59 @@
const {WebDriver} = require('selenium-webdriver');
const {Executor} = require('selenium-webdriver/http');
const SeleniumServer = require('./selenium');
const http = require('selenium-webdriver/http');
const {isObject} = require('../../utils');


module.exports = class AppiumServer extends SeleniumServer {
static get ServiceBuilder() {
return require('./service-builders/appium.js');
}
garg3133 marked this conversation as resolved.
Show resolved Hide resolved

get defaultBrowser() {
return null;
}

get ServiceBuilder() {
return AppiumServer.ServiceBuilder;
}

get defaultServerUrl() {
return 'http://127.0.0.1:4723';
}

get defaultPort() {
return '4723';
}

get defaultPathPrefix() {
return '/wd/hub';
}

createSessionOptions() {
const options = this.desiredCapabilities;

// break 'appium:options' to individual configs
if (isObject(options['appium:options'])) {
const appiumOptions = options['appium:options'];
for (let key of Object.keys(appiumOptions)) {
const value = appiumOptions[key];

if (!key.startsWith('appium:')) {
key = `appium:${key}`;
}
options[key] = value;
}

delete options['appium:options'];
}

return options;
}

createDriver({options = this.desiredCapabilities} = {}) {
const httpClient = new http.HttpClient(this.getServerUrl());

return WebDriver.createSession(new Executor(httpClient), options);
}
};
2 changes: 1 addition & 1 deletion lib/transport/selenium-webdriver/browserstack.js
@@ -1,6 +1,6 @@
const stripAnsi = require('strip-ansi');
const {Logger} = require('../../utils');
const BaseDriver = require('./mobile-webdriver.js');
const BaseDriver = require('./appium.js');
garg3133 marked this conversation as resolved.
Show resolved Hide resolved
const defaultsDeep = require('lodash.defaultsdeep');

class Browserstack extends BaseDriver {
Expand Down
5 changes: 1 addition & 4 deletions lib/transport/selenium-webdriver/index.js
Expand Up @@ -193,17 +193,14 @@ class Transport extends BaseTransport {

async createDriverService({options, moduleKey, reuseBrowser = false}) {
try {
moduleKey = this.settings.webdriver.log_file_name || moduleKey || '';

if (!this.shouldReuseDriverService(reuseBrowser)) {
Transport.driverService = new this.ServiceBuilder(this.settings);
await Transport.driverService.setOutputFile(reuseBrowser ? 'test' : moduleKey).init(options);
}

this.driverService = Transport.driverService;

moduleKey = this.settings.webdriver.log_file_name || moduleKey || '';

Transport.driverService = this.driverService;
garg3133 marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
this.showConnectSpinner(colors.red(`Failed to start ${this.serviceName}.`), 'warn');

Expand Down
25 changes: 0 additions & 25 deletions lib/transport/selenium-webdriver/mobile-webdriver.js

This file was deleted.

6 changes: 6 additions & 0 deletions lib/transport/selenium-webdriver/options.js
Expand Up @@ -247,6 +247,12 @@ module.exports = class SeleniumCapabilities {
if (this.shouldSetupWebdriver()) {
try {
if (this.usingSeleniumServer()) {
if (this.settings.selenium.isAppium) {
this.settings.selenium.server_path = this.settings.webdriver.server_path = require.resolve('appium');

return this;
}

this.settings.selenium.server_path = this.settings.webdriver.server_path = require('@nightwatch/selenium-server').path;
this.settings.selenium.cli_args = this.settings.selenium.cli_args || {};

Expand Down
12 changes: 7 additions & 5 deletions lib/transport/selenium-webdriver/selenium.js
@@ -1,5 +1,4 @@
const DefaultSeleniumDriver = require('./');
const SeleniumServerBuilder = require('./service-builders/selenium.js');

module.exports = class SeleniumServer extends DefaultSeleniumDriver {
/**
Expand All @@ -10,21 +9,24 @@ module.exports = class SeleniumServer extends DefaultSeleniumDriver {
const opts = new Options({settings});
opts.updateWebdriverPath();

const seleniumService = new SeleniumServerBuilder(settings);
const moduleKey = settings.webdriver.log_file_name || '';
const seleniumService = new this.ServiceBuilder(settings);

seleniumService.setOutputFile(moduleKey);
const outputFile = settings.webdriver.log_file_name || '';
seleniumService.setOutputFile(outputFile);

return seleniumService;
}

static get ServiceBuilder() {
return require('./service-builders/selenium.js');
}
garg3133 marked this conversation as resolved.
Show resolved Hide resolved

get defaultBrowser() {
return 'firefox';
}

get ServiceBuilder() {
return SeleniumServerBuilder;
return SeleniumServer.ServiceBuilder;
}

get defaultServerUrl() {
Expand Down
98 changes: 98 additions & 0 deletions lib/transport/selenium-webdriver/service-builders/appium.js
@@ -0,0 +1,98 @@
const {SeleniumServer, DriverService} = require('selenium-webdriver/remote');
const {getFreePort} = require('../../../utils');
const BaseService = require('./base-service.js');

class AppiumServer extends DriverService {
constructor(server_path, opt_options) {
const options = opt_options || {};
const {args} = options;

const port = options.port;
if (port !== AppiumServiceBuilder.defaultPort && !args.includes('--port')) {
args.unshift('--port', port);
}

let cmd = 'node';
if (server_path.startsWith('appium')) {
cmd = server_path;
} else {
args.unshift(server_path);
}

super(cmd, {
loopback: options.loopback,
port,
args,
path: '/wd/hub',
env: options.env,
stdio: options.stdio
});
}
}

class AppiumServiceBuilder extends BaseService {
static get serviceName() {
return 'Appium Server';
}

static get defaultPort() {
return 4723;
}

get npmPackageName() {
return 'appium';
}

get outputFile() {
return this._outputFile + '_appium-server.log';
}

get defaultPort() {
return AppiumServiceBuilder.defaultPort;
}

get serviceName() {
return 'Appium Server';
}

get downloadMessage() {
return 'install Appium globally with "npm i -g appium" command, \n and set ' +
'"selenium.server_path" config option to "appium".';
}

/**
* @param {Capabilities} opts
* @returns {Promise<void>}
*/
async createService(opts = {}) {
const {port} = this;
const options = new SeleniumServer.Options();
options.port = port || await getFreePort();
let {server_path, appium_version} = this.settings.webdriver;

const introMsg = `Starting Appium Server on port ${options.port}...`;

if (opts.showSpinner) {
opts.showSpinner(`${introMsg}\n\n`);
} else {
// eslint-disable-next-line
console.info(introMsg);
}

// TODO: read the log_path and add it to cliArgs
// above TODO is copied from ./selenium.js
options.args = this.cliArgs;
options.appium_version = appium_version;

if (this.hasSinkSupport() && this.needsSinkProcess()) {
this.createSinkProcess();
options.stdio = ['pipe', this.process.stdin, this.process.stdin];
}

this.service = new AppiumServer(server_path, options);

return this.service.start();
}
}

module.exports = AppiumServiceBuilder;
Expand Up @@ -359,7 +359,7 @@ class BaseService {
return resolve();
}

Logger.info(`Wrote log file to: ${filePath}.`);
Logger.info(`Wrote log file to: ${filePath}`);
this.stopped = true;
resolve();
});
Expand Down