Skip to content

Commit

Permalink
chore: handle exceptions and edge cases
Browse files Browse the repository at this point in the history
  • Loading branch information
agoldis committed Jun 2, 2023
1 parent 39f2f20 commit f281119
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 55 deletions.
31 changes: 26 additions & 5 deletions packages/cypress-cloud/lib/cypress/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Debug from "debug";
import _ from "lodash";
import { getCypressRunAPIParams } from "../config";
import { safe } from "../lang";
import { warn } from "../log";
import { getWSSPort } from "../ws";

const debug = Debug("currents:cypress");
Expand Down Expand Up @@ -55,24 +56,44 @@ export async function runSpecFile(
debug("running cypress with options %o", options);
const result = await cypress.run(options);

if (result.status === "failed") {
warn('Cypress runner failed with message: "%s"', result.message);
warn(
"The following spec files will be marked as failed: %s",
spec
.split(",")
.map((i) => `\n - ${i}`)
.join("")
);
}
debug("cypress run result %o", result);
return result;
}

export const runSpecFileSafe = (
...args: Parameters<typeof runSpecFile>
spec: RunCypressSpecFile,
cypressRunOptions: ValidatedCurrentsParameters
): Promise<CypressResult> =>
safe(
runSpecFile,
(error) => {
const message = `Cypress runnner crashed with an error:\n${
(error as Error).message
}\n${(error as Error).stack}}`;
debug("cypress run exception %o", error);
warn('Cypress runner crashed: "%s"', message);
warn(
"The following spec files will be marked as failed: %s",
spec.spec
.split(",")
.map((i) => `\n - ${i}`)
.join("")
);
return {
status: "failed" as const,
failures: 1,
message: `Cypress process crashed with an error:\n${
(error as Error).message
}\n${(error as Error).stack}}`,
message,
};
},
() => {}
)(...args);
)(spec, cypressRunOptions);
64 changes: 28 additions & 36 deletions packages/cypress-cloud/lib/results/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
UpdateInstanceResultsPayload,
} from "../api";
import { MergedConfig } from "../config";
import { getConfig } from "../runner";

const debug = Debug("currents:results");

Expand Down Expand Up @@ -53,10 +54,6 @@ export const getTestAttempt = (attempt: CypressCommandLine.AttemptResult) => {
export const getInstanceResultPayload = (
runResult: CypressCommandLine.RunResult
): UpdateInstanceResultsPayload => {
const altTests = [];
if (runResult.error && !runResult.tests?.length) {
altTests.push(getFakeTestFromException(runResult.error, runResult.stats));
}
return {
stats: getStats(runResult.stats),
reporterStats: runResult.reporterStats,
Expand All @@ -70,11 +67,11 @@ export const getInstanceResultPayload = (
hooks: runResult.hooks,
attempts: test.attempts?.map(getTestAttempt) ?? [],
clientId: `r${i}`,
})) ?? altTests,
})) ?? [],
};
};

function getFakeTestFromException(
export function getFakeTestFromException(
error: string,
stats: CypressCommandLine.RunResult["stats"]
) {
Expand Down Expand Up @@ -106,10 +103,6 @@ export const getInstanceTestsPayload = (
runResult: CypressCommandLine.RunResult,
config: Cypress.ResolvedConfigOptions
): SetInstanceTestsPayload => {
const altTests = [];
if (runResult.error && !runResult.tests?.length) {
altTests.push(getFakeTestFromException(runResult.error, runResult.stats));
}
return {
config,
tests:
Expand All @@ -119,7 +112,7 @@ export const getInstanceTestsPayload = (
body: test.body,
clientId: `r${i}`,
hookIds: [],
})) ?? altTests,
})) ?? [],
hooks: runResult.hooks,
};
};
Expand Down Expand Up @@ -201,19 +194,39 @@ const emptyStats = {
totalTests: 0,
};

const getDummyFailedTest = (start: string, error: string) => ({
title: ["Unknown"],
state: "failed",
body: "// This test is automatically generated due to execution failure",
displayError: error,
attempts: [
{
state: "failed",
startedAt: start,
duration: 0,
videoTimestamp: 0,
screenshots: [],
error: {
name: "CypressExecutionError",
message: error,
stack: "",
},
},
],
});

