Skip to content

Commit

Permalink
Merge pull request #45 from javierbrea/release
Browse files Browse the repository at this point in the history
Release v1.4.0
  • Loading branch information
javierbrea committed Jan 2, 2021
2 parents 99d89b7 + 7378551 commit 9f055f5
Show file tree
Hide file tree
Showing 39 changed files with 807 additions and 167 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed
### BREAKING CHANGES

## [1.4.0] - 2021-01-02

### Added
- feat: Add suite and tests plugin custom configuration. Enable or disable plugin for suites or tests using the enabled property from custom config
- test(e2e): Add helper to run E2E tests with different specs files and configurations

### Changed
- feat: Do not log plugin tasks, except when setting shouldSkip flag to true
- docs: Change TypeScript example
- refactor: Do not check plugin configuration inside Node.js plugin
- refactor: Rename plugin tasks. Start all with same namespace

### Removed
- chore: Remove unused eslint settings from src folder

## [1.3.1] - 2020-12-31
### Fixed
- docs: Fix E2E tests versions links
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,53 @@ From now, if one test fail after its last retry, the rest of tests will be skipp

![Cypress results screenshot](docs/assets/cypress-fail-fast-screenshot.png)

## Custom Configurations

If you want to configure the plugin on a specific test, you can set this by using the `failFast` property in [test's configuration](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Test-Configuration). The plugin allows next config values:

* __`failFast`__: Configuration for the plugin, containing any of next properties:
* __`enabled`__ : Indicates wheter a failure of the current test or children tests _(if configuration is [applied to a suite](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Suite-configuration))_ should produce to skip the rest of tests or not. (Note that setting this property as `true` will not have effect if the plugin is disabled globally using the `FAIL_FAST` environment variable)


### Example

In the next example, tests are configured to `fail-fast` only in case the test with the "sanity test" description fails. If any of the other tests fails, `fail-fast` will not be applied.

```js
describe("All tests", {
failFast: {
enabled: false, // Children tests and describes will inherit this configuration
},
}, () => {
it("sanity test", {
failFast: {
enabled: true, // Overwrite configuration defined in parents
},
}, () => {
// Will skip the rest of tests if this one fails
expect(true).to.be.true;
});

it("second test",() => {
// Will continue executing tests if this one fails
expect(true).to.be.true;
});

it("third test",() => {
// Will continue executing tests if this one fails
expect(true).to.be.true;
});
});
```

## Usage with TypeScript

If you are using [TypeScript in the Cypress plugins file][cypress-typescript], this plugin includes TypeScript declarations and can be imported like the following:

```ts
import cypressFailFast = require("cypress-fail-fast/plugin");

export default (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.ResolvedConfigOptions => {
export default (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions => {
cypressFailFast(on, config);
return config;
};
Expand Down
17 changes: 17 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
/// <reference types="cypress" />

declare namespace Cypress {
interface FailFastConfigOptions {
/**
* Disables fail-fast plugin
* If the test fails, the rest of tests won't be skipped
*/
enabled: boolean
}

interface TestConfigOverrides extends Partial<TestConfigOverrides> {
/**
* Configuration for fail-fast plugin
*/
failFast: Partial<FailFastConfigOptions>
}
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cypress-fail-fast",
"version": "1.3.1",
"version": "1.4.0",
"description": "Skip the rest of Cypress tests on first failure",
"keywords": [
"cypress",
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
sonar.organization=javierbrea
sonar.projectKey=cypress-fail-fast
sonar.projectVersion=1.3.1
sonar.projectVersion=1.4.0

sonar.javascript.file.suffixes=.js
sonar.sourceEncoding=UTF-8
Expand Down
9 changes: 0 additions & 9 deletions src/.eslintrc.json

This file was deleted.

4 changes: 4 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const PLUGIN_ENVIRONMENT_VAR = "FAIL_FAST";
const SHOULD_SKIP_TASK = "failFastShouldSkip";
const RESET_SKIP_TASK = "failFastResetSkip";

module.exports = {
PLUGIN_ENVIRONMENT_VAR,
SHOULD_SKIP_TASK,
RESET_SKIP_TASK,
};
17 changes: 6 additions & 11 deletions src/plugin.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
const { PLUGIN_ENVIRONMENT_VAR } = require("./helpers");

function shouldFailFast(config) {
return (
config.env[PLUGIN_ENVIRONMENT_VAR] === true || config.env[PLUGIN_ENVIRONMENT_VAR] === "true"
);
}
const { SHOULD_SKIP_TASK, RESET_SKIP_TASK } = require("./helpers");

module.exports = (on, config) => {
// Expose fail fast tasks
// store skip flag
let shouldSkip = false;

// Expose fail fast tasks
on("task", {
resetShouldSkipDueToFailFast() {
[RESET_SKIP_TASK]: function () {
shouldSkip = false;
return null;
},
shouldSkipDueToFailFast(value) {
if (value === true && shouldFailFast(config)) {
[SHOULD_SKIP_TASK]: function (value) {
if (value === true) {
shouldSkip = value;
}
return shouldSkip;
Expand Down
67 changes: 45 additions & 22 deletions src/support.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
const { PLUGIN_ENVIRONMENT_VAR } = require("./helpers");
const { PLUGIN_ENVIRONMENT_VAR, SHOULD_SKIP_TASK, RESET_SKIP_TASK } = require("./helpers");

function support(Cypress, cy, beforeEach, afterEach, before) {
function isHeaded() {
return Cypress.browser && Cypress.browser.isHeaded;
}

function shouldFailFast() {
return (
Cypress.env(PLUGIN_ENVIRONMENT_VAR) === true ||
Cypress.env(PLUGIN_ENVIRONMENT_VAR) === "true"
);
function getFailFastEnvironmentConfig() {
return {
enabled:
Cypress.env(PLUGIN_ENVIRONMENT_VAR) === true ||
Cypress.env(PLUGIN_ENVIRONMENT_VAR) === "true",
};
}

function testState(test) {
return (
test.currentTest &&
test.currentTest.state === "failed" &&
test.currentTest.currentRetry() === test.currentTest.retries()
);
function getTestFailFastConfig(currentTest) {
if (currentTest.cfg && currentTest.cfg.failFast) {
return currentTest.cfg.failFast;
}
if (currentTest.parent) {
return getTestFailFastConfig(currentTest.parent);
}
return getFailFastEnvironmentConfig();
}

function pluginIsEnabled() {
return getFailFastEnvironmentConfig().enabled;
}

function shouldSkipRestOfTests(currentTest) {
return getTestFailFastConfig(currentTest).enabled;
}

function testHasFailed(currentTest) {
return currentTest.state === "failed" && currentTest.currentRetry() === currentTest.retries();
}

beforeEach(function () {
if (shouldFailFast()) {
cy.task("shouldSkipDueToFailFast").then((value) => {
if (pluginIsEnabled()) {
cy.task(SHOULD_SKIP_TASK, null, { log: false }).then((value) => {
if (value === true) {
Cypress.runner.stop();
}
Expand All @@ -32,19 +47,27 @@ function support(Cypress, cy, beforeEach, afterEach, before) {

afterEach(function () {
// Mark skip flag as true if test failed
if (shouldFailFast() && testState(this)) {
cy.task("shouldSkipDueToFailFast", true);
const currentTest = this.currentTest;
if (
currentTest &&
pluginIsEnabled() &&
testHasFailed(currentTest) &&
shouldSkipRestOfTests(currentTest)
) {
cy.task(SHOULD_SKIP_TASK, true);
Cypress.runner.stop();
}
});

before(function () {
if (isHeaded() && shouldFailFast()) {
// Reset the shouldSkip flag at the start of a run, so that it
// doesn't carry over into subsequent runs.
// Do this only for headed runs because in headless runs,
// the `before` hook is executed for each spec file.
cy.task("resetShouldSkipDueToFailFast");
if (isHeaded() && pluginIsEnabled()) {
/*
Reset the shouldSkip flag at the start of a run, so that it
doesn't carry over into subsequent runs.
Do this only for headed runs because in headless runs,
the `before` hook is executed for each spec file.
*/
cy.task(RESET_SKIP_TASK, null, { log: false });
}
});
}
Expand Down
17 changes: 2 additions & 15 deletions test-e2e/commands/build.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
const { copyPluginToCypressSupport, copyCypressSources } = require("./support/copy");

const VARIANTS = [
{
path: "cypress-5",
typescript: false,
},
{
path: "cypress-6",
typescript: false,
},
{
path: "typescript",
typescript: true,
},
];
const variants = require("./support/variants");

VARIANTS.forEach((variant) => {
variants.forEach((variant) => {
copyCypressSources(variant.path, variant.typescript);
if (variant.typescript) {
copyPluginToCypressSupport(variant.path);
Expand Down
61 changes: 41 additions & 20 deletions test-e2e/commands/support/copy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// TODO, use typescript paths alias to load directly library from parent folder instead of copying it

const path = require("path");
const fsExtra = require("fs-extra");

Expand All @@ -10,6 +8,7 @@ const CYPRESS_INTEGRATION_PATH = "integration";
const TESTS_PATH = path.resolve(__dirname, "..", "..");
const ROOT_LIB_PATH = path.resolve(TESTS_PATH, "..");
const VARIANTS_PATH = path.resolve(TESTS_PATH, "cypress-variants");
const CYPRESS_SRC_PATH = path.resolve(TESTS_PATH, "cypress-src");

const toTypeScriptName = (fileName, hasToBeRenamed) => {
return hasToBeRenamed ? fileName.replace(".js", ".ts") : fileName;
Expand All @@ -27,6 +26,7 @@ const variantPaths = (variant) => {
};
};

// TODO, use typescript paths alias to load directly library from parent folder instead of copying it
const copyPluginToCypressSupport = (variant) => {
const PLUGIN_DEST_FOLDER = "cypress-fail-fast";
const SRC_FOLDER = "src";
Expand Down Expand Up @@ -58,21 +58,15 @@ const copyPluginToCypressSupport = (variant) => {

const copyCypressSources = (variant, typescript = false) => {
const destPaths = variantPaths(variant);
const INTEGRATION_A_FILE = "a-file.js";
const INTEGRATION_B_FILE = "b-file.js";
const BABEL_CONFIG_FILE = "babel.config.js";
const CYPRESS_CONFIG_FILE = "cypress.json";
const INDEX_FILE = toTypeScriptName("index.js", typescript);

const cypressSrcPath = path.resolve(TESTS_PATH, "cypress-src");
const integrationPath = path.resolve(cypressSrcPath, CYPRESS_INTEGRATION_PATH);
const pluginsPath = path.resolve(cypressSrcPath, CYPRESS_PLUGINS_PATH);
const supportPath = path.resolve(cypressSrcPath, CYPRESS_SUPPORT_PATH);
const pluginsPath = path.resolve(CYPRESS_SRC_PATH, CYPRESS_PLUGINS_PATH);
const supportPath = path.resolve(CYPRESS_SRC_PATH, CYPRESS_SUPPORT_PATH);

const cypressConfigFile = path.resolve(cypressSrcPath, CYPRESS_CONFIG_FILE);
const babelConfigFile = path.resolve(cypressSrcPath, BABEL_CONFIG_FILE);
const integrationAFile = path.resolve(integrationPath, INTEGRATION_A_FILE);
const integrationBFile = path.resolve(integrationPath, INTEGRATION_B_FILE);
const cypressConfigFile = path.resolve(CYPRESS_SRC_PATH, CYPRESS_CONFIG_FILE);
const babelConfigFile = path.resolve(CYPRESS_SRC_PATH, BABEL_CONFIG_FILE);
const pluginFile = path.resolve(pluginsPath, INDEX_FILE);
const supportFile = path.resolve(supportPath, INDEX_FILE);

Expand All @@ -84,25 +78,52 @@ const copyCypressSources = (variant, typescript = false) => {
fsExtra.ensureDirSync(destPaths.cypress.support);
fsExtra.copySync(supportFile, path.resolve(destPaths.cypress.support, INDEX_FILE));

fsExtra.copySync(cypressConfigFile, path.resolve(destPaths.root, CYPRESS_CONFIG_FILE));

if (!typescript) {
fsExtra.copySync(babelConfigFile, path.resolve(destPaths.root, BABEL_CONFIG_FILE));
}
};

const copyCypressSpecs = (specsFolder, variant) => {
const destPaths = variantPaths(variant.path);
const INTEGRATION_A_FILE = "a-file.js";
const INTEGRATION_B_FILE = "b-file.js";
const INTEGRATION_C_FILE = "c-file.js";

const integrationPath = path.resolve(CYPRESS_SRC_PATH, CYPRESS_INTEGRATION_PATH, specsFolder);

const integrationAFile = path.resolve(integrationPath, INTEGRATION_A_FILE);
const integrationBFile = path.resolve(integrationPath, INTEGRATION_B_FILE);
const integrationCFile = path.resolve(integrationPath, INTEGRATION_C_FILE);

fsExtra.removeSync(destPaths.cypress.integration);
fsExtra.ensureDirSync(destPaths.cypress.integration);
fsExtra.copySync(
integrationAFile,
path.resolve(destPaths.cypress.integration, toTypeScriptName(INTEGRATION_A_FILE, typescript))
path.resolve(
destPaths.cypress.integration,
toTypeScriptName(INTEGRATION_A_FILE, variant.typescript)
)
);
fsExtra.copySync(
integrationBFile,
path.resolve(destPaths.cypress.integration, toTypeScriptName(INTEGRATION_B_FILE, typescript))
path.resolve(
destPaths.cypress.integration,
toTypeScriptName(INTEGRATION_B_FILE, variant.typescript)
)
);
fsExtra.copySync(
integrationCFile,
path.resolve(
destPaths.cypress.integration,
toTypeScriptName(INTEGRATION_C_FILE, variant.typescript)
)
);

fsExtra.copySync(cypressConfigFile, path.resolve(destPaths.root, CYPRESS_CONFIG_FILE));

if (!typescript) {
fsExtra.copySync(babelConfigFile, path.resolve(destPaths.root, BABEL_CONFIG_FILE));
}
};

module.exports = {
copyPluginToCypressSupport,
copyCypressSources,
copyCypressSpecs,
};

0 comments on commit 9f055f5

Please sign in to comment.