diff --git a/lib/effects.js b/lib/effects.js index f5b8890cb..3e3a7c462 100644 --- a/lib/effects.js +++ b/lib/effects.js @@ -1,84 +1,33 @@ const recorder = require('./recorder') const { debug } = require('./output') const store = require('./store') +const event = require('./event') /** - * @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 - * ``` + * A utility function for CodeceptJS tests that acts as a soft assertion. + * Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately. * * @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. + * @param {Function} callback - The callback function containing the logic to validate. + * This function should perform the desired assertion or condition check. + * @returns {Promise} A promise resolving to `true` if the assertion or condition was successful, + * or `false` if an error occurred. + * + * @description + * - Designed for use in CodeceptJS tests as a "soft assertion." + * Unlike standard assertions, it does not stop the test execution on failure. + * - Starts a new recorder session named 'hopeThat' and manages state restoration. + * - Logs errors and attaches them as notes to the test, enabling post-test reporting of soft assertion failures. + * - Resets the `store.hopeThat` flag after the execution, ensuring clean state for subsequent operations. * * @example - * // Multiple Conditional Assertions - * const assert = require('assert'); - * const { hopeThat } = require('codeceptjs/effects'); + * const { hopeThat } = require('codeceptjs/effects') + * await hopeThat(() => { + * I.see('Welcome'); // Perform a soft assertion + * }); * - * 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')); + * @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test. */ async function hopeThat(callback) { if (store.dryRun) return @@ -100,6 +49,9 @@ async function hopeThat(callback) { result = false const msg = err.inspect ? err.inspect() : err.toString() debug(`Unsuccessful assertion > ${msg}`) + event.dispatcher.once(event.test.finished, test => { + test.notes.push({ type: 'conditionalError', text: msg }) + }) recorder.session.restore(sessionName) return result }) @@ -118,6 +70,149 @@ async function hopeThat(callback) { ) } +/** + * A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval. + * + * @async + * @function retryTo + * @param {Function} callback - The function to execute, which will be retried upon failure. + * Receives the current retry count as an argument. + * @param {number} maxTries - The maximum number of attempts to retry the callback. + * @param {number} [pollInterval=200] - The delay (in milliseconds) between retry attempts. + * @returns {Promise} A promise that resolves when the callback executes successfully, or rejects after reaching the maximum retries. + * + * @description + * - This function is designed for use in CodeceptJS tests to handle intermittent or flaky test steps. + * - Starts a new recorder session for each retry attempt, ensuring proper state management and error handling. + * - Logs errors and retries the callback until it either succeeds or the maximum number of attempts is reached. + * - Restores the session state after each attempt, whether successful or not. + * + * @example + * const { hopeThat } = require('codeceptjs/effects') + * await retryTo((tries) => { + * if (tries < 3) { + * I.see('Non-existent element'); // Simulates a failure + * } else { + * I.see('Welcome'); // Succeeds on the 3rd attempt + * } + * }, 5, 300); // Retry up to 5 times, with a 300ms interval + * + * @throws Will reject with the last error encountered if the maximum retries are exceeded. + */ +async function retryTo(callback, maxTries, pollInterval = 200) { + const sessionName = 'retryTo' + + return new Promise((done, reject) => { + let tries = 1 + + function handleRetryException(err) { + recorder.throw(err) + reject(err) + } + + const tryBlock = async () => { + tries++ + recorder.session.start(`${sessionName} ${tries}`) + try { + await callback(tries) + } catch (err) { + handleRetryException(err) + } + + // Call done if no errors + recorder.add(() => { + recorder.session.restore(`${sessionName} ${tries}`) + done(null) + }) + + // Catch errors and retry + recorder.session.catch(err => { + recorder.session.restore(`${sessionName} ${tries}`) + if (tries <= maxTries) { + debug(`Error ${err}... Retrying`) + recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval)) + } else { + // if maxTries reached + handleRetryException(err) + } + }) + } + + recorder.add(sessionName, tryBlock).catch(err => { + console.error('An error occurred:', err) + done(null) + }) + }) +} + +/** + * A CodeceptJS utility function to attempt a step or callback without failing the test. + * If the step fails, the test continues execution without interruption, and the result is logged. + * + * @async + * @function tryTo + * @param {Function} callback - The function to execute, which may succeed or fail. + * This function contains the logic to be attempted. + * @returns {Promise} A promise resolving to `true` if the step succeeds, or `false` if it fails. + * + * @description + * - Useful for scenarios where certain steps are optional or their failure should not interrupt the test flow. + * - Starts a new recorder session named 'tryTo' for isolation and error handling. + * - Captures errors during execution and logs them for debugging purposes. + * - Ensures the `store.tryTo` flag is reset after execution to maintain a clean state. + * + * @example + * const { tryTo } = require('codeceptjs/effects') + * const wasSuccessful = await tryTo(() => { + * I.see('Welcome'); // Attempt to find an element on the page + * }); + * + * if (!wasSuccessful) { + * I.say('Optional step failed, but test continues.'); + * } + * + * @throws Will handle errors internally, logging them and returning `false` as the result. + */ +async function tryTo(callback) { + if (store.dryRun) return + const sessionName = 'tryTo' + + let result = false + return recorder.add( + sessionName, + () => { + recorder.session.start(sessionName) + store.tryTo = 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 try > ${msg}`) + recorder.session.restore(sessionName) + return result + }) + return recorder.add( + 'result', + () => { + store.tryTo = undefined + return result + }, + true, + false, + ) + }, + false, + false, + ) +} + module.exports = { hopeThat, + retryTo, + tryTo, } diff --git a/lib/plugin/retryTo.js b/lib/plugin/retryTo.js index 3d26abc00..ed9c3cdfc 100644 --- a/lib/plugin/retryTo.js +++ b/lib/plugin/retryTo.js @@ -1,127 +1,19 @@ -const recorder = require('../recorder') -const { debug } = require('../output') - -const defaultConfig = { - registerGlobal: true, - pollInterval: 200, -} - -/** - * - * - * Adds global `retryTo` which retries steps a few times before failing. - * - * Enable this plugin in `codecept.conf.js` (enabled by default for new setups): - * - * ```js - * plugins: { - * retryTo: { - * enabled: true - * } - * } - * ``` - * - * Use it in your tests: - * - * ```js - * // retry these steps 5 times before failing - * await retryTo((tryNum) => { - * I.switchTo('#editor frame'); - * I.click('Open'); - * I.see('Opened') - * }, 5); - * ``` - * Set polling interval as 3rd argument (200ms by default): - * - * ```js - * // retry these steps 5 times before failing - * await retryTo((tryNum) => { - * I.switchTo('#editor frame'); - * I.click('Open'); - * I.see('Opened') - * }, 5, 100); - * ``` - * - * Default polling interval can be changed in a config: - * - * ```js - * plugins: { - * retryTo: { - * enabled: true, - * pollInterval: 500, - * } - * } - * ``` - * - * Disables retryFailedStep plugin for steps inside a block; - * - * Use this plugin if: - * - * * you need repeat a set of actions in flaky tests - * * iframe was not rendered and you need to retry switching to it - * - * - * #### Configuration - * - * * `pollInterval` - default interval between retries in ms. 200 by default. - * * `registerGlobal` - to register `retryTo` function globally, true by default - * - * If `registerGlobal` is false you can use retryTo from the plugin: - * - * ```js - * const retryTo = codeceptjs.container.plugins('retryTo'); - * ``` - * - */ -module.exports = function (config) { - config = Object.assign(defaultConfig, config) - function retryTo(callback, maxTries, pollInterval = config.pollInterval) { - return new Promise((done, reject) => { - let tries = 1 - - function handleRetryException(err) { - recorder.throw(err) - reject(err) - } - - const tryBlock = async () => { - tries++ - recorder.session.start(`retryTo ${tries}`) - try { - await callback(tries) - } catch (err) { - handleRetryException(err) - } - - // Call done if no errors - recorder.add(() => { - recorder.session.restore(`retryTo ${tries}`) - done(null) - }) - - // Catch errors and retry - recorder.session.catch(err => { - recorder.session.restore(`retryTo ${tries}`) - if (tries <= maxTries) { - debug(`Error ${err}... Retrying`) - recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval)) - } else { - // if maxTries reached - handleRetryException(err) - } - }) - } - - recorder.add('retryTo', tryBlock).catch(err => { - console.error('An error occurred:', err) - done(null) - }) - }) - } - - if (config.registerGlobal) { - global.retryTo = retryTo - } - - return retryTo +module.exports = function () { + console.log(` +Deprecated Warning: 'retryTo' has been moved to the effects module. +You should update your tests to use it as follows: + +\`\`\`javascript +const { retryTo } = require('codeceptjs/effects'); + +// Example: Retry these steps 5 times before failing +await retryTo((tryNum) => { + I.switchTo('#editor frame'); + I.click('Open'); + I.see('Opened'); +}, 5); +\`\`\` + +For more details, refer to the documentation. + `) } diff --git a/lib/plugin/tryTo.js b/lib/plugin/tryTo.js index b71448172..195cea28a 100644 --- a/lib/plugin/tryTo.js +++ b/lib/plugin/tryTo.js @@ -1,115 +1,17 @@ -const recorder = require('../recorder') -const { debug } = require('../output') +module.exports = function () { + console.log(` +Deprecated Warning: 'tryTo' has been moved to the effects module. +You should update your tests to use it as follows: -const defaultConfig = { - registerGlobal: true, -} - -/** - * - * - * Adds global `tryTo` function in which all failed steps won't fail a test but will return true/false. - * - * Enable this plugin in `codecept.conf.js` (enabled by default for new setups): - * - * ```js - * plugins: { - * tryTo: { - * enabled: true - * } - * } - * ``` - * Use it in your tests: - * - * ```js - * const result = await tryTo(() => I.see('Welcome')); - * - * // if text "Welcome" is on page, result => true - * // if text "Welcome" is not on page, result => false - * ``` - * - * Disables retryFailedStep plugin for steps inside a block; - * - * Use this plugin if: - * - * * you need to perform multiple assertions inside a test - * * there is A/B testing on a website you test - * * there is "Accept Cookie" banner which may surprisingly appear on a page. - * - * #### Usage - * - * #### Multiple Conditional Assertions - * - * ```js - * - * Add assert requires first: - * ```js - * const assert = require('assert'); - * ``` - * Then use the assertion: - * const result1 = await tryTo(() => I.see('Hello, user')); - * const result2 = await tryTo(() => I.seeElement('.welcome')); - * assert.ok(result1 && result2, 'Assertions were not succesful'); - * ``` - * - * ##### Optional click - * - * ```js - * I.amOnPage('/'); - * tryTo(() => I.click('Agree', '.cookies')); - * ``` - * - * #### Configuration - * - * * `registerGlobal` - to register `tryTo` function globally, true by default - * - * If `registerGlobal` is false you can use tryTo from the plugin: - * - * ```js - * const tryTo = codeceptjs.container.plugins('tryTo'); - * ``` - * - */ -module.exports = function (config) { - config = Object.assign(defaultConfig, config) +\`\`\`javascript +const { tryTo } = require('codeceptjs/effects'); - if (config.registerGlobal) { - global.tryTo = tryTo - } - return tryTo -} +// Example: failed step won't fail a test but will return true/false +await tryTo(() => { + I.switchTo('#editor frame'); +}); +\`\`\` -function tryTo(callback) { - let result = false - return recorder.add( - 'tryTo', - () => { - recorder.session.start('tryTo') - process.env.TRY_TO = 'true' - callback() - recorder.add(() => { - result = true - recorder.session.restore('tryTo') - return result - }) - recorder.session.catch(err => { - result = false - const msg = err.inspect ? err.inspect() : err.toString() - debug(`Unsuccessful try > ${msg}`) - recorder.session.restore('tryTo') - return result - }) - return recorder.add( - 'result', - () => { - process.env.TRY_TO = undefined - return result - }, - true, - false, - ) - }, - false, - false, - ) +For more details, refer to the documentation. + `) } diff --git a/test/acceptance/codecept.Playwright.retryTo.js b/test/acceptance/codecept.Playwright.retryTo.js deleted file mode 100644 index 696fcfbab..000000000 --- a/test/acceptance/codecept.Playwright.retryTo.js +++ /dev/null @@ -1,46 +0,0 @@ -const TestHelper = require('../support/TestHelper') - -module.exports.config = { - tests: './*_test.js', - timeout: 10000, - output: './output', - grep: '@Playwright', - helpers: { - Playwright: { - url: TestHelper.siteUrl(), - show: false, - restart: process.env.BROWSER_RESTART || false, - browser: process.env.BROWSER || 'chromium', - ignoreHTTPSErrors: true, - webkit: { - ignoreHTTPSErrors: true, - }, - }, - JSONResponse: { - requestHelper: 'Playwright', - }, - ScreenshotSessionHelper: { - require: '../support/ScreenshotSessionHelper.js', - outputPath: 'test/acceptance/output', - }, - Expect: { - require: '@codeceptjs/expect-helper', - }, - }, - include: {}, - bootstrap: false, - mocha: {}, - plugins: { - screenshotOnFail: { - enabled: true, - }, - retryTo: { - enabled: true, - }, - }, - name: 'acceptance', - gherkin: { - features: './gherkin/*.feature', - steps: ['./gherkin/steps.js'], - }, -} diff --git a/test/plugin/plugin_test.js b/test/plugin/plugin_test.js index 4757ae4e7..7fe594a49 100644 --- a/test/plugin/plugin_test.js +++ b/test/plugin/plugin_test.js @@ -14,24 +14,6 @@ describe('CodeceptJS plugin', function () { process.chdir(codecept_dir) }) - it('should retry the await/non await steps', done => { - exec(`${config_run_config('codecept.Playwright.retryTo.js', '@plugin')} --verbose`, (err, stdout) => { - const lines = stdout.split('\n') - expect(lines).toEqual(expect.arrayContaining([expect.stringContaining('... Retrying')])) - expect(err).toBeFalsy() - done() - }) - }) - - it('should failed before the retryTo instruction', done => { - exec(`${config_run_config('codecept.Playwright.retryTo.js', 'Should be succeed')} --verbose`, (err, stdout) => { - expect(stdout).toContain('locator.waitFor: Timeout 1000ms exceeded.') - expect(stdout).toContain('[1] Error | Error: element (.nothing) still not visible after 1 sec') - expect(err).toBeTruthy() - done() - }) - }) - it('should generate the coverage report', done => { exec(`${config_run_config('codecept.Playwright.coverage.js', '@coverage')} --debug`, (err, stdout) => { const lines = stdout.split('\n') @@ -40,13 +22,4 @@ describe('CodeceptJS plugin', function () { done() }) }) - - it('should retry to failure', done => { - exec(`${config_run_config('codecept.Playwright.retryTo.js', 'Should fail after reached max retries')} --verbose`, (err, stdout) => { - const lines = stdout.split('\n') - expect(lines).toEqual(expect.arrayContaining([expect.stringContaining('Custom pluginRetryTo Error')])) - expect(err).toBeTruthy() - done() - }) - }) }) diff --git a/test/unit/effects_test.js b/test/unit/effects_test.js new file mode 100644 index 000000000..6a021d595 --- /dev/null +++ b/test/unit/effects_test.js @@ -0,0 +1,94 @@ +const { expect } = require('chai') +const { hopeThat, retryTo, tryTo } = require('../../lib/effects') +const recorder = require('../../lib/recorder') + +describe('effects', () => { + describe('hopeThat', () => { + beforeEach(() => { + recorder.reset() + 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() + }) + }) + + describe('tryTo', () => { + beforeEach(() => { + recorder.reset() + recorder.start() + }) + + it('should execute command on success', async () => { + const ok = await tryTo(() => recorder.add(() => 5)) + expect(ok).to.be.equal(true) + return recorder.promise() + }) + + it('should execute command on fail', async () => { + const notOk = await tryTo(() => + recorder.add(() => { + throw new Error('Ups') + }), + ) + expect(false).is.equal(notOk) + return recorder.promise() + }) + }) + + describe('retryTo', () => { + beforeEach(() => { + recorder.reset() + recorder.start() + }) + + it('should execute command on success', async () => { + let counter = 0 + await retryTo( + () => + recorder.add(() => { + counter++ + }), + 5, + ) + expect(counter).is.equal(1) + return recorder.promise() + }) + + it('should execute few times command on fail', async () => { + let counter = 0 + let errorCaught = false + try { + await retryTo( + () => { + recorder.add(() => counter++) + recorder.add(() => { + throw new Error('Ups') + }) + }, + 5, + 10, + ) + await recorder.promise() + } catch (err) { + errorCaught = true + expect(err.message).to.eql('Ups') + } + expect(counter).to.equal(5) + expect(errorCaught).is.true + }) + }) +}) diff --git a/test/unit/hopeThat_test.js b/test/unit/hopeThat_test.js deleted file mode 100644 index 738f29152..000000000 --- a/test/unit/hopeThat_test.js +++ /dev/null @@ -1,27 +0,0 @@ -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/mocha/asyncWrapper_test.js b/test/unit/mocha/asyncWrapper_test.js index 284e01641..24528eaf8 100644 --- a/test/unit/mocha/asyncWrapper_test.js +++ b/test/unit/mocha/asyncWrapper_test.js @@ -35,9 +35,9 @@ describe('AsyncWrapper', () => { let counter = 0 test.fn = () => { recorder.add('test', async () => { - await counter++ - await counter++ - await counter++ + counter++ + counter++ + counter++ counter++ }) } @@ -60,6 +60,7 @@ describe('AsyncWrapper', () => { }) it('should fire events', () => { + recorder.reset() testWrapper(test).fn(() => null) expect(started.called).is.ok teardown() diff --git a/test/unit/plugin/retryto_test.js b/test/unit/plugin/retryto_test.js deleted file mode 100644 index 213dd577b..000000000 --- a/test/unit/plugin/retryto_test.js +++ /dev/null @@ -1,42 +0,0 @@ -let expect -import('chai').then(chai => { - expect = chai.expect -}) -const retryTo = require('../../../lib/plugin/retryTo')() -const recorder = require('../../../lib/recorder') - -describe('retryTo plugin', () => { - beforeEach(() => { - recorder.start() - }) - - it('should execute command on success', async () => { - let counter = 0 - await retryTo(() => recorder.add(() => counter++), 5) - expect(counter).is.equal(1) - return recorder.promise() - }) - - it('should execute few times command on fail', async () => { - let counter = 0 - let errorCaught = false - try { - await retryTo( - () => { - recorder.add(() => counter++) - recorder.add(() => { - throw new Error('Ups') - }) - }, - 5, - 10, - ) - await recorder.promise() - } catch (err) { - errorCaught = true - expect(err.message).to.eql('Ups') - } - expect(counter).to.equal(5) - expect(errorCaught).is.true - }) -}) diff --git a/test/unit/plugin/subtitles_test.js b/test/unit/plugin/subtitles_test.js index a20b5459c..60db3691d 100644 --- a/test/unit/plugin/subtitles_test.js +++ b/test/unit/plugin/subtitles_test.js @@ -28,7 +28,7 @@ describe('subtitles', () => { it('should not capture subtitle as video artifact was missing', async () => { const fsMock = sinon.mock(fsPromises) - const test = {} + const test = { notes: [] } fsMock.expects('writeFile').never() @@ -44,6 +44,7 @@ describe('subtitles', () => { const fsMock = sinon.mock(fsPromises) const test = { + notes: [], artifacts: { video: '../../lib/output/failedTest1.webm', }, @@ -71,6 +72,7 @@ describe('subtitles', () => { const fsMock = sinon.mock(fsPromises) const test = { + notes: [], artifacts: { video: '../../lib/output/failedTest1.webm', }, @@ -101,10 +103,11 @@ describe('subtitles', () => { fsMock.verify() }) - it('should capture seperate steps for separate tests', async () => { + it('should capture separate steps for separate tests', async () => { const fsMock = sinon.mock(fsPromises) const test1 = { + notes: [], artifacts: { video: '../../lib/output/failedTest1.webm', }, @@ -149,6 +152,7 @@ describe('subtitles', () => { }), ) const test2 = { + notes: [], artifacts: { video: '../../lib/output/failedTest2.webm', }, diff --git a/test/unit/plugin/tryTo_test.js b/test/unit/plugin/tryTo_test.js deleted file mode 100644 index efd8ebdea..000000000 --- a/test/unit/plugin/tryTo_test.js +++ /dev/null @@ -1,28 +0,0 @@ -let expect -import('chai').then(chai => { - expect = chai.expect -}) -const tryTo = require('../../../lib/plugin/tryTo')() -const recorder = require('../../../lib/recorder') - -describe('tryTo plugin', () => { - beforeEach(() => { - recorder.start() - }) - - it('should execute command on success', async () => { - const ok = await tryTo(() => recorder.add(() => 5)) - expect(true).is.equal(ok) - return recorder.promise() - }) - - it('should execute command on fail', async () => { - const notOk = await tryTo(() => - recorder.add(() => { - throw new Error('Ups') - }), - ) - expect(false).is.equal(notOk) - return recorder.promise() - }) -})