Skip to content
This repository was archived by the owner on Jan 19, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Build Status](https://travis-ci.org/TestArmada/magellan-testobject-executor.svg?branch=master)](https://travis-ci.org/TestArmada/magellan-testobject-executor)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![codecov](https://codecov.io/gh/TestArmada/magellan-testobject-executor/branch/master/graph/badge.svg)](https://codecov.io/gh/TestArmada/magellan-testobject-executor)
[![Downloads](http://img.shields.io/npm/dm/testarmada-magellan-testobject-executor?style=flat)](https://npmjs.org/package/testarmada-magellan-testobject-executor)

Executor for [Magellan](https://github.com/TestArmada/magellan) to run [nightwatchjs](http://nightwatchjs.org/) tests in [TestObject](https://testobject.com/) environment.

Expand All @@ -11,6 +12,7 @@ Executor for [Magellan](https://github.com/TestArmada/magellan) to run [nightwat
## What does this executor do
1. It talks [Muffin]() so that the desiredCapabilities shrinks down to a string, which makes your browser selection an easy work
2. It runs nightwatch test by forking it as magellan child process
3. It launches sauce tunnel and manages its life cycle for you during test. Check this [page](https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy+and+Real+Device+Testing) for more details.

## How To Use
Please follow the steps
Expand All @@ -23,30 +25,50 @@ Please follow the steps
]
```
3. set env variables
```
```bash
export TESTOBJECT_USERNAME=${USERNAME}
export TESTOBJECT_API_KEY=${ACCESS_KEY}
```

If you want to use this executor to launch sauce tunnel for you, set this env variable.
```bash
export TESTOBJECT_PASSWORD=${PASSWORD}
```

Optional env variable. If set, all traffic to TestObject, including TestObject api and selenium calls, will be going through it.
```
```bash
export TESTOBJECT_OUTBOUND_PROXY=http://${your.proxy}:${your.port}
```


4. `./node_modules/.bin/magellan --help` to see if you can see the following content printed out
```
Executor-specific (testarmada-magellan-testobject-executor)
--to_device=devicename String represents one device which TestObject supports
--to_devices=d1,d2,.. String represents multiple devices which TestObject supports
--to_list_devices List the available devices TestObject supports.
--to_create_tunnel Create and use sauce tunnel for testing
--to_tunnel_id Existing tunnel identifier for testing
--to_app_id=1 APP id of the uploaded app to TestObject
--to_platform_name=iOS String represents the mobile platform
--to_platform_version=10.2 String represents the mobile platform version
```

Congratulations, you're all set.

## Run your test with sauce tunnel
TestObject recently launched the beta program to run real device test with sauce tunnel. Find more info [here](https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy+and+Real+Device+Testing). You can tell this executor if you want it to manage sauce tunnel for you during test, or if you want to use an existing sauce tunnel.

1. launch sauce tunnel automatically

Simply set `TESTOBJECT_PASSWORD` env variable and add `--to_create_tunnel` to your command line. This executor will create a tunnel for you per magellan run, and automatically close it eventually.

2. use an existing sauce tunnel

Add `--to_tunnel_id ${TUNNEL_ID}` to your command line. This executor will add `${TUNNEL_ID}` to desiredCapabilities.

Please note: `--to_create_tunnel` and `--to_tunnel_id` cannot co-exist. Once executor founds them both from command line, `--to_create_tunnel` will be in use.


## Run your test in parallel
TestObject takes both generic device desiredCapability with device and platform information and specific device id. A specific device id is a string that TestObject uses as an unique identifier to represent a particular device. There are two ways to get device id

Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const executor = require("./lib/executor").default;
const executor = require("./lib/executor");
const configuration = require("./lib/configuration").default;
const profile = require("./lib/profile").default;
const help = require("./lib/help").default;
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "testarmada-magellan-testobject-executor",
"version": "1.3.1",
"version": "1.4.0",
"description": "magellan executor for testobject",
"main": "index.js",
"scripts": {
Expand All @@ -20,6 +20,7 @@
"cli-table": "^0.3.1",
"lodash": "^4.17.4",
"request": "^2.81.0",
"sauce-connect-launcher": "^1.2.2",
"testarmada-logger": "^1.1.1",
"yargs": "^6.6.0"
},
Expand All @@ -40,7 +41,8 @@
"keywords": [
"magellan",
"executor",
"testobject"
"testobject",
"saucelabs"
],
"author": "Lei Zhu <[email protected]>",
"license": "MIT",
Expand Down
46 changes: 44 additions & 2 deletions src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import _ from "lodash";
import logger from "testarmada-logger";
import settings from "./settings";

const TESTOBJECT_REST_URL = "https://us1.api.testobject.com/sc/rest/v1";
const RAND_MAX = 9999999999999999;
const STRNUM_BASE = 16;

const guid = () => Math.round(Math.random() * RAND_MAX).toString(STRNUM_BASE);

export default {
getConfig: () => {
logger.prefix = "TestObject Executor";
Expand Down Expand Up @@ -48,6 +54,27 @@ export default {
settings.config.testobjectOutboundProxy = env.TESTOBJECT_OUTBOUND_PROXY;
}

if (runArgv.to_create_tunnel) {
// if to_create_tunnel is in use
// required
settings.config.tunnel.username = settings.config.accessUser;

settings.config.tunnel.accessKey = env.TESTOBJECT_PASSWORD;
// optional
if (runArgv.to_password && !settings.config.tunnel.accessKey) {
// only accept argument from command line if env variable isn't set
settings.config.tunnel.accessKey = runArgv.to_password;
}

settings.config.tunnel.tunnelIdentifier = guid();
settings.config.tunnel.restUrl = TESTOBJECT_REST_URL;
settings.config.useTunnels = true;
} else if (runArgv.to_tunnel_id) {
// if tunnel id is passed in
settings.config.tunnel.tunnelIdentifier = runArgv.to_tunnel_id;
settings.config.tunnel.restUrl = TESTOBJECT_REST_URL;
}

settings.config.appID = runArgv.to_app_id;

const parameterWarnings = {
Expand All @@ -72,11 +99,11 @@ export default {
_.forEach(parameterWarnings, (v, k) => {
if (!settings.config[k]) {
if (v.required) {
logger.err(`Error! TestObject requires ${k} to be set. Check if the`
logger.err(`TestObject requires ${k} to be set. Check if the`
+ ` environment variable $${v.envKey} is defined.`);
valid = false;
} else {
logger.warn(`Warning! No ${k} is set. This is set via the`
logger.warn(`No ${k} is set. This is set via the`
+ ` environment variable $${v.envKey} . This isn't required, but can cause `
+ "problems with TestObject if not set");
}
Expand All @@ -99,6 +126,21 @@ export default {
+ "cannot co-exist in the arguments");
}

// validate tunnel configs
if (runArgv.to_create_tunnel) {
if (!settings.config.tunnel.accessKey) {
logger.err(`TestObject requires TESTOBJECT_PASSWORD to be set. Check if the`
+ ` environment variable TESTOBJECT_PASSWORD is defined.`);

throw new Error("Missing configuration for TestObject connection.");
}

if (runArgv.to_tunnel_id) {
logger.warn("--to_create_tunnel and --to_tunnel_id shouldn't be used together." +
" TestObject excutor will ignore --to_tunnel_id in this case.");
}
}

logger.debug("TestObject configuration: ");
logger.debug(JSON.stringify(settings.config));

Expand Down
81 changes: 74 additions & 7 deletions src/executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,85 @@ import request from "request";
import logger from "testarmada-logger";

import settings from "./settings";
import Tunnel from "./tunnel";

export default {
let config = settings.config;

let tunnel = null;

const Executor = {
/*eslint-disable no-unused-vars*/
setupRunner: (mocks = null) => {
return new Promise((resolve) => {
resolve();
});
return Executor
.setupTunnels(mocks);
},

setupTunnels: (mocks = null) => {
logger.prefix = "TestObject Executor";

let ITunnel = Tunnel;

if (mocks) {
if (mocks.Tunnel) {
ITunnel = mocks.Tunnel;
}
if (mocks.config) {
config = mocks.config;
}
}

if (config.useTunnels) {
// create new tunnel if needed
tunnel = new ITunnel(config);

return tunnel
.initialize()
.then(() => {
return tunnel.open();
})
.then(() => {
logger.log("Sauce tunnel is opened! Continuing...");
logger.log(`Assigned tunnel [${config.tunnel.tunnelIdentifier}] to all workers`);
})
.catch((err) => {
return new Promise((resolve, reject) => {
reject(err);
});
});
} else {
return new Promise((resolve) => {
if (config.tunnel.tunnelIdentifier) {
const tunnelAnnouncement = config.tunnel.tunnelIdentifier;

logger.log(`Connected to sauce tunnel [${tunnelAnnouncement}]`);
} else {
logger.log("Connected to TestObject without tunnel");
}
return resolve();
});
}
},

/*eslint-disable no-unused-vars*/
teardownRunner: (mocks = null) => {
return new Promise((resolve) => {
resolve();
});
logger.prefix = "TestObject Executor";

if (mocks && mocks.config) {
config = mocks.config;
}

// close tunnel if needed
if (tunnel && config.useTunnels) {
return tunnel
.close()
.then(() => {
logger.log("Sauce tunnel is closed! Continuing...");
});
} else {
return new Promise((resolve) => {
resolve();
});
}
},

setupTest: (callback) => {
Expand Down Expand Up @@ -98,3 +163,5 @@ export default {
}

};

module.exports = Executor;
16 changes: 16 additions & 0 deletions src/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ export default {
"example": "mikebrown",
"description": "Your TestObject username"
},
"to_password": {
"visible": false,
"type": "string",
"example": "sd9f81l",
"description": "Your TestObject password, for creating tunnel"
},
"to_create_tunnel": {
"visible": true,
"type": "boolean",
"description": "Create and use sauce tunnel for testing"
},
"to_tunnel_id": {
"visible": true,
"type": "string",
"description": "Existing tunnel identifier for testing"
},
"to_app_id": {
"visible": true,
"type": "string",
Expand Down
5 changes: 5 additions & 0 deletions src/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export default {
config.proxy = settings.config.testobjectOutboundProxy;
}

if (settings.config.tunnel.tunnelIdentifier) {
// we're told to use tunnel
config.desiredCapabilities.tunnelIdentifier = settings.config.tunnel.tunnelIdentifier;
}

logger.debug(`executor config: ${JSON.stringify(config)}`);
return config;
},
Expand Down
20 changes: 19 additions & 1 deletion src/settings.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
import { argv } from "yargs";
import path from "path";

const debug = argv.debug;
const API_DELAY = 20000;
const tempDir = path.resolve(argv.temp_dir || "./temp");

/*eslint-disable no-magic-numbers*/
const config = {
accessUser: null,
accessAPI: null,
appID: null,
testobjectOutboundProxy: null
testobjectOutboundProxy: null,

useTunnels: false,

tunnel: {
username: null,
accessKey: null,
restUrl: null,

// optional
tunnelIdentifier: null,
connectVersion: null
}
};

export default {
debug,
tempDir,
MAX_CONNECT_RETRIES: process.env.SAUCE_CONNECT_NUM_RETRIES || 10,
TESTOBJECT_API_DELAY: process.env.TESTOBJECT_API_DELAY || API_DELAY,
BASE_SELENIUM_PORT_OFFSET: 56000,
config
};
Loading