@tremho/jove-test
Version:
Test API Module for Jove Framework
259 lines (258 loc) • 11 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.wait = exports.askAHuman = exports.remoteTitle = exports.compare = exports.screenshot = exports.runRemoteTest = exports.endTest = exports.startTest = exports.callRemote = exports.testRemote = void 0;
const tap_1 = __importDefault(require("tap"));
const WSServer_1 = require("./WSServer");
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const imageComp_1 = require("./imageComp");
let stream;
let desc, r, x;
let runcount = 0;
let previous;
let prevResolve;
/**
* Transact a single remote test action at the connected app client
*
* @param {TAP} t The tap instance passed into test execution function from `runRemoteTest`, or null to skip tap
* @param {string} action The directive to perform
* @param {string} description A description of this test action
* @param {string} expected The expected return result of this test action
*
* @return {boolean} the result of the test result matching expected; may be ignored.
*/
function testRemote(t, action, description, expected) {
return __awaiter(this, void 0, void 0, function* () {
desc = description;
x = expected;
r = yield stream.sendDirective(action);
if (typeof r === 'string' && typeof x !== 'string')
x = '' + x;
const ok = r === x;
if (t)
t.ok(ok, desc + ` expected ${x}, got ${r}`);
return ok;
});
}
exports.testRemote = testRemote;
/**
* similar to `testRemote`, but simply calls the action and returns the result without submitting to test
*
* @param {string} action The directive to perform
* @returns {string} The JSON result of the action
*/
function callRemote(action) {
return __awaiter(this, void 0, void 0, function* () {
// console.log('callRemote', action)
let r = yield stream.sendDirective(action);
if (typeof r === 'string') {
try {
r = JSON.parse(r);
}
catch (e) { }
}
return r;
});
}
exports.callRemote = callRemote;
/**
* Should be called at the top of a test suite, but it's just for symmetry with `endTest`
* In this version, nothing is actually sent to the server.
* @param {TAP} t The tap instance, if using tap
*
* return {boolean} true if stream is ready.
*/
function startTest(t = null) {
return __awaiter(this, void 0, void 0, function* () {
// console.log("%%%%%%%%%%%%%% startTest directive called %%%%%%%%%%%%%%%")
desc = 'stream connect';
r = !!stream;
x = true;
const ok = r === x;
if (t)
t.ok(ok, desc + (ok ? ' successful' : ' FAILED'));
return ok;
});
}
exports.startTest = startTest;
/**
* Call when all the tests are complete. The client will continue with its disposition after that, either exiting, or
* continuing to run. This should be the end of any remote testing, regardless.
* @param {TAP} t The tap instance, if using tap. Will signal the end on this tap instance.
*/
function endTest(t = null) {
return __awaiter(this, void 0, void 0, function* () {
// console.log('endTest called', prevResolve)
if (t)
t.end();
let report = yield stream.sendDirective('getReport');
report = report.replace(/--/g, '=');
saveReport(report);
return stream.sendDirective('end');
});
}
exports.endTest = endTest;
/**
* Initiate the connected test. Pass the title of the test and the async function that conducts the test suite
*
* At this point the client has been launched, possibly under appium
*
* @param {string} title Title of this test that will appear on the report
* @param {Function} testFunc The function from the test script that conducts the test with `startTest` then a series of `testRemote` directives, then an `endTest`
*/
function runRemoteTest(title, testFunc) {
return __awaiter(this, void 0, void 0, function* () {
stream = new WSServer_1.WSServer();
let cf = yield stream.listen();
(0, WSServer_1.setEndResolver)(() => {
process.exit(0);
});
yield stream.sendDirective('startReport ' + title);
return tap_1.default.test('Remote E2E: ' + title, (t) => {
if (cf)
testFunc(t);
else {
t.ok(false, 'Only one Remote Test in a test suite is allowed.');
}
});
});
}
exports.runRemoteTest = runRemoteTest;
/**
* Takes a screenshot of the current page
*
* Image will be saved as a PNG in the appropriate report directory
*
* @param {string} name Name to give this image
*/
function screenshot(t, name) {
return __awaiter(this, void 0, void 0, function* () {
// console.log('jove-test is issuing a screenshot call...')
const ssrt = yield stream.sendDirective('screenshot ' + name);
if (ssrt.substring(0, 4) === 'data') {
// console.log('we see a base 64 return of', ssrt.substring(0,10)+'...', 'that we could write to a file for', name)
const rootPath = path_1.default.resolve('.');
if (fs_1.default.existsSync(path_1.default.join(rootPath, 'report', 'latest'))) {
const rptImgPath = path_1.default.join(rootPath, 'report', 'latest', 'images');
fs_1.default.mkdirSync(rptImgPath, { recursive: true });
const imgPath = path_1.default.join(rptImgPath, name + '.png');
const b64 = ssrt.substring(ssrt.indexOf(',') + 1);
fs_1.default.writeFileSync(imgPath, b64, "base64");
t.ok(true, 'screenshot ' + name + ' taken');
// console.log('image saved as', fs.realpathSync(imgPath))
// console.log('verified: ', fs.existsSync(imgPath))
return imgPath;
}
else {
t.ok(false, 'screenshot ' + name + ' fails - invalid root directory');
console.error('rootPath is not recognized', rootPath);
return "ERR:Bad-rootPath";
}
}
t.ok(false, 'screenshot ' + name + ' fails - data return not recognized');
console.error('data return not recognized', ssrt.substring(0, 5));
return "ERR:Not-Base64";
});
}
exports.screenshot = screenshot;
/**
* Compare a screenshot taken with `screenshot` to a comp file
* in the `reports/comp` directory of the same name
* @param {TAP} t the Tap object to report through , or null if not using
* @param {string} name Name of the screenshot / comp image
* @param [passingPct] Percentage of pixels that can be different and still pass (default = 0)
*/
function compare(t, name, passingPct = 0) {
return __awaiter(this, void 0, void 0, function* () {
// console.log('test: compare --->>')
const data = yield (0, imageComp_1.compareToComp)(name + ".png", passingPct);
// console.log('data returned', data)
let ok = data && data.ok;
let message = (ok ? 'image matches' : data.error || 'image does not match' + ` (${data.percentDiff}% difference)`);
if (t)
t.ok(ok, 'compare ' + name + ': ' + message);
if (!ok) {
let res = `${name},${data.percentDiff},${data.error || ''}`;
yield callRemote('compareReport ' + res);
}
return data;
});
}
exports.compare = compare;
/**
* Assigns a title for this test that will appear on test report.
* @param {TAP} [t] The TAP object, if using and want report on this operation
* @param {string} title Title of this test series
*/
function remoteTitle(t, title) {
return __awaiter(this, void 0, void 0, function* () {
yield callRemote('remoteTitle ' + title.replace(/ /g, '+'));
if (t) {
t.ok(true, '>remoteTitle: ' + title);
}
});
}
exports.remoteTitle = remoteTitle;
/**
* Prsent a prompt dialog to the user asking a question with given button choices, and the expected response for all OK.
* Test passes if response is the expected response, or if there is a timeout.
* If no response is made within the timeout period (30 seconds default), the dialog is dismissed and the test passses.
*
* @param {TAP} t The TAP object
* @param {string} prompt prompt to the user
* @param {string} choices comma-delimited set of choice buttons to present
* @param {string} expect the response expected for passing
* @param {number} [timeoutSeconds] Seconds until timeout (default is 30)
*/
function askAHuman(t, prompt, choices, expect, timeoutSeconds = 30) {
return __awaiter(this, void 0, void 0, function* () {
console.log(">>> Ask a Human");
let px = prompt.replace(/\+/g, '%plus%');
px = px.replace(/ /g, '+');
let resp = yield callRemote('askAHuman ' + px + ' ' + choices + ' ' + expect + ' ' + timeoutSeconds);
console.log(' response is ', resp);
if (resp === 'undefined')
resp = undefined;
let exprt = resp ? (resp === expect) ? ` [${resp}]` : ` [${resp}, expected ${expect}]`
: ` [timed out]`;
t.ok(resp === expect || resp === undefined, 'askAHuman: ' + prompt + exprt);
});
}
exports.askAHuman = askAHuman;
/**
* Waits for the given time, in milliseconds
* @param millis
*/
function wait(millis) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(resolve => { setTimeout(resolve, millis); });
});
}
exports.wait = wait;
function saveReport(report) {
const rootPath = path_1.default.resolve('.');
// console.log("TEST REPORT ROOT PATH", rootPath)
if (fs_1.default.existsSync(path_1.default.join(rootPath, 'package.json'))) {
const dtf = "current";
const folderPath = path_1.default.join(rootPath, 'report', 'latest');
// fs.mkdirSync(folderPath, {recursive:true})
const rptPath = path_1.default.join(folderPath, 'report.html');
// console.log("TEST REPORT PATH", rptPath)
fs_1.default.writeFileSync(rptPath, report);
}
else {
console.error('TEST REPORT: Root path not detected at ', rootPath);
}
}