Skip to content

Commit e5559cd

Browse files
committed
improvements
1 parent 5a82700 commit e5559cd

File tree

10 files changed

+2110
-29
lines changed

10 files changed

+2110
-29
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ module.exports = {
1010
rules: {
1111
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
1212
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
13+
"@typescript-eslint/no-var-requires": "off",
14+
"@typescript-eslint/no-explicit-any": "off"
1315
}
1416
};

package-lock.json

Lines changed: 1906 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
},
2929
"dependencies": {
3030
"archiver": "^5.0.0",
31+
"chalk": "^4.1.0",
32+
"ora": "^5.0.0",
3133
"request": "^2.88.2",
34+
"testingbot-tunnel-launcher": "^1.1.7",
3235
"tracer": "^1.1.3",
3336
"yargs": "^15.4.1"
3437
}

src/commands/run.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import log from '../log';
22
import Archiver from '../utils/archiver';
33
import Uploader from '../utils/uploader';
4+
import Poller from '../utils/poller';
45
import Config from '../utils/config';
6+
import Tunnel from '../utils/tunnel';
7+
import ora from 'ora';
58

69
interface Arguments {
710
[x: string]: unknown;
@@ -11,6 +14,8 @@ interface Arguments {
1114
export default class RunProject {
1215
private archiver: Archiver | undefined = undefined;
1316
private uploader: Uploader | undefined = undefined;
17+
private poller: Poller | undefined = undefined;
18+
private tunnel: Tunnel | undefined = undefined;
1419

1520
constructor(argv: Arguments) {
1621
try {
@@ -33,13 +38,21 @@ export default class RunProject {
3338

3439
this.archiver = new Archiver(config);
3540
this.uploader = new Uploader(config);
41+
this.poller = new Poller(config);
42+
this.tunnel = new Tunnel(config);
3643

3744
let zipFile: string;
3845

3946
if (!this.archiver || !this.uploader) {
4047
throw new Error(`Invalid state, please try again`);
4148
}
4249

50+
if (config.run_settings.start_tunnel) {
51+
const tunnelSpinner = ora('Starting TestingBot Tunnel').start();
52+
await this.tunnel.start();
53+
tunnelSpinner.succeed('TestingBot Tunnel Ready');
54+
}
55+
4356
try {
4457
zipFile = await this.archiver.start();
4558
} catch (err) {
@@ -48,10 +61,19 @@ export default class RunProject {
4861
}
4962

5063
try {
51-
const success = await this.uploader.start(zipFile);
52-
log.info('got success', success);
64+
const uploadSpinner = ora('Starting Project on TestingBot').start();
65+
66+
const response = await this.uploader.start(zipFile);
67+
uploadSpinner.succeed('Cypress is now running on TestingBot')
68+
69+
const poller = await this.poller.check(response.id)
70+
log.info(poller)
71+
5372
} catch (err) {
5473
log.error(err);
74+
if (config.run_settings.start_tunnel) {
75+
await this.tunnel.stop();
76+
}
5577
return;
5678
}
5779
}

src/templates/config.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,8 @@ export default (): string => {
77
browsers: [
88
{
99
browserName: 'chrome',
10-
platform: 'Windows 10',
11-
versions: ['78', '77'],
12-
},
13-
{
14-
browserName: 'firefox',
15-
os: 'Mojave',
16-
versions: ['74', '75'],
10+
platform: 'MOJAVE',
11+
version: 83
1712
},
1813
],
1914
run_settings: {
@@ -24,6 +19,8 @@ export default (): string => {
2419
parallel_count: 'How many tests you want to run in parallel',
2520
npm_dependencies: {},
2621
package_config_options: {},
22+
start_tunnel: true,
23+
local_ports: []
2724
},
2825
};
2926

src/utils/archiver.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,19 @@ export default class Archiver {
5252
'ts',
5353
'feature',
5454
'features',
55+
'pdf',
56+
'jpg',
57+
'jpeg',
58+
'png',
59+
'zip'
5560
];
5661
allowedFileTypes.forEach((fileType) => {
5762
archive.glob(`**/*.${fileType}`, {
5863
cwd: this.config.run_settings.cypress_project_dir,
5964
matchBase: true,
6065
ignore: [
61-
'node_modules/**',
66+
'**/node_modules/**',
67+
'./node_modules/**',
6268
'package-lock.json',
6369
'package.json',
6470
'testingbot-package.json',

src/utils/config.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,27 @@ interface IAuth {
77
secret: string;
88
}
99

10-
interface IBrowser {
10+
export interface ICapability {
1111
browserName: string;
1212
platform: string;
13-
versions: string[];
13+
version: string | number;
14+
localHttpPorts?: number[];
1415
}
1516

1617
interface IRunSettings {
17-
cypress_project_dir: string;
18-
project_name: string;
19-
build_name: string;
20-
parallel_count: number;
21-
npm_dependencies: any;
22-
package_config_options: any;
18+
cypress_project_dir: string
19+
project_name: string
20+
build_name: string
21+
parallel_count: number
22+
npm_dependencies: any
23+
package_config_options: any
24+
start_tunnel: boolean
25+
local_ports: number[]
2326
}
2427

2528
export interface IConfig {
2629
auth: IAuth;
27-
browsers: IBrowser[];
30+
browsers: ICapability[];
2831
run_settings: IRunSettings;
2932
}
3033

src/utils/poller.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import request from 'request';
2+
import log from './../log';
3+
import { IConfig } from './config';
4+
5+
interface IPollResponse {
6+
status: string
7+
errors: string[]
8+
}
9+
10+
export default class Poller {
11+
private config: IConfig;
12+
private retryNumber = 0;
13+
private intervalId: NodeJS.Timeout | undefined;
14+
private static readonly MAX_RETRIES_WAITING = 60;
15+
private static readonly MAX_RETRIES_READY = 600;
16+
17+
constructor(config: IConfig) {
18+
this.config = config;
19+
}
20+
21+
public async check(id: number): Promise<IPollResponse> {
22+
return new Promise((resolve, reject) => {
23+
this.intervalId = setInterval(async () => {
24+
const response = await this.getStatus(id);
25+
log.info('checking', response);
26+
27+
if (response.status === 'DONE') {
28+
if (response.errors.length === 0) {
29+
if (this.intervalId) {
30+
clearInterval(this.intervalId);
31+
this.intervalId = undefined;
32+
}
33+
return resolve(response);
34+
}
35+
36+
if (this.intervalId) {
37+
clearInterval(this.intervalId);
38+
this.intervalId = undefined;
39+
}
40+
return reject(response);
41+
}
42+
43+
if (response.status === 'WAITING' && this.retryNumber > Poller.MAX_RETRIES_WAITING) {
44+
if (this.intervalId) {
45+
clearInterval(this.intervalId);
46+
this.intervalId = undefined;
47+
}
48+
return reject(new Error(`Waited too long to retrieve information, please try again later.`));
49+
} else if (response.status === 'READY' && this.retryNumber > Poller.MAX_RETRIES_READY) {
50+
if (this.intervalId) {
51+
clearInterval(this.intervalId);
52+
this.intervalId = undefined;
53+
}
54+
return reject(new Error(`This project has been running for over 20 minutes, stopping now.`));
55+
}
56+
57+
this.retryNumber += 1;
58+
}, 2000);
59+
});
60+
}
61+
62+
private async getStatus(id: number): Promise<IPollResponse> {
63+
return new Promise((resolve, reject) => {
64+
const requestOptions = {
65+
method: 'GET',
66+
uri: `https://api.testingbot.com/v1/cypress/${id}`,
67+
auth: {
68+
user: this.config.auth.key,
69+
pass: this.config.auth.secret,
70+
sendImmediately: true,
71+
}
72+
};
73+
74+
request(requestOptions, (error, response) => {
75+
if (error) {
76+
return reject(error);
77+
}
78+
let responseBody: IPollResponse = { status: 'WAITING', errors: [] };
79+
if (response) {
80+
if (response.body && typeof response.body === 'string') {
81+
response.body = JSON.parse(response.body) as IPollResponse;
82+
}
83+
if (response.statusCode.toString().substring(0, 1) === '2') {
84+
responseBody = response.body;
85+
} else {
86+
return reject(response.body);
87+
}
88+
}
89+
90+
resolve(responseBody);
91+
});
92+
});
93+
}
94+
}

src/utils/tunnel.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { IConfig } from "./config";
2+
const testingbotTunnel = require('testingbot-tunnel-launcher');
3+
4+
export default class Tunnel {
5+
private tunnel: any;
6+
7+
private config: IConfig;
8+
9+
constructor(config: IConfig) {
10+
this.config = config;
11+
}
12+
13+
public async start(): Promise<void> {
14+
return new Promise((resolve, reject) => {
15+
testingbotTunnel({
16+
apiKey: this.config.auth.key,
17+
apiSecret: this.config.auth.secret,
18+
}, (err: any, tunnel: any) => {
19+
if (err) {
20+
reject(err);
21+
return;
22+
}
23+
24+
this.tunnel = tunnel;
25+
resolve(tunnel);
26+
});
27+
});
28+
}
29+
30+
public async stop(): Promise<void> {
31+
return new Promise((resolve, reject) => {
32+
if (this.tunnel === null) {
33+
reject(new Error(`Tunnel not active`));
34+
return;
35+
}
36+
37+
this.tunnel.close(() => {
38+
this.tunnel = null;
39+
resolve();
40+
});
41+
});
42+
}
43+
}

src/utils/uploader.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import fs from 'fs';
22
import request from 'request';
3-
import log from './../log';
4-
import { IConfig } from './config';
3+
import { IConfig, ICapability } from './config';
4+
5+
interface IResponse {
6+
id: number
7+
}
58

69
export default class Uploader {
710
private config: IConfig;
@@ -10,8 +13,14 @@ export default class Uploader {
1013
this.config = config;
1114
}
1215

13-
public async start(zipFile: string): Promise<string> {
16+
public async start(zipFile: string): Promise<IResponse> {
1417
return new Promise((resolve, reject) => {
18+
const capabilities = this.config.browsers;
19+
if (this.config.run_settings.local_ports.length > 0) {
20+
capabilities.map((capability: ICapability) => {
21+
capability.localHttpPorts = this.config.run_settings.local_ports
22+
})
23+
}
1524
const requestOptions = {
1625
method: 'POST',
1726
uri: `https://api.testingbot.com/v1/cypress`,
@@ -22,22 +31,18 @@ export default class Uploader {
2231
},
2332
formData: {
2433
file: fs.createReadStream(zipFile),
25-
capabilities: JSON.stringify([{
26-
platform: 'MOJAVE',
27-
browserName: 'chrome',
28-
version: '82'
29-
}])
34+
capabilities: JSON.stringify(capabilities)
3035
},
3136
};
3237

3338
request(requestOptions, function (error, response) {
3439
if (error) {
3540
return reject(error);
3641
}
37-
let responseBody = null;
42+
let responseBody: IResponse = { id: 0 };
3843
if (response) {
3944
if (response.body && typeof response.body === 'string') {
40-
response.body = JSON.parse(response.body);
45+
response.body = JSON.parse(response.body) as IResponse;
4146
}
4247
if (response.statusCode.toString().substring(0, 1) === '2') {
4348
responseBody = response.body;

0 commit comments

Comments
 (0)