@assurka/cypress
Version:
Assurka Cypress Plugin
284 lines (254 loc) • 8.44 kB
text/typescript
/// <reference types="cypress" />
/**
* @type {Cypress.PluginConfig}
*/
import fs from "fs-extra";
import * as path from "path";
import del from "del";
import _ from "lodash";
import fetch, { Headers } from "node-fetch";
import { getLastCommit } from "git-last-commit";
import FormData from "form-data";
export interface AssurkaConfig {
projectId: string;
secret: string;
token?: string;
testSpecs?: any[];
testRun?: any;
testPlanId: any;
testPlan?: any;
testPlanParams?: any;
host?: string;
port?: number;
}
export const assurka = (on, config, assurkaConfig: AssurkaConfig) => {
// console.log("config", config);
on("before:run", async (details) => {
// console.log("before:run", details);
// reset any config's
assurkaConfig.testSpecs = [];
assurkaConfig.testRun = null;
// check if we have an existing token
if (!assurkaConfig.token) {
const tokenResponse: any = await api(`auth/api`, "POST", {
id: assurkaConfig.projectId,
secret: assurkaConfig.secret,
});
assurkaConfig.token = tokenResponse.token;
const commit = await getGitCommit(details.config.projectRoot);
const payload = details;
delete details.specs;
assurkaConfig.testRun = await api(
`test-run/${assurkaConfig.testPlanId}/create`,
"POST",
{
...payload,
commit,
platform: "cypress",
}
);
}
});
on("after:run", async (results) => {
console.log("after:run", results);
if (results) {
const testRunPayload = {
duration: results.totalDuration,
status: results.status,
browserName: results.browserName,
browserVersion: results.browserVersion,
cypressVersion: results.cypressVersion,
osName: results.osName,
osVersion: results.osVersion,
endedTestsAt: results.endedTestsAt,
startedTestsAt: results.startedTestsAt,
numFailedTests: results.totalFailed,
numPassedTests: results.totalPassed,
numPendingTests: results.totalPending,
numSkippedTest: results.totalSkipped,
numTestSuites: results.totalSuites,
numTotalTests: results.totalTests,
};
//update the test run
await api(
`test-run/${assurkaConfig.testPlanId}/update/${assurkaConfig.testRun.id}`,
"PATCH",
testRunPayload
);
//update the test plan
await api(
`test-run/${assurkaConfig.projectId}/update/${assurkaConfig.testPlan}`,
"PATCH",
testRunPayload
);
// results will be undefined in interactive mode
console.log(results.totalPassed, "out of", results.totalTests, "passed");
}
});
on("before:spec", async (spec) => {
//console.log("before:spec", spec);
const testSpec = await api(
`test-spec/${assurkaConfig.testRun.id}/create`,
"POST",
{
name: spec.name,
testRunId: assurkaConfig.testRun.id,
}
);
assurkaConfig.testSpecs.push(testSpec);
});
on("after:spec", async (spec, results) => {
// console.log("after:spec", spec, results);
// get the test spec
const testSpec = assurkaConfig.testSpecs.find((f) => f.name === spec.name);
// make sure we have some results
if (results) {
const screenshots = [];
// do we have any screenshots to upload?
if (results && results.screenshots) {
// upload the screenshots for the spec
for (const screenshot of results.screenshots) {
const result = await upload(screenshot.path);
screenshots.push(result.filename);
}
}
// do we have a video?
let video = undefined;
if (results && results.video) {
// upload the video
const videoResponse = await upload(results.video);
if (videoResponse && videoResponse.filename) {
video = videoResponse.filename;
}
}
// setup the payload for the spec file
let specPayload = {
numPassingTests: results.stats.passes,
numPendingTests: results.stats.pending,
numFailingTests: results.stats.failures,
duration: results.stats.wallClockDuration,
runtime: results.stats.wallClockDuration,
screenshots: screenshots,
video: video,
testRunId: assurkaConfig.testRun.id,
};
if (results.video) {
}
await api(
`test-spec/${assurkaConfig.testRun.id}/update/${testSpec.id}`,
"PATCH",
specPayload
);
for (let index = 0; index < results.tests.length; index++) {
const test = results.tests[index];
const testPayload = {
title: test.title[test.title.length - 1],
ancestorTitles: test.title,
status: test.state,
attempts: test.attempts,
body: test.body,
displayError: test.displayError,
testSpecId: testSpec.id,
testRunId: assurkaConfig.testRun.id,
position: index,
};
await api(`test-case/${testSpec.id}/create`, "POST", testPayload);
}
// delete the recorded video if the spec passed
if (results && results.stats.failures === 0 && results.video) {
// `del()` returns a promise, so it's important to return it to ensure
// deleting the video is finished before moving on
return del(results.video);
}
// delete the recorded video if no tests retried
if (results && results.video) {
// Do we have failures for any retry attempts?
const failures = _.some(results.tests, (test) => {
return _.some(test.attempts, { state: "failed" });
});
if (!failures) {
// delete the video if the spec passed and no tests retried
return del(results.video);
}
}
}
});
on("after:screenshot", (details) => {
//console.log("after:screenshot", details);
});
on("before:browser:launch", (browser, launchOptions) => {
// console.log("before:browser:launch", browser, launchOptions);
if (browser.name === "chrome" && browser.isHeadless) {
// fullPage screenshot size is 1400x1200 on non-retina screens
// and 2800x2400 on retina screens
launchOptions.args.push("--window-size=1400,1200");
// force screen to be non-retina (1400x1200 size)
launchOptions.args.push("--force-device-scale-factor=1");
}
if (browser.name === "electron" && browser.isHeadless) {
// fullPage screenshot size is 1400x1200
launchOptions.preferences.width = 1400;
launchOptions.preferences.height = 1200;
}
if (browser.name === "firefox" && browser.isHeadless) {
// menubars take up height on the screen
// so fullPage screenshot size is 1400x1126
launchOptions.args.push("--width=1400");
launchOptions.args.push("--height=1200");
}
return launchOptions;
});
/**
*Sends results to the Assurka API
* @param {string} url - the url
* @param {string} method - the api method
* @param {*} body - the api body
* @memberof AssurkaCustomReporter
*/
const api = async (url: string, method: string, body?: any) => {
let headers = new Headers({ "Content-Type": "application/json" });
if (assurkaConfig.token) {
headers.append("Authorization", "Bearer " + assurkaConfig.token);
}
return fetch(`${assurkaConfig.host}/${url}`, {
method,
headers: headers,
body: JSON.stringify(body),
}).then((response) => {
if (!response.ok) {
return null;
}
return response.json();
});
};
const upload = async (filePath: string) => {
const form = new FormData();
const fileStream = fs.createReadStream(filePath);
form.append("file", fileStream);
const options = {
method: "POST",
credentials: "include",
body: form,
};
return fetch(`${assurkaConfig.host}/file/upload`, { ...options }).then(
(response) => {
if (!response.ok) {
console.warn(response.statusText);
return null;
}
return response.json();
}
);
};
const getGitCommit = async (path: string) => {
return new Promise((res, _rej) => {
getLastCommit(
(err, commit) => {
if (err) return res(null);
return res(commit);
},
{ dst: path }
);
});
};
};