Skip to content
This repository was archived by the owner on Jan 19, 2021. It is now read-only.

Commit ceec7ee

Browse files
authored
Merge pull request #4 from TestArmada/tunnel
Allow to manage sauce tunnel
2 parents f45e26c + 3d2a632 commit ceec7ee

File tree

13 files changed

+685
-28
lines changed

13 files changed

+685
-28
lines changed

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[![Build Status](https://travis-ci.org/TestArmada/magellan-testobject-executor.svg?branch=master)](https://travis-ci.org/TestArmada/magellan-testobject-executor)
44
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
55
[![codecov](https://codecov.io/gh/TestArmada/magellan-testobject-executor/branch/master/graph/badge.svg)](https://codecov.io/gh/TestArmada/magellan-testobject-executor)
6+
[![Downloads](http://img.shields.io/npm/dm/testarmada-magellan-testobject-executor?style=flat)](https://npmjs.org/package/testarmada-magellan-testobject-executor)
67

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

@@ -11,6 +12,7 @@ Executor for [Magellan](https://github.com/TestArmada/magellan) to run [nightwat
1112
## What does this executor do
1213
1. It talks [Muffin]() so that the desiredCapabilities shrinks down to a string, which makes your browser selection an easy work
1314
2. It runs nightwatch test by forking it as magellan child process
15+
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.
1416

1517
## How To Use
1618
Please follow the steps
@@ -23,30 +25,50 @@ Please follow the steps
2325
]
2426
```
2527
3. set env variables
26-
```
28+
```bash
2729
export TESTOBJECT_USERNAME=${USERNAME}
2830
export TESTOBJECT_API_KEY=${ACCESS_KEY}
2931
```
3032

33+
If you want to use this executor to launch sauce tunnel for you, set this env variable.
34+
```bash
35+
export TESTOBJECT_PASSWORD=${PASSWORD}
36+
```
37+
3138
Optional env variable. If set, all traffic to TestObject, including TestObject api and selenium calls, will be going through it.
32-
```
39+
```bash
3340
export TESTOBJECT_OUTBOUND_PROXY=http://${your.proxy}:${your.port}
3441
```
3542

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

4856
Congratulations, you're all set.
4957

58+
## Run your test with sauce tunnel
59+
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.
60+
61+
1. launch sauce tunnel automatically
62+
63+
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.
64+
65+
2. use an existing sauce tunnel
66+
67+
Add `--to_tunnel_id ${TUNNEL_ID}` to your command line. This executor will add `${TUNNEL_ID}` to desiredCapabilities.
68+
69+
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.
70+
71+
5072
## Run your test in parallel
5173
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
5274

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const executor = require("./lib/executor").default;
1+
const executor = require("./lib/executor");
22
const configuration = require("./lib/configuration").default;
33
const profile = require("./lib/profile").default;
44
const help = require("./lib/help").default;

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "testarmada-magellan-testobject-executor",
3-
"version": "1.3.1",
3+
"version": "1.4.0",
44
"description": "magellan executor for testobject",
55
"main": "index.js",
66
"scripts": {
@@ -20,6 +20,7 @@
2020
"cli-table": "^0.3.1",
2121
"lodash": "^4.17.4",
2222
"request": "^2.81.0",
23+
"sauce-connect-launcher": "^1.2.2",
2324
"testarmada-logger": "^1.1.1",
2425
"yargs": "^6.6.0"
2526
},
@@ -40,7 +41,8 @@
4041
"keywords": [
4142
"magellan",
4243
"executor",
43-
"testobject"
44+
"testobject",
45+
"saucelabs"
4446
],
4547
"author": "Lei Zhu <[email protected]>",
4648
"license": "MIT",

src/configuration.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import _ from "lodash";
33
import logger from "testarmada-logger";
44
import settings from "./settings";
55

6+
const TESTOBJECT_REST_URL = "https://us1.api.testobject.com/sc/rest/v1";
7+
const RAND_MAX = 9999999999999999;
8+
const STRNUM_BASE = 16;
9+
10+
const guid = () => Math.round(Math.random() * RAND_MAX).toString(STRNUM_BASE);
11+
612
export default {
713
getConfig: () => {
814
logger.prefix = "TestObject Executor";
@@ -48,6 +54,27 @@ export default {
4854
settings.config.testobjectOutboundProxy = env.TESTOBJECT_OUTBOUND_PROXY;
4955
}
5056

57+
if (runArgv.to_create_tunnel) {
58+
// if to_create_tunnel is in use
59+
// required
60+
settings.config.tunnel.username = settings.config.accessUser;
61+
62+
settings.config.tunnel.accessKey = env.TESTOBJECT_PASSWORD;
63+
// optional
64+
if (runArgv.to_password && !settings.config.tunnel.accessKey) {
65+
// only accept argument from command line if env variable isn't set
66+
settings.config.tunnel.accessKey = runArgv.to_password;
67+
}
68+
69+
settings.config.tunnel.tunnelIdentifier = guid();
70+
settings.config.tunnel.restUrl = TESTOBJECT_REST_URL;
71+
settings.config.useTunnels = true;
72+
} else if (runArgv.to_tunnel_id) {
73+
// if tunnel id is passed in
74+
settings.config.tunnel.tunnelIdentifier = runArgv.to_tunnel_id;
75+
settings.config.tunnel.restUrl = TESTOBJECT_REST_URL;
76+
}
77+
5178
settings.config.appID = runArgv.to_app_id;
5279

5380
const parameterWarnings = {
@@ -72,11 +99,11 @@ export default {
7299
_.forEach(parameterWarnings, (v, k) => {
73100
if (!settings.config[k]) {
74101
if (v.required) {
75-
logger.err(`Error! TestObject requires ${k} to be set. Check if the`
102+
logger.err(`TestObject requires ${k} to be set. Check if the`
76103
+ ` environment variable $${v.envKey} is defined.`);
77104
valid = false;
78105
} else {
79-
logger.warn(`Warning! No ${k} is set. This is set via the`
106+
logger.warn(`No ${k} is set. This is set via the`
80107
+ ` environment variable $${v.envKey} . This isn't required, but can cause `
81108
+ "problems with TestObject if not set");
82109
}
@@ -99,6 +126,21 @@ export default {
99126
+ "cannot co-exist in the arguments");
100127
}
101128

129+
// validate tunnel configs
130+
if (runArgv.to_create_tunnel) {
131+
if (!settings.config.tunnel.accessKey) {
132+
logger.err(`TestObject requires TESTOBJECT_PASSWORD to be set. Check if the`
133+
+ ` environment variable TESTOBJECT_PASSWORD is defined.`);
134+
135+
throw new Error("Missing configuration for TestObject connection.");
136+
}
137+
138+
if (runArgv.to_tunnel_id) {
139+
logger.warn("--to_create_tunnel and --to_tunnel_id shouldn't be used together." +
140+
" TestObject excutor will ignore --to_tunnel_id in this case.");
141+
}
142+
}
143+
102144
logger.debug("TestObject configuration: ");
103145
logger.debug(JSON.stringify(settings.config));
104146

src/executor.js

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,85 @@ import request from "request";
33
import logger from "testarmada-logger";
44

55
import settings from "./settings";
6+
import Tunnel from "./tunnel";
67

7-
export default {
8+
let config = settings.config;
9+
10+
let tunnel = null;
11+
12+
const Executor = {
813
/*eslint-disable no-unused-vars*/
914
setupRunner: (mocks = null) => {
10-
return new Promise((resolve) => {
11-
resolve();
12-
});
15+
return Executor
16+
.setupTunnels(mocks);
17+
},
18+
19+
setupTunnels: (mocks = null) => {
20+
logger.prefix = "TestObject Executor";
21+
22+
let ITunnel = Tunnel;
23+
24+
if (mocks) {
25+
if (mocks.Tunnel) {
26+
ITunnel = mocks.Tunnel;
27+
}
28+
if (mocks.config) {
29+
config = mocks.config;
30+
}
31+
}
32+
33+
if (config.useTunnels) {
34+
// create new tunnel if needed
35+
tunnel = new ITunnel(config);
36+
37+
return tunnel
38+
.initialize()
39+
.then(() => {
40+
return tunnel.open();
41+
})
42+
.then(() => {
43+
logger.log("Sauce tunnel is opened! Continuing...");
44+
logger.log(`Assigned tunnel [${config.tunnel.tunnelIdentifier}] to all workers`);
45+
})
46+
.catch((err) => {
47+
return new Promise((resolve, reject) => {
48+
reject(err);
49+
});
50+
});
51+
} else {
52+
return new Promise((resolve) => {
53+
if (config.tunnel.tunnelIdentifier) {
54+
const tunnelAnnouncement = config.tunnel.tunnelIdentifier;
55+
56+
logger.log(`Connected to sauce tunnel [${tunnelAnnouncement}]`);
57+
} else {
58+
logger.log("Connected to TestObject without tunnel");
59+
}
60+
return resolve();
61+
});
62+
}
1363
},
1464

1565
/*eslint-disable no-unused-vars*/
1666
teardownRunner: (mocks = null) => {
17-
return new Promise((resolve) => {
18-
resolve();
19-
});
67+
logger.prefix = "TestObject Executor";
68+
69+
if (mocks && mocks.config) {
70+
config = mocks.config;
71+
}
72+
73+
// close tunnel if needed
74+
if (tunnel && config.useTunnels) {
75+
return tunnel
76+
.close()
77+
.then(() => {
78+
logger.log("Sauce tunnel is closed! Continuing...");
79+
});
80+
} else {
81+
return new Promise((resolve) => {
82+
resolve();
83+
});
84+
}
2085
},
2186

2287
setupTest: (callback) => {
@@ -98,3 +163,5 @@ export default {
98163
}
99164

100165
};
166+
167+
module.exports = Executor;

src/help.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ export default {
2828
"example": "mikebrown",
2929
"description": "Your TestObject username"
3030
},
31+
"to_password": {
32+
"visible": false,
33+
"type": "string",
34+
"example": "sd9f81l",
35+
"description": "Your TestObject password, for creating tunnel"
36+
},
37+
"to_create_tunnel": {
38+
"visible": true,
39+
"type": "boolean",
40+
"description": "Create and use sauce tunnel for testing"
41+
},
42+
"to_tunnel_id": {
43+
"visible": true,
44+
"type": "string",
45+
"description": "Existing tunnel identifier for testing"
46+
},
3147
"to_app_id": {
3248
"visible": true,
3349
"type": "string",

src/profile.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export default {
2323
config.proxy = settings.config.testobjectOutboundProxy;
2424
}
2525

26+
if (settings.config.tunnel.tunnelIdentifier) {
27+
// we're told to use tunnel
28+
config.desiredCapabilities.tunnelIdentifier = settings.config.tunnel.tunnelIdentifier;
29+
}
30+
2631
logger.debug(`executor config: ${JSON.stringify(config)}`);
2732
return config;
2833
},

src/settings.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
11
import { argv } from "yargs";
2+
import path from "path";
23

34
const debug = argv.debug;
45
const API_DELAY = 20000;
6+
const tempDir = path.resolve(argv.temp_dir || "./temp");
57

8+
/*eslint-disable no-magic-numbers*/
69
const config = {
710
accessUser: null,
811
accessAPI: null,
912
appID: null,
10-
testobjectOutboundProxy: null
13+
testobjectOutboundProxy: null,
14+
15+
useTunnels: false,
16+
17+
tunnel: {
18+
username: null,
19+
accessKey: null,
20+
restUrl: null,
21+
22+
// optional
23+
tunnelIdentifier: null,
24+
connectVersion: null
25+
}
1126
};
1227

1328
export default {
1429
debug,
30+
tempDir,
31+
MAX_CONNECT_RETRIES: process.env.SAUCE_CONNECT_NUM_RETRIES || 10,
1532
TESTOBJECT_API_DELAY: process.env.TESTOBJECT_API_DELAY || API_DELAY,
33+
BASE_SELENIUM_PORT_OFFSET: 56000,
1634
config
1735
};

0 commit comments

Comments
 (0)