-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0dcf2cd
commit e5ed3b0
Showing
7 changed files
with
613 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Command } from 'commander'; | ||
import logger from './logger'; | ||
import auth from './auth'; | ||
import Espresso from './providers/espresso'; | ||
import EspressoOptions from './models/espresso_options'; | ||
import XCUITestOptions from './models/xcuitest_options'; | ||
import XCUITest from './providers/xcuitest'; | ||
import packageJson from '../package.json'; | ||
import MaestroOptions from './models/maestro_options'; | ||
import Maestro from './providers/maestro'; | ||
|
||
const program = new Command(); | ||
|
||
program | ||
.version(packageJson.version) | ||
.description( | ||
'TestingBotCTL is a CLI-tool to run Espresso, XCUITest and Maestro tests in the TestingBot cloud', | ||
); | ||
|
||
program | ||
.command('espresso') | ||
.description('Bootstrap an Espresso project.') | ||
.requiredOption('--app <string>', 'Path to application under test.') | ||
.requiredOption('--device <device>', 'Real device to use for testing.') | ||
.requiredOption( | ||
'--emulator <emulator>', | ||
'Android emulator to use for testing.', | ||
) | ||
.requiredOption('--test-app <string>', 'Path to test application.') | ||
.action(async (args) => { | ||
try { | ||
const options = new EspressoOptions( | ||
args.app, | ||
args.testApp, | ||
args.device, | ||
args.emulator, | ||
); | ||
const credentials = await auth.getCredentials(); | ||
if (credentials === null) { | ||
throw new Error('Please specify credentials'); | ||
} | ||
const espresso = new Espresso(credentials, options); | ||
await espresso.run(); | ||
} catch (err: any) { | ||
logger.error(`Espresso error: ${err.message}`); | ||
} | ||
}) | ||
.showHelpAfterError(true); | ||
|
||
program | ||
.command('maestro') | ||
.description('Bootstrap a Maestro project.') | ||
.requiredOption('--app <string>', 'Path to application under test.') | ||
.requiredOption( | ||
'--device <device>', | ||
'Android emulator or iOS Simulator to use for testing.', | ||
) | ||
.requiredOption('--test-app <string>', 'Path to test application.') | ||
.action(async (args) => { | ||
try { | ||
const options = new MaestroOptions( | ||
args.app, | ||
args.testApp, | ||
args.device, | ||
args.emulator, | ||
); | ||
const credentials = await auth.getCredentials(); | ||
if (credentials === null) { | ||
throw new Error('Please specify credentials'); | ||
} | ||
const maestto = new Maestro(credentials, options); | ||
await maestto.run(); | ||
} catch (err: any) { | ||
logger.error(`Maestro error: ${err.message}`); | ||
} | ||
}) | ||
.showHelpAfterError(true); | ||
|
||
program | ||
.command('xcuitest') | ||
.description('Bootstrap an XCUITest project.') | ||
.requiredOption('--app <string>', 'Path to application under test.') | ||
.requiredOption('--device <device>', 'Real device to use for testing.') | ||
.requiredOption('--test-app <string>', 'Path to test application.') | ||
.action(async (args) => { | ||
try { | ||
const options = new XCUITestOptions(args.app, args.testApp, args.device); | ||
const credentials = await auth.getCredentials(); | ||
if (credentials === null) { | ||
throw new Error('Please specify credentials'); | ||
} | ||
const xcuitest = new XCUITest(credentials, options); | ||
await xcuitest.run(); | ||
} catch (err: any) { | ||
logger.error(`XCUITest error: ${err.message}`); | ||
} | ||
}); | ||
|
||
export default program; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,74 +1,2 @@ | ||
import { Command } from 'commander'; | ||
import logger from './logger'; | ||
import auth from './auth'; | ||
import Espresso from './providers/espresso'; | ||
import EspressoOptions from './models/espresso_options'; | ||
import XCUITestOptions from './models/xcuitest_options'; | ||
import XCUITest from './providers/xcuitest'; | ||
import packageJson from '../package.json'; | ||
|
||
const program = new Command(); | ||
|
||
program | ||
.version(packageJson.version) | ||
.description( | ||
'TestingBotCTL is a CLI-tool to run Espresso, XCUITest and Maestro tests in the TestingBot cloud', | ||
); | ||
|
||
program | ||
.command('espresso') | ||
.description('Bootstrap an Espresso project.') | ||
.requiredOption('--app <string>', 'Path to application under test.') | ||
.requiredOption('--device <device>', 'Real device to use for testing.') | ||
.requiredOption( | ||
'--emulator <emulator>', | ||
'Android emulator to use for testing.', | ||
) | ||
.requiredOption('--test-app <string>', 'Path to test application.') | ||
.action(async (args) => { | ||
try { | ||
const options = new EspressoOptions( | ||
args.app, | ||
args.testApp, | ||
args.device, | ||
args.emulator, | ||
); | ||
const credentials = await auth.getCredentials(); | ||
if (credentials === null) { | ||
throw new Error('Please specify credentials'); | ||
} | ||
const espresso = new Espresso(credentials, options); | ||
await espresso.run(); | ||
} catch (err: any) { | ||
logger.error(`Espresso error: ${err.message}`); | ||
process.exit(1); | ||
} | ||
}) | ||
.showHelpAfterError(true); | ||
|
||
program | ||
.command('xcuitest') | ||
.description('Bootstrap an XCUITest project.') | ||
.requiredOption('--app <string>', 'Path to application under test.') | ||
.requiredOption('--device <device>', 'Real device to use for testing.') | ||
.requiredOption('--test-app <string>', 'Path to test application.') | ||
.action(async (args) => { | ||
try { | ||
const options = new XCUITestOptions(args.app, args.testApp, args.device); | ||
const credentials = await auth.getCredentials(); | ||
if (credentials === null) { | ||
throw new Error('Please specify credentials'); | ||
} | ||
const xcuitest = new XCUITest(credentials, options); | ||
await xcuitest.run(); | ||
} catch (err: any) { | ||
logger.error(`XCUITest error: ${err.message}`); | ||
process.exit(1); | ||
} | ||
}); | ||
|
||
import program from './cli'; | ||
program.parse(process.argv); | ||
|
||
auth.getCredentials().then((credentials: any) => { | ||
logger.info(credentials.toString()); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
export default class MaestroOptions { | ||
private _app: string; | ||
private _testApp: string; | ||
private _device: string; | ||
private _emulator: string; | ||
|
||
public constructor( | ||
app: string, | ||
testApp: string, | ||
device: string, | ||
emulator: string, | ||
) { | ||
this._app = app; | ||
this._testApp = testApp; | ||
this._device = device; | ||
this._emulator = emulator; | ||
} | ||
|
||
public get app(): string { | ||
return this._app; | ||
} | ||
|
||
public get testApp(): string { | ||
return this._testApp; | ||
} | ||
|
||
public get device(): string { | ||
return this._device; | ||
} | ||
|
||
public get emulator(): string { | ||
return this._emulator; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import MaestroOptions from '../models/maestro_options'; | ||
import logger from '../logger'; | ||
import Credentials from '../models/credentials'; | ||
import axios from 'axios'; | ||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import FormData from 'form-data'; | ||
import TestingBotError from '../models/testingbot_error'; | ||
import utils from '../utils'; | ||
|
||
export default class Maestro { | ||
private readonly URL = 'https://api.testingbot.com/v1/app-automate/maestro'; | ||
private credentials: Credentials; | ||
private options: MaestroOptions; | ||
|
||
private appId: number | undefined = undefined; | ||
|
||
public constructor(credentials: Credentials, options: MaestroOptions) { | ||
this.credentials = credentials; | ||
this.options = options; | ||
} | ||
|
||
private async validate(): Promise<boolean> { | ||
if (this.options.app === undefined) { | ||
throw new TestingBotError(`app option is required`); | ||
} | ||
|
||
try { | ||
await fs.promises.access(this.options.app, fs.constants.R_OK); | ||
} catch (err) { | ||
throw new TestingBotError( | ||
`Provided app path does not exist ${this.options.app}`, | ||
); | ||
} | ||
|
||
if (this.options.testApp === undefined) { | ||
throw new TestingBotError(`testApp option is required`); | ||
} | ||
|
||
try { | ||
await fs.promises.access(this.options.testApp, fs.constants.R_OK); | ||
} catch (err) { | ||
throw new TestingBotError( | ||
`testApp path does not exist ${this.options.testApp}`, | ||
); | ||
} | ||
|
||
if ( | ||
this.options.device === undefined && | ||
this.options.emulator === undefined | ||
) { | ||
throw new TestingBotError(`Please specify either a device or emulator`); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
public async run() { | ||
if (!(await this.validate())) { | ||
return; | ||
} | ||
try { | ||
logger.info('Uploading Maestro App'); | ||
await this.uploadApp(); | ||
|
||
logger.info('Uploading Maestro Test App'); | ||
await this.uploadTestApp(); | ||
|
||
logger.info('Running Maestro Tests'); | ||
await this.runTests(); | ||
} catch (error: any) { | ||
logger.error(error.message); | ||
} | ||
} | ||
|
||
private async uploadApp() { | ||
const fileName = path.basename(this.options.app); | ||
const fileStream = fs.createReadStream(this.options.app); | ||
|
||
const formData = new FormData(); | ||
formData.append('file', fileStream); | ||
const response = await axios.post(`${this.URL}/app`, formData, { | ||
headers: { | ||
'Content-Type': 'application/vnd.android.package-archive', | ||
'Content-Disposition': `attachment; filename=${fileName}`, | ||
'User-Agent': utils.getUserAgent(), | ||
}, | ||
auth: { | ||
username: this.credentials.userName, | ||
password: this.credentials.accessKey, | ||
}, | ||
}); | ||
|
||
const result = response.data; | ||
if (result.id) { | ||
this.appId = result.id; | ||
} else { | ||
throw new TestingBotError(`Uploading app failed: ${result.error}`); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private async uploadTestApp() { | ||
const fileName = path.basename(this.options.testApp); | ||
const fileStream = fs.createReadStream(this.options.testApp); | ||
|
||
const formData = new FormData(); | ||
formData.append('file', fileStream); | ||
const response = await axios.post( | ||
`${this.URL}/${this.appId}/tests`, | ||
formData, | ||
{ | ||
headers: { | ||
'Content-Type': 'application/zip,', | ||
'Content-Disposition': `attachment; filename=${fileName}`, | ||
'User-Agent': utils.getUserAgent(), | ||
}, | ||
auth: { | ||
username: this.credentials.userName, | ||
password: this.credentials.accessKey, | ||
}, | ||
}, | ||
); | ||
|
||
const result = response.data; | ||
if (!result.id) { | ||
throw new TestingBotError(`Uploading test app failed: ${result.error}`); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private async runTests() { | ||
try { | ||
const response = await axios.post( | ||
`${this.URL}/${this.appId}/run`, | ||
{ | ||
capabilities: [ | ||
{ | ||
deviceName: this.options.emulator, | ||
}, | ||
], | ||
}, | ||
{ | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'User-Agent': utils.getUserAgent(), | ||
}, | ||
auth: { | ||
username: this.credentials.userName, | ||
password: this.credentials.accessKey, | ||
}, | ||
}, | ||
); | ||
|
||
const result = response.data; | ||
if (result.success === false) { | ||
throw new TestingBotError(`Running Maestro test failed`, { | ||
cause: result.error, | ||
}); | ||
} | ||
|
||
return true; | ||
} catch (error) { | ||
throw new TestingBotError(`Running Maestro test failed`, { | ||
cause: error, | ||
}); | ||
} | ||
} | ||
} |
Oops, something went wrong.