CodeceptJS
CodeceptJS copied to clipboard
retryTo plugin trigger unhandled promise rejection
Helloveryone !
What are you trying to achieve?
I try to use the plugin retryTo with a small function to retry when an error occurs.
What do you get instead?
Instead, the retryTo don't work, and the error throw is an unhandled promise rejection, who freeze the process. All error, even outside the block retryTo, cause this unhandled promise rejectio. If I disable the plugin in the config file, everything is fine. I try to remove the retryFailedStep too, but don't change anything.
Provide console output if related. Use
--verbosemode for more details.
pageConnexionUtilisateur: login "sweetyid", "mybigpasswordyouknow"
Je suis sur la page "/"
» [Browser:Verbose] [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o
Je redimensionne la fenêtre 1920, 1080
Je remplis le champ "Identifiant", "sweetyid"
Je remplis le champ "Mot de passe", *****
Je clique sur "Se connecter"
» [Browser:Verbose] [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o
Je redimensionne la fenêtre 1920, 1080
pageMenuPrincipal: changeStructure "RECETTE"
Je clique sur "//li[contains(@class,"dropdown") and a[contains(@class,"navbar-brand")]]"
[1] Retrying... Attempt #2
[1] Retrying... Attempt #3
[1] Retrying... Attempt #4
[1] Retrying... Attempt #5
[1] Retrying... Attempt #6
[1] Error | Error: Clickable element "//li[contains(@class,"dropdown") and a[contains(@class,"navbar-brand")]]" was not found by text|CSS|XPath
[1] Error | Error: Clickable element "//li[contains(@class,"dropdown") and a[contains(@class,"navbar-brand")]]" was not found by text|CSS|XPath
[1] Error | Error: Clickable element "//li[contains(@class,"dropdown") and a[contains(@class,"navbar-brand")]]" was not found by text|CSS|XPath
(node:19756) UnhandledPromiseRejectionWarning: Error: Clickable element "//li[contains(@class,"dropdown") and a[contains(@class,"navbar-brand")]]" was not found by text|CSS|XPath
at new ElementNotFound (C:\Docker\billy_docker_dev_test\billy_back\app\framework\node_modules\codeceptjs\lib\helper\errors\ElementNotFound.js:14:11)
at assertElementExists (C:\Docker\billy_docker_dev_test\billy_back\app\framework\node_modules\codeceptjs\lib\helper\Playwright.js:2722:11)
at Playwright.proceedClick (C:\Docker\billy_docker_dev_test\billy_back\app\framework\node_modules\codeceptjs\lib\helper\Playwright.js:2498:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:19756) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 7)
(node:19756) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Provide test source code if related
async allerVersPageProjets() {
await retryTo((tryNum) => {
I.retry(5).click(this.crmMenu);
I.retry(5).click(this.projetsButton);
I.wait(2);
I.retry(5).switchToNextTab();
//I.retry(5).switchToNextTab();
I.retry(5).see('Projets');
I.retry(5).resizeWindow(1920, 1080);
}, 5);
}
Details
- CodeceptJS version: tested from 3.2.0 to 3.3.0
- NodeJS Version: 14
- Operating System: Windows
- Playwright tested from 1.12.3 to the latest (But i use only the 1.12.3 because drag and drop don't work on later versions for my case)
- Configuration file:
const { setHeadlessWhen } = require('@codeceptjs/configure');
exports.config = {
tests: './local/*_test.js',
output: './output',
helpers: {
FileSystem: {},
Playwright: {
url: 'http://localhost',
show: true,
browser: 'chromium',
windowSize: '1920x1080',
restart: true,
waitForAction: 1000,
getPageTimeout: 10000,
waitForNavigation: 'networkidle0',
},
},
include: {
userCommercial: './local/model/users/user-commercial.js',
userCommercial2: './local/model/users/user-commercial2.js',
userBillyMonday: './local/model/users/user-billy-monday.js',
userQualificateur: './local/model/users/user-qualificateur.js',
userReleveur: './local/model/users/user-releveur.js',
userAdmin: './local/model/users/user-admin.js',
userADV: './local/model/users/user-adv.js',
userMetreur: './local/model/users/user-metreur.js',
userTec: './local/model/users/user-tec.js',
userVerificateur: './local/model/users/user-verificateur.js',
userIfmResponsable: './local/model/users/user-ifm-responsable.js',
userOelResponsable: './local/model/users/user-oel-responsable.js',
userThermieResponsable: './local/model/users/user-thermie-responsable.js',
userResponsableProd: './local/model/users/user-responsable-prod.js',
userResponsableMaintenance: './local/model/users/user-responsable-maintenance.js',
userValerieGuiboux: './local/model/users/user-valerie-guiboux.js',
tabInfosProjets: './local/pages/partenaires/page-fiche-projet/tab-infos-projet.js',
tabDevis: './local/pages/partenaires/page-fiche-projet/tab-espace-devis.js',
tabInfosPlateforme: './local/pages/partenaires/page-fiche-projet/tab-infos-plateforme.js',
tabTaches: './local/pages/partenaires/page-fiche-projet/tab-taches.js',
tabDocuments: './local/pages/partenaires/page-fiche-projet/tab-documents.js',
pageMenuPrincipal: './local/pages/page-menu-principal.js',
pageConnexionUtilisateur: './local/pages/page-connexion-utilisateur.js',
pageListeProjets: './local/pages/partenaires/page-liste-projets.js',
pageDessin: './local/pages/partenaires/page-dessin.js',
pageListeProjets: './local/pages/partenaires/page-liste-projets.js',
pagePlanningPose: './local/pages/partenaires/page-planning-pose.js',
pageEspaceMetreur: './local/pages/partenaires/page-espace-metreur.js',
pageFacturation: './local/pages/partenaires/page-facturation.js',
pageParametres: './local/pages/partenaires/page-parametres.js',
pagePlateforme: './local/pages/partenaires/page-plateforme.js',
pageTachesDossier: './local/pages/partenaires/page-taches-dossier.js',
pagePlanningReleveur: './local/pages/partenaires/page-planning-releveur.js',
toolFakerIdentity: './local/tools/faker-identity.js',
pageMonday: './local/tools/page-monday.js',
pageCommande: './local/pages/logs/commandes/page-commande.js',
pagePlanningProduction: './local/pages/logs/commandes/page-planning-production.js',
pageOrdreFabrication: './local/pages/logs/commandes/page-ordre-fabrication.js',
pageActionsCommerciales: './local/pages/partenaires/page-actions-com.js',
},
mode: 'production',
bootstrap: null,
mocha: {},
name: 'Test',
translation: 'fr-FR',
plugins: {
retryTo: {
enabled: true,
},
tryTo: {
enabled: true,
},
screenshotOnFail: {
enabled: true,
},
pauseOnFail: {},
},
};
I updated node to v16, the error has changed :
Error: Clickable element "//li[contains(@class,"dropdown") and a[contains(@class,"navbar-brand")]]" was not found by text|CSS|XPath
(node:24092) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 7)
(Use `node --trace-warnings ...` to show where the warning was created)
And this time, the test print the error in the normal way, but the process still freeze. Can't use the plugin pauseOnFail by example, because after the error the process stop working.
After many test, i made a simple test that you can make :
Scenario('TEST GOOGLE', async ({ I }) => {
I.amOnPage('http://www.google.fr');
I.see('I NEVER GONNA GIVE YOU UP');
await retryTo((tryNum) => {
I.say(tryNum);
}, 5);
});
I start this test with :
npx codeceptjs run --grep "TEST GOOGLE" --verbose -p pauseOnFail
It throw me this error :
Error
(node:17636) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 2)
(Use `node --trace-warnings ...` to show where the warning was created)
And the pauseOnFail never happened.
The process stop correctly if i remove "restart : true" from config file. But it throws the same error.
@umadgen I had the same issue and desperately needed retryTo functionality. I realized that if a step before await retryTo fails but is not awaited, you will get "unhandled promise rejection".
So:
Scenario('This will not work', async ({ I }) => {
I.see('nonexisting string');
await retryTo((tryNum) => {
I.say(tryNum);
}, 5);
});
Scenario('This will work', async ({ I }) => {
await I.see('nonexisting string');
await retryTo((tryNum) => {
I.say(tryNum);
}, 5);
});
If a step that fails (i.e. I.see()) is not awaited, script will continue and call await retryTo (and that will call recorder.add()). Once an un-awaited step fails it will throw error in recorder.add() and that is not handeled.
I did not want to add await before every call, so following the code in https://github.com/codeceptjs/CodeceptJS/blob/3.x/lib/plugin/retryTo.js I wrote this custom helper to retry steps:
const { v1 } = require('uuid');
const { recorder, output } = require('codeceptjs');
// Code in this file is base on:
// https://github.com/codeceptjs/CodeceptJS/blob/3.x/lib/plugin/retryTo.js
/**
* Function to retry a sequence of steps if one fails
* @param {*} I : self explanatory
* @param {*} callback : function that excepts I and contains sequence of steps
* @param {*} maxTries : number of retries. Defaults to 1
* @param {*} retryOffset : time in ms to wait between retries
*
* Example call:
* await retrySteps(I, (I) => {
* I.click('#some-id');
* I.click('Some title');
* }, 2, 1000);
*/
async function retrySteps(I, callback, maxTries, retryOffset) {
let tries = 1;
var retryBlockId = v1().substring(0, 4);
return new Promise((finish) => {
const tryBlock = () => {
recorder.session.start(`retrySteps ${retryBlockId} ${tries}`);
callback(I, tries);
recorder.add(() => {
recorder.session.restore(`retrySteps ${retryBlockId} ${tries}`);
finish(null);
});
recorder.session.catch((e) => {
recorder.session.restore(`retrySteps ${retryBlockId} ${tries}`);
tries++;
if (tries <= maxTries) {
output.debug(`Error ${e}... Retrying #${tries}`);
recorder.add(`retrySteps ${retryBlockId}`, () => {
I.wait((retryOffset || 0) / 1000);
tryBlock();
});
} else {
finish(e);
}
});
};
recorder
.add(`retrySteps ${retryBlockId}`, tryBlock)
.catch((e) => finish(e));
}).then((err) => {
if (err) recorder.throw(err);
});
}
module.exports = {
retrySteps
};
Closed for now! Feel free to reopen if you still encounter the issue with latest version and would be nice to provide a sample code to reproduce the issue.