export function getFailedDummyResult({
specs,
error,
config,
}: {
specs: string[];
error: string;
config: any; // TODO tighten this up
}): CypressCommandLine.CypressRunResult {
const start = new Date().toISOString();
const end = new Date().toISOString();
return {
config,
// @ts-ignore
config: getConfig() ?? {},
status: "finished",
startedTestsAt: new Date().toISOString(),
endedTestsAt: new Date().toISOString(),
Expand Down Expand Up @@ -253,28 +266,7 @@ export function getFailedDummyResult({
absolute: s,
relativeToCommonRoot: s,
},
tests: [
{
title: ["Unknown"],
state: "failed",
body: "// This test is automatically generated due to execution failure",
displayError: error,
attempts: [
{
state: "failed",
startedAt: start,
duration: 0,
videoTimestamp: 0,
screenshots: [],
error: {
name: "CloudExecutionError",
message: error,
stack: "",
},
},
],
},
],
tests: [getDummyFailedTest(start, error)],
shouldUploadVideo: false,
skippedSpec: false,
})),
Expand Down
4 changes: 3 additions & 1 deletion packages/cypress-cloud/lib/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "./init";
import Debug from "debug";
import { CurrentsRunParameters } from "../types";
import { createRun } from "./api";
import { cutInitialOutput } from "./capture";
import { cutInitialOutput, getCapturedOutput } from "./capture";
import { getCI } from "./ciProvider";
import {
getMergedConfig,
Expand All @@ -26,6 +26,7 @@ import {
setConfig,
setSpecAfter,
setSpecBefore,
setSpecOutput,
} from "./runner";
import { shutdown } from "./shutdown";
import { getSpecFiles } from "./specMatcher";
Expand Down Expand Up @@ -155,6 +156,7 @@ function listenToSpecEvents() {
async ({ spec, results }: { spec: Cypress.Spec; results: any }) => {
debug("after:spec %o %o", spec, results);
setSpecAfter(spec.relative, results);
setSpecOutput(spec.relative, getCapturedOutput());
createReportTaskSpec(spec.relative);
}
);
Expand Down
4 changes: 2 additions & 2 deletions packages/cypress-cloud/lib/runner/mapResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function specResultsToCypressResults(
duration: specAfterResult.stats.wallClockDuration,
},
reporter: specAfterResult.reporter,
reporterStats: specAfterResult.reporterStats,
reporterStats: specAfterResult.reporterStats ?? {},
spec: specAfterResult.spec,
error: specAfterResult.error,
video: specAfterResult.video,
Expand All @@ -76,7 +76,7 @@ export function specResultsToCypressResults(
// wrong typedef for CypressCommandLine.CypressRunResult
// actual HookName is "before all" | "before each" | "after all" | "after each"
hooks: specAfterResult.hooks,
tests: specAfterResult.tests.map((t) =>
tests: (specAfterResult.tests ?? []).map((t) =>
getTest(t, specAfterResult.screenshots)
),
},
Expand Down
6 changes: 3 additions & 3 deletions packages/cypress-cloud/lib/runner/spec.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
export interface SpecResult {
error: string | null;
exception: null | string;
hooks: TestHook[];
hooks: TestHook[] | null;
reporter: string;
reporterStats: ReporterStats;
reporterStats: ReporterStats | null;
screenshots: Screenshot[];
spec: Spec;
stats: Stats;
tests: Test[];
tests: Test[] | null;
video: string | null;
}

Expand Down
49 changes: 41 additions & 8 deletions packages/cypress-cloud/lib/runner/state.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { InstanceId } from "cypress-cloud/types";
import { CypressRun, InstanceId } from "cypress-cloud/types";
import Debug from "debug";
import { error, warn } from "../log";
import { getFailedDummyResult } from "../results";
import { getFailedDummyResult, getFakeTestFromException } from "../results";
import { specResultsToCypressResults } from "./mapResult";
import { SpecResult } from "./spec.type";

const debug = Debug("currents:state");

// Careful here - it is a global mutable state 🐲
type InstanceExecutionState = {
instanceId: InstanceId;
Expand Down Expand Up @@ -76,12 +79,25 @@ export const setInstanceResult = (
i.runResultsReportedAt = new Date();
};

export const setSpecOutput = (spec: string, output: string) => {
const i = getExecutionStateSpec(spec);
if (!i) {
warn('Cannot find execution state for spec "%s"', spec);
return;
}
setInstanceOutput(i.instanceId, output);
};

export const setInstanceOutput = (instanceId: string, output: string) => {
const i = executionState[instanceId];
if (!i) {
warn('Cannot find execution state for instance "%s"', instanceId);
return;
}
if (i.output) {
debug('Instance "%s" already has output', instanceId);
return;
}
i.output = output;
};

Expand All @@ -101,27 +117,44 @@ export const getInstanceResults = (

return getFailedDummyResult({
specs: ["unknown"],
error: "cypress-cloud: Cannot find execution state for instance",
config: {},
error: "Cannot find execution state for instance",
});
}

// use spec:after results - it can become available before run results
if (i.specAfterResults) {
return specResultsToCypressResults(i.specAfterResults);
return backfillException(specResultsToCypressResults(i.specAfterResults));
}

if (i.runResults) {
return i.runResults;
return backfillException(i.runResults);
}

debug('No results detected for "%s"', i.spec);
return getFailedDummyResult({
specs: [i.spec],
error: "cypress-cloud: Cannot find execution state for instance",
config: {},
error: `No results detected for the spec file. That usually happens because of cypress crash. See the console output for details.`,
});
};

const backfillException = (result: CypressCommandLine.CypressRunResult) => {
return {
...result,
runs: result.runs.map(backfillExceptionRun),
};
};

const backfillExceptionRun = (run: CypressRun) => {
if (!run.error) {
return run;
}

return {
...run,
tests: [getFakeTestFromException(run.error, run.stats)],
};
};

let _config: Cypress.ResolvedConfigOptions | undefined = undefined;
export const setConfig = (c: typeof _config) => (_config = c);
export const getConfig = () => _config;

0 comments on commit f281119

Please sign in to comment.