Skip to content

Commit

Permalink
Improved passing step options (#4733)
Browse files Browse the repository at this point in the history
* initial implementation for step params

* refactored steps

* refactored steps

* fixed refactoring

* fixed typings

* fixed typings

* added steps to export

* added step timeouts tests

* deprecated I.limitTime and I.retry

* fix failed timeout tests

* added test for case insensitive opt

* fixed flakiness of timeout tests

---------

Co-authored-by: DavertMik <[email protected]>
  • Loading branch information
DavertMik and DavertMik authored Jan 13, 2025
1 parent f5cbe20 commit 7b97a55
Show file tree
Hide file tree
Showing 32 changed files with 1,108 additions and 756 deletions.
586 changes: 291 additions & 295 deletions docs/helpers/Appium.md

Large diffs are not rendered by default.

139 changes: 47 additions & 92 deletions lib/actor.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const Step = require('./step');
const { MetaStep } = require('./step');
const container = require('./container');
const { methodsOfObject } = require('./utils');
const recorder = require('./recorder');
const event = require('./event');
const store = require('./store');
const output = require('./output');
const Step = require('./step')
const MetaStep = require('./step/meta')
const recordStep = require('./step/record')
const container = require('./container')
const { methodsOfObject } = require('./utils')
const { TIMEOUT_ORDER } = require('./step/timeout')
const recorder = require('./recorder')
const event = require('./event')
const store = require('./store')
const output = require('./output')

/**
* @interface
Expand All @@ -21,13 +23,13 @@ class Actor {
* ⚠️ returns a promise which is synchronized internally by recorder
*/
async say(msg, color = 'cyan') {
const step = new Step('say', 'say');
step.status = 'passed';
const step = new Step('say', 'say')
step.status = 'passed'
return recordStep(step, [msg]).then(() => {
// this is backward compatibility as this event may be used somewhere
event.emit(event.step.comment, msg);
output.say(msg, `${color}`);
});
event.emit(event.step.comment, msg)
output.say(msg, `${color}`)
})
}

/**
Expand All @@ -38,14 +40,16 @@ class Actor {
* @inner
*/
limitTime(timeout) {
if (!store.timeouts) return this;
if (!store.timeouts) return this

console.log('I.limitTime() is deprecated, use step.timeout() instead')

event.dispatcher.prependOnceListener(event.step.before, step => {
output.log(`Timeout to ${step}: ${timeout}s`);
step.setTimeout(timeout * 1000, Step.TIMEOUT_ORDER.codeLimitTime);
});
output.log(`Timeout to ${step}: ${timeout}s`)
step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
})

return this;
return this
}

/**
Expand All @@ -55,11 +59,10 @@ class Actor {
* @inner
*/
retry(opts) {
if (opts === undefined) opts = 1;
recorder.retry(opts);
// remove retry once the step passed
recorder.add(() => event.dispatcher.once(event.step.finished, () => recorder.retries.pop()));
return this;
console.log('I.retry() is deprecated, use step.retry() instead')
const retryStep = require('./step/retry')
retryStep(opts)
return this
}
}

Expand All @@ -70,102 +73,54 @@ class Actor {
* @ignore
*/
module.exports = function (obj = {}) {
const actor = container.actor() || new Actor();
const actor = container.actor() || new Actor()

// load all helpers once container initialized
container.started(() => {
const translation = container.translation();
const helpers = container.helpers();
const translation = container.translation()
const helpers = container.helpers()

// add methods from enabled helpers
Object.values(helpers).forEach(helper => {
methodsOfObject(helper, 'Helper')
.filter(method => method !== 'constructor' && method[0] !== '_')
.forEach(action => {
const actionAlias = translation.actionAliasFor(action);
const actionAlias = translation.actionAliasFor(action)
if (!actor[action]) {
actor[action] = actor[actionAlias] = function () {
const step = new Step(helper, action);
const step = new Step(helper, action)
if (translation.loaded) {
step.name = actionAlias;
step.actor = translation.I;
step.name = actionAlias
step.actor = translation.I
}
// add methods to promise chain
return recordStep(step, Array.from(arguments));
};
return recordStep(step, Array.from(arguments))
}
}
});
});
})
})

// add translated custom steps from actor
Object.keys(obj).forEach(key => {
const actionAlias = translation.actionAliasFor(key);
const actionAlias = translation.actionAliasFor(key)
if (!actor[actionAlias]) {
actor[actionAlias] = actor[key];
actor[actionAlias] = actor[key]
}
});
})

container.append({
support: {
I: actor,
},
});
});
})
})
// store.actor = actor;
// add custom steps from actor
Object.keys(obj).forEach(key => {
const ms = new MetaStep('I', key);
ms.setContext(actor);
actor[key] = ms.run.bind(ms, obj[key]);
});

return actor;
};

function recordStep(step, args) {
step.status = 'queued';
step.setArguments(args);

// run async before step hooks
event.emit(event.step.before, step);

const task = `${step.name}: ${step.humanizeArgs()}`;
let val;

// run step inside promise
recorder.add(
task,
() => {
if (!step.startTime) {
// step can be retries
event.emit(event.step.started, step);
step.startTime = Date.now();
}
return (val = step.run(...args));
},
false,
undefined,
step.getTimeout(),
);

event.emit(event.step.after, step);

recorder.add('step passed', () => {
step.endTime = Date.now();
event.emit(event.step.passed, step, val);
event.emit(event.step.finished, step);
});

recorder.catchWithoutStop(err => {
step.status = 'failed';
step.endTime = Date.now();
event.emit(event.step.failed, step);
event.emit(event.step.finished, step);
throw err;
});

recorder.add('return result', () => val);
// run async after step hooks
const ms = new MetaStep('I', key)
ms.setContext(actor)
actor[key] = ms.run.bind(ms, obj[key])
})

return recorder.promise();
return actor
}
5 changes: 5 additions & 0 deletions lib/helper/Playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -3509,6 +3509,11 @@ async function proceedSee(assertType, text, context, strict = false) {
allText = await Promise.all(els.map(el => el.innerText()))
}

if (store?.currentStep?.opts?.ignoreCase === true) {
text = text.toLowerCase()
allText = allText.map(elText => elText.toLowerCase())
}

if (strict) {
return allText.map(elText => equals(description)[assertType](text, elText))
}
Expand Down
5 changes: 5 additions & 0 deletions lib/helper/Puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2769,6 +2769,11 @@ async function proceedSee(assertType, text, context, strict = false) {
allText = await Promise.all(els.map(el => el.getProperty('innerText').then(p => p.jsonValue())))
}

if (store?.currentStep?.opts?.ignoreCase === true) {
text = text.toLowerCase()
allText = allText.map(elText => elText.toLowerCase())
}

if (strict) {
return allText.map(elText => equals(description)[assertType](text, elText))
}
Expand Down
10 changes: 9 additions & 1 deletion lib/helper/WebDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Helper = require('@codeceptjs/helper')
const promiseRetry = require('promise-retry')
const stringIncludes = require('../assert/include').includes
const { urlEquals, equals } = require('../assert/equal')
const store = require('../store')
const { debug } = require('../output')
const { empty } = require('../assert/empty')
const { truth } = require('../assert/truth')
Expand Down Expand Up @@ -2698,7 +2699,14 @@ async function proceedSee(assertType, text, context, strict = false) {
const smartWaitEnabled = assertType === 'assert'
const res = await this._locate(withStrictLocator(context), smartWaitEnabled)
assertElementExists(res, context)
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
let selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))

// apply ignoreCase option
if (store?.currentStep?.opts?.ignoreCase === true) {
text = text.toLowerCase()
selected = selected.map(elText => elText.toLowerCase())
}

if (strict) {
if (Array.isArray(selected) && selected.length !== 0) {
return selected.map(elText => equals(description)[assertType](text, elText))
Expand Down
2 changes: 1 addition & 1 deletion lib/listener/globalTimeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const output = require('../output')
const recorder = require('../recorder')
const Config = require('../config')
const { timeouts } = require('../store')
const TIMEOUT_ORDER = require('../step').TIMEOUT_ORDER
const { TIMEOUT_ORDER } = require('../step/timeout')

module.exports = function () {
let timeout
Expand Down
3 changes: 3 additions & 0 deletions lib/listener/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ module.exports = function () {
event.dispatcher.on(event.step.started, step => {
step.startedAt = +new Date()
step.test = currentTest
store.currentStep = step
if (currentHook && Array.isArray(currentHook.steps)) {
return currentHook.steps.push(step)
}
Expand All @@ -84,5 +85,7 @@ module.exports = function () {
step.finishedAt = +new Date()
if (step.startedAt) step.duration = step.finishedAt - step.startedAt
debug(`Step '${step}' finished; Duration: ${step.duration || 0}ms`)
store.currentStep = null
store.stepOptions = null
})
}
2 changes: 1 addition & 1 deletion lib/plugin/stepTimeout.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const event = require('../event')
const TIMEOUT_ORDER = require('../step').TIMEOUT_ORDER
const { TIMEOUT_ORDER } = require('../step/timeout')

const defaultConfig = {
timeout: 150,
Expand Down
2 changes: 1 addition & 1 deletion lib/recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ module.exports = {
}
if (retry === undefined) retry = true
if (!running && !force) {
return
return Promise.resolve()
}
tasks.push(taskName)
debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`))
Expand Down
Loading

0 comments on commit 7b97a55

Please sign in to comment.