UNPKG

@assurka/cypress

Version:

Assurka Cypress Plugin

284 lines (254 loc) 8.44 kB
/// <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 } ); }); }; };