diff --git a/lib/effects.js b/lib/effects.js new file mode 100644 index 000000000..f5b8890cb --- /dev/null +++ b/lib/effects.js @@ -0,0 +1,123 @@ +const recorder = require('./recorder') +const { debug } = require('./output') +const store = require('./store') + +/** + * @module hopeThat + * + * `hopeThat` is a utility function for CodeceptJS tests that allows for soft assertions. + * It enables conditional assertions without terminating the test upon failure. + * This is particularly useful in scenarios like A/B testing, handling unexpected elements, + * or performing multiple assertions where you want to collect all results before deciding + * on the test outcome. + * + * ## Use Cases + * + * - **Multiple Conditional Assertions**: Perform several assertions and evaluate all their outcomes together. + * - **A/B Testing**: Handle different variants in A/B tests without failing the entire test upon one variant's failure. + * - **Unexpected Elements**: Manage elements that may or may not appear, such as "Accept Cookie" banners. + * + * ## Examples + * + * ### Multiple Conditional Assertions + * + * Add the assertion library: + * ```js + * const assert = require('assert'); + * const { hopeThat } = require('codeceptjs/effects'); + * ``` + * + * Use `hopeThat` with assertions: + * ```js + * const result1 = await hopeThat(() => I.see('Hello, user')); + * const result2 = await hopeThat(() => I.seeElement('.welcome')); + * assert.ok(result1 && result2, 'Assertions were not successful'); + * ``` + * + * ### Optional Click + * + * ```js + * const { hopeThat } = require('codeceptjs/effects'); + * + * I.amOnPage('/'); + * await hopeThat(() => I.click('Agree', '.cookies')); + * ``` + * + * Performs a soft assertion within CodeceptJS tests. + * + * This function records the execution of a callback containing assertion logic. + * If the assertion fails, it logs the failure without stopping the test execution. + * It is useful for scenarios where multiple assertions are performed, and you want + * to evaluate all outcomes before deciding on the test result. + * + * ## Usage + * + * ```js + * const result = await hopeThat(() => I.see('Welcome')); + * + * // If the text "Welcome" is on the page, result => true + * // If the text "Welcome" is not on the page, result => false + * ``` + * + * @async + * @function hopeThat + * @param {Function} callback - The callback function containing the soft assertion logic. + * @returns {Promise} - Resolves to `true` if the assertion is successful, or `false` if it fails. + * + * @example + * // Multiple Conditional Assertions + * const assert = require('assert'); + * const { hopeThat } = require('codeceptjs/effects'); + * + * const result1 = await hopeThat(() => I.see('Hello, user')); + * const result2 = await hopeThat(() => I.seeElement('.welcome')); + * assert.ok(result1 && result2, 'Assertions were not successful'); + * + * @example + * // Optional Click + * const { hopeThat } = require('codeceptjs/effects'); + * + * I.amOnPage('/'); + * await hopeThat(() => I.click('Agree', '.cookies')); + */ +async function hopeThat(callback) { + if (store.dryRun) return + const sessionName = 'hopeThat' + + let result = false + return recorder.add( + 'hopeThat', + () => { + recorder.session.start(sessionName) + store.hopeThat = true + callback() + recorder.add(() => { + result = true + recorder.session.restore(sessionName) + return result + }) + recorder.session.catch(err => { + result = false + const msg = err.inspect ? err.inspect() : err.toString() + debug(`Unsuccessful assertion > ${msg}`) + recorder.session.restore(sessionName) + return result + }) + return recorder.add( + 'result', + () => { + store.hopeThat = undefined + return result + }, + true, + false, + ) + }, + false, + false, + ) +} + +module.exports = { + hopeThat, +} diff --git a/lib/plugin/hopeThat.js b/lib/plugin/hopeThat.js deleted file mode 100644 index 08d3cd463..000000000 --- a/lib/plugin/hopeThat.js +++ /dev/null @@ -1,122 +0,0 @@ -const recorder = require('../recorder') -const { debug } = require('../output') - -const defaultConfig = { - registerGlobal: true, -} - -/** - * Adds a global `hopeThat` function for soft assertions. - * - * Steps executed inside `hopeThat` will not fail the test when they fail; - * instead, they will return `true` or `false`. - * - * Enable this plugin in `codecept.conf.js`: - * - * ```js - * plugins: { - * hopeThat: { - * enabled: true - * } - * } - * ``` - * - * ### Usage - * - * Use `hopeThat` in your tests for conditional assertions: - * - * ```js - * const result = await hopeThat(() => I.see('Welcome')); - * - * // If the text "Welcome" is on the page, result => true - * // If the text "Welcome" is not on the page, result => false - * ``` - * - * This utility disables the `retryFailedStep` plugin for steps inside its block. - * - * #### Use Cases - * - * - Perform multiple conditional assertions in a test. - * - Handle scenarios with A/B testing on a website. - * - Handle unexpected elements, such as "Accept Cookie" banners. - * - * #### Examples - * - * ##### Multiple Conditional Assertions - * - * Add the assertion library: - * ```js - * const assert = require('assert'); - * ``` - * - * Use `hopeThat` with assertions: - * ```js - * const result1 = await hopeThat(() => I.see('Hello, user')); - * const result2 = await hopeThat(() => I.seeElement('.welcome')); - * assert.ok(result1 && result2, 'Assertions were not successful'); - * ``` - * - * ##### Optional Click - * - * ```js - * I.amOnPage('/'); - * hopeThat(() => I.click('Agree', '.cookies')); - * ``` - * - * ### Configuration - * - * - `registerGlobal` (boolean, default: `true`) — Registers `hopeThat` globally. - * - * If `registerGlobal` is `false`, use `hopeThat` via the plugin: - * - * ```js - * const hopeThat = codeceptjs.container.plugins('hopeThat'); - * ``` - * - * @param {Object} config - Configuration object. - * @param {boolean} [config.registerGlobal=true] - Whether to register `hopeThat` globally. - * @returns {Function} hopeThat - The soft assertion function. - */ -module.exports = function (config) { - config = Object.assign(defaultConfig, config) - - if (config.registerGlobal) { - global.hopeThat = hopeThat - } - return hopeThat -} - -function hopeThat(callback) { - let result = false - return recorder.add( - 'hopeThat', - () => { - recorder.session.start('hopeThat') - process.env.HOPE_THAT = 'true' - callback() - recorder.add(() => { - result = true - recorder.session.restore('hopeThat') - return result - }) - recorder.session.catch(err => { - result = false - const msg = err.inspect ? err.inspect() : err.toString() - debug(`Unsuccessful assertion > ${msg}`) - recorder.session.restore('hopeThat') - return result - }) - return recorder.add( - 'result', - () => { - process.env.HOPE_THAT = undefined - return result - }, - true, - false, - ) - }, - false, - false, - ) -} diff --git a/test/unit/hopeThat_test.js b/test/unit/hopeThat_test.js new file mode 100644 index 000000000..738f29152 --- /dev/null +++ b/test/unit/hopeThat_test.js @@ -0,0 +1,27 @@ +const { expect } = require('chai') +const { hopeThat } = require('../../lib/effects') +const recorder = require('../../lib/recorder') + +describe('effects', () => { + describe('hopeThat', () => { + beforeEach(() => { + recorder.start() + }) + + it('should execute command on success', async () => { + const ok = await hopeThat(() => recorder.add(() => 5)) + expect(true).is.equal(ok) + return recorder.promise() + }) + + it('should execute command on fail', async () => { + const notOk = await hopeThat(() => + recorder.add(() => { + throw new Error('Ups') + }), + ) + expect(false).is.equal(notOk) + return recorder.promise() + }) + }) +}) diff --git a/test/unit/plugin/hopeThat_test.js b/test/unit/plugin/hopeThat_test.js deleted file mode 100644 index 0bc4e4766..000000000 --- a/test/unit/plugin/hopeThat_test.js +++ /dev/null @@ -1,28 +0,0 @@ -let expect -import('chai').then(chai => { - expect = chai.expect -}) -const hopeThat = require('../../../lib/plugin/hopeThat')() -const recorder = require('../../../lib/recorder') - -describe('hopeThat plugin', () => { - beforeEach(() => { - recorder.start() - }) - - it('should execute command on success', async () => { - const ok = await hopeThat(() => recorder.add(() => 5)) - expect(true).is.equal(ok) - return recorder.promise() - }) - - it('should execute command on fail', async () => { - const notOk = await hopeThat(() => - recorder.add(() => { - throw new Error('Ups') - }), - ) - expect(false).is.equal(notOk) - return recorder.promise() - }) -})