artes
Version:
The simplest way to automate UI and API tests using Cucumber-style steps.
217 lines (180 loc) • 5.9 kB
JavaScript
const {
BeforeAll,
Before,
After,
Status,
setDefaultTimeout,
AfterStep,
BeforeStep,
AfterAll,
} = require("@cucumber/cucumber");
const { spawnSync } = require("child_process");
const { invokeBrowser } = require("../helper/contextManager/browserManager");
const { invokeRequest } = require("../helper/contextManager/requestManager");
const { pomCollector } = require("../helper/controller/pomCollector");
const cucumberConfig = require("../../cucumber.config");
const { context } = require("./context");
const fs = require("fs");
const path = require("path");
const { moduleConfig } = require("artes/src/helper/imports/commons");
const statusDir = path.join(process.cwd(), "testsStatus");
const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
/* ------------------- Helpers ------------------- */
setDefaultTimeout(cucumberConfig.default.timeout);
async function attachResponse(attachFn) {
if (!context.response) return;
for (const [key, value] of Object.entries(context.response)) {
const text =
typeof value === "object"
? `${key}:\n${JSON.stringify(value, null, 2)}`
: `${key}:\n${value}`;
await attachFn(text, "text/plain");
}
}
function saveTestStatus(result, pickle) {
fs.mkdirSync(statusDir, { recursive: true });
fs.writeFileSync(
path.join(statusDir, `${result.status}-${pickle.id}.txt`),
"",
);
}
/* ------------------- Hooks ------------------- */
BeforeAll(() => {
pomCollector();
});
Before(async function () {
context.vars = {};
const envFilePath = path.join(
moduleConfig.projectPath,
"tests",
"environment_variables",
`${cucumberConfig.env}.env.json`,
);
if (fs.existsSync(envFilePath)) {
let env_vars = fs.readFileSync(envFilePath, "utf-8");
try {
env_vars = JSON.parse(env_vars);
context.vars = { ...context.vars, ...env_vars };
} catch (err) {
console.error("Error parsing environment variables JSON:", err);
}
}
const { browser, context: browserContext } = await invokeBrowser();
const requestInstance = await invokeRequest();
context.browser = browser;
context.browserContext = browserContext;
context.page = await browserContext.newPage();
context.request = requestInstance;
await context.page.setDefaultTimeout(cucumberConfig.default.timeout);
if (
(cucumberConfig.default.reportWithTrace || cucumberConfig.default.trace) &&
!context.response
) {
await browserContext.tracing.start({
sources: true,
screenshots: true,
snapshots: true,
});
}
});
BeforeStep(({ pickleStep }) => {
if (HTTP_METHODS.some((method) => pickleStep.text.includes(method))) {
context.response = {};
}
});
AfterStep(async function ({ pickleStep }) {
if (HTTP_METHODS.some((method) => pickleStep.text.includes(method))) {
await attachResponse(this.attach);
}
});
After(async function ({ pickle, result }) {
const shouldReport =
(cucumberConfig.default.successReport ||
result?.status !== Status.PASSED) &&
!context.response;
if (shouldReport) {
const screenshotPath = path.join(
"test-results",
"visualReport",
pickle.name,
`${pickle.name}.png`,
);
const img = await context.page.screenshot({
path: screenshotPath,
type: "png",
});
await this.attach(img, {
mediaType: "image/png",
fileName: `${pickle.name.replaceAll(" ", "_")}.png`,
});
}
saveTestStatus(result, pickle);
const tracePath = path.join(
moduleConfig.projectPath,
`./${pickle.name.replaceAll(" ", "_")}.zip`,
);
if (
(cucumberConfig.default.reportWithTrace || cucumberConfig.default.trace) &&
!context.response &&
shouldReport
) {
await context.browserContext.tracing.stop({
path: tracePath,
});
if (cucumberConfig.default.reportWithTrace) {
const trace = fs.readFileSync(tracePath);
await this.attach(trace, {
mediaType: "application/zip",
fileName: `${pickle.name.replace(/\s+/g, "_")}_trace.zip`,
});
if (!cucumberConfig.default.trace) {
spawnSync("npx", ["rimraf", tracePath], {
cwd: moduleConfig.projectPath,
stdio: "inherit",
shell: true,
});
}
}
}
await attachResponse(this.attach);
await context.page?.close();
await context.browserContext?.close();
await context.browser?.close();
await context.request?.dispose();
if (shouldReport && context.page.video) {
const video = context.page.video();
if (video) {
const videoPath = await video.path();
await new Promise((resolve) => setTimeout(resolve, 1000));
if (fs.existsSync(videoPath)) {
const webmBuffer = fs.readFileSync(videoPath);
await this.attach(webmBuffer, {
mediaType: "video/webm",
fileName: `${pickle.name.replaceAll(" ", "_")}.webm`,
});
}
}
}
});
AfterAll(() => {
if (!fs.existsSync(statusDir)) return;
const files = fs.readdirSync(statusDir);
const passedCount = files.filter((f) => f.split("-")[0] === "PASSED").length;
const totalTests = files.length;
const successPercentage = (passedCount / totalTests) * 100;
if (cucumberConfig.default.testPercentage !== undefined) {
const meetsThreshold =
successPercentage >= cucumberConfig.default.testPercentage;
if (meetsThreshold) {
console.log(
`✅ Tests passed required ${cucumberConfig.default.testPercentage}% success rate with ${successPercentage.toFixed(2)}%!`,
);
fs.writeFileSync(path.join(process.cwd(), "EXIT_CODE.txt"), "0");
} else {
console.log(
`❌ Tests failed required ${cucumberConfig.default.testPercentage}% success rate with ${successPercentage.toFixed(2)}%!`,
);
fs.writeFileSync(path.join(process.cwd(), "EXIT_CODE.txt"), "1");
}
}
});