codeceptjs 3.4.1 → 3.5.1-2.beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -30
- package/bin/codecept.js +1 -1
- package/lib/actor.js +6 -3
- package/lib/ai.js +180 -0
- package/lib/cli.js +13 -3
- package/lib/codecept.js +8 -0
- package/lib/colorUtils.js +10 -0
- package/lib/command/definitions.js +2 -7
- package/lib/command/dryRun.js +11 -2
- package/lib/command/generate.js +46 -3
- package/lib/command/info.js +24 -0
- package/lib/command/init.js +64 -6
- package/lib/command/interactive.js +15 -1
- package/lib/command/run-multiple/collection.js +17 -5
- package/lib/command/run-multiple.js +4 -2
- package/lib/command/run-workers.js +68 -5
- package/lib/command/run.js +7 -0
- package/lib/command/workers/runTests.js +39 -0
- package/lib/container.js +13 -3
- package/lib/data/context.js +14 -6
- package/lib/event.js +4 -0
- package/lib/helper/ApiDataFactory.js +2 -1
- package/lib/helper/Appium.js +116 -29
- package/lib/helper/Expect.js +422 -0
- package/lib/helper/FileSystem.js +1 -1
- package/lib/helper/GraphQL.js +25 -0
- package/lib/helper/JSONResponse.js +4 -4
- package/lib/helper/Nightmare.js +10 -5
- package/lib/helper/OpenAI.js +126 -0
- package/lib/helper/Playwright.js +1298 -229
- package/lib/helper/Protractor.js +12 -7
- package/lib/helper/Puppeteer.js +204 -64
- package/lib/helper/REST.js +15 -5
- package/lib/helper/TestCafe.js +45 -10
- package/lib/helper/WebDriver.js +252 -83
- package/lib/helper/errors/ElementNotFound.js +2 -1
- package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
- package/lib/helper/scripts/blurElement.js +17 -0
- package/lib/helper/scripts/focusElement.js +17 -0
- package/lib/helper/scripts/highlightElement.js +20 -0
- package/lib/html.js +258 -0
- package/lib/interfaces/bdd.js +1 -1
- package/lib/interfaces/gherkin.js +37 -3
- package/lib/interfaces/scenarioConfig.js +1 -0
- package/lib/listener/retry.js +2 -1
- package/lib/locator.js +17 -4
- package/lib/mochaFactory.js +2 -1
- package/lib/output.js +1 -1
- package/lib/pause.js +78 -19
- package/lib/plugin/autoLogin.js +45 -10
- package/lib/plugin/debugErrors.js +67 -0
- package/lib/plugin/fakerTransform.js +4 -6
- package/lib/plugin/heal.js +209 -0
- package/lib/plugin/retryFailedStep.js +10 -1
- package/lib/plugin/retryTo.js +2 -4
- package/lib/plugin/screenshotOnFail.js +11 -2
- package/lib/plugin/selenoid.js +6 -1
- package/lib/plugin/standardActingHelpers.js +0 -2
- package/lib/plugin/stepByStepReport.js +2 -2
- package/lib/plugin/tryTo.js +5 -7
- package/lib/plugin/wdio.js +0 -1
- package/lib/recorder.js +22 -11
- package/lib/secret.js +5 -4
- package/lib/session.js +1 -1
- package/lib/step.js +36 -12
- package/lib/ui.js +5 -3
- package/lib/utils.js +22 -1
- package/lib/workers.js +83 -10
- package/package.json +117 -95
- package/translations/de-DE.js +5 -0
- package/translations/fr-FR.js +14 -1
- package/translations/it-IT.js +1 -0
- package/translations/ja-JP.js +14 -9
- package/translations/pl-PL.js +5 -0
- package/translations/pt-BR.js +1 -0
- package/translations/ru-RU.js +1 -0
- package/translations/zh-CN.js +5 -0
- package/translations/zh-TW.js +5 -0
- package/typings/index.d.ts +51 -15
- package/typings/promiseBasedTypes.d.ts +864 -802
- package/typings/types.d.ts +1339 -744
- package/CHANGELOG.md +0 -2427
- package/docs/advanced.md +0 -351
- package/docs/api.md +0 -323
- package/docs/basics.md +0 -980
- package/docs/bdd.md +0 -535
- package/docs/best.md +0 -237
- package/docs/books.md +0 -37
- package/docs/bootstrap.md +0 -135
- package/docs/build/ApiDataFactory.js +0 -409
- package/docs/build/Appium.js +0 -1938
- package/docs/build/FileSystem.js +0 -228
- package/docs/build/GraphQL.js +0 -204
- package/docs/build/GraphQLDataFactory.js +0 -309
- package/docs/build/JSONResponse.js +0 -338
- package/docs/build/Mochawesome.js +0 -71
- package/docs/build/Nightmare.js +0 -2145
- package/docs/build/Playwright.js +0 -3986
- package/docs/build/Polly.js +0 -42
- package/docs/build/Protractor.js +0 -2699
- package/docs/build/Puppeteer.js +0 -3710
- package/docs/build/REST.js +0 -334
- package/docs/build/SeleniumWebdriver.js +0 -76
- package/docs/build/TestCafe.js +0 -2057
- package/docs/build/WebDriver.js +0 -4017
- package/docs/changelog.md +0 -2436
- package/docs/commands.md +0 -254
- package/docs/community-helpers.md +0 -58
- package/docs/configuration.md +0 -157
- package/docs/continuous-integration.md +0 -22
- package/docs/custom-helpers.md +0 -306
- package/docs/data.md +0 -375
- package/docs/detox.md +0 -235
- package/docs/docker.md +0 -137
- package/docs/email.md +0 -183
- package/docs/examples.md +0 -149
- package/docs/helpers/ApiDataFactory.md +0 -266
- package/docs/helpers/Appium.md +0 -1312
- package/docs/helpers/Detox.md +0 -586
- package/docs/helpers/FileSystem.md +0 -152
- package/docs/helpers/GraphQL.md +0 -130
- package/docs/helpers/GraphQLDataFactory.md +0 -226
- package/docs/helpers/JSONResponse.md +0 -254
- package/docs/helpers/Mochawesome.md +0 -8
- package/docs/helpers/MockRequest.md +0 -377
- package/docs/helpers/Nightmare.md +0 -1256
- package/docs/helpers/Playwright.md +0 -2208
- package/docs/helpers/Polly.md +0 -44
- package/docs/helpers/Puppeteer-firefox.md +0 -86
- package/docs/helpers/Puppeteer.md +0 -2141
- package/docs/helpers/REST.md +0 -217
- package/docs/helpers/TestCafe.md +0 -1222
- package/docs/helpers/WebDriver.md +0 -2319
- package/docs/hooks.md +0 -340
- package/docs/index.md +0 -111
- package/docs/installation.md +0 -75
- package/docs/internal-api.md +0 -265
- package/docs/locators.md +0 -331
- package/docs/mobile-react-native-locators.md +0 -67
- package/docs/mobile.md +0 -297
- package/docs/nightmare.md +0 -223
- package/docs/pageobjects.md +0 -291
- package/docs/parallel.md +0 -232
- package/docs/playwright.md +0 -609
- package/docs/plugins.md +0 -1171
- package/docs/puppeteer.md +0 -316
- package/docs/quickstart.md +0 -163
- package/docs/react.md +0 -69
- package/docs/reports.md +0 -392
- package/docs/secrets.md +0 -30
- package/docs/shadow.md +0 -68
- package/docs/shared/keys.mustache +0 -31
- package/docs/shared/react.mustache +0 -1
- package/docs/testcafe.md +0 -174
- package/docs/translation.md +0 -247
- package/docs/tutorial.md +0 -271
- package/docs/typescript.md +0 -180
- package/docs/ui.md +0 -59
- package/docs/videos.md +0 -28
- package/docs/visual.md +0 -202
- package/docs/vue.md +0 -121
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -9
- package/docs/webapi/attachFile.mustache +0 -12
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -10
- package/docs/webapi/clearField.mustache +0 -9
- package/docs/webapi/click.mustache +0 -25
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -8
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -11
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -16
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/moveCursorTo.mustache +0 -12
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -8
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -12
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/selectOption.mustache +0 -21
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -18
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
- package/docs/webdriver.md +0 -657
- package/docs/wiki/Books-&-Posts.md +0 -27
- package/docs/wiki/Community-Helpers-&-Plugins.md +0 -49
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -29
- package/docs/wiki/Examples.md +0 -139
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
- package/docs/wiki/Home.md +0 -16
- package/docs/wiki/Release-Process.md +0 -24
- package/docs/wiki/Roadmap.md +0 -23
- package/docs/wiki/Tests.md +0 -1393
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
- package/docs/wiki/Videos.md +0 -19
package/lib/pause.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const colors = require('chalk');
|
|
2
2
|
const readline = require('readline');
|
|
3
|
+
const ora = require('ora-classic');
|
|
4
|
+
const debug = require('debug')('codeceptjs:pause');
|
|
3
5
|
|
|
4
6
|
const container = require('./container');
|
|
5
7
|
const history = require('./history');
|
|
6
8
|
const store = require('./store');
|
|
9
|
+
const AiAssistant = require('./ai');
|
|
7
10
|
const recorder = require('./recorder');
|
|
8
11
|
const event = require('./event');
|
|
9
12
|
const output = require('./output');
|
|
@@ -15,8 +18,10 @@ let nextStep;
|
|
|
15
18
|
let finish;
|
|
16
19
|
let next;
|
|
17
20
|
let registeredVariables = {};
|
|
21
|
+
let aiAssistant;
|
|
18
22
|
/**
|
|
19
23
|
* Pauses test execution and starts interactive shell
|
|
24
|
+
* @param {Object<string, *>} [passedObject]
|
|
20
25
|
*/
|
|
21
26
|
const pause = function (passedObject = {}) {
|
|
22
27
|
if (store.dryRun) return;
|
|
@@ -39,12 +44,22 @@ function pauseSession(passedObject = {}) {
|
|
|
39
44
|
let vars = Object.keys(registeredVariables).join(', ');
|
|
40
45
|
if (vars) vars = `(vars: ${vars})`;
|
|
41
46
|
|
|
47
|
+
aiAssistant = AiAssistant.getInstance();
|
|
48
|
+
|
|
42
49
|
output.print(colors.yellow(' Interactive shell started'));
|
|
43
50
|
output.print(colors.yellow(' Use JavaScript syntax to try steps in action'));
|
|
44
51
|
output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`));
|
|
45
52
|
output.print(colors.yellow(` - Press ${colors.bold('TAB')} twice to see all available commands`));
|
|
46
53
|
output.print(colors.yellow(` - Type ${colors.bold('exit')} + Enter to exit the interactive shell`));
|
|
47
54
|
output.print(colors.yellow(` - Prefix ${colors.bold('=>')} to run js commands ${colors.bold(vars)}`));
|
|
55
|
+
|
|
56
|
+
if (aiAssistant.isEnabled) {
|
|
57
|
+
output.print(colors.blue(` ${colors.bold('OpenAI is enabled! (experimental)')} Write what you want and make OpenAI run it`));
|
|
58
|
+
output.print(colors.blue(' Please note, only HTML fragments with interactive elements are sent to OpenAI'));
|
|
59
|
+
output.print(colors.blue(' Ideas: ask it to fill forms for you or to click'));
|
|
60
|
+
} else {
|
|
61
|
+
output.print(colors.blue(` Enable OpenAI assistant by setting ${colors.bold('OPENAI_API_KEY')} env variable`));
|
|
62
|
+
}
|
|
48
63
|
}
|
|
49
64
|
rl = readline.createInterface(process.stdin, process.stdout, completer);
|
|
50
65
|
|
|
@@ -54,15 +69,16 @@ function pauseSession(passedObject = {}) {
|
|
|
54
69
|
});
|
|
55
70
|
return new Promise(((resolve) => {
|
|
56
71
|
finish = resolve;
|
|
72
|
+
// eslint-disable-next-line
|
|
57
73
|
return askForStep();
|
|
58
74
|
}));
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
/* eslint-disable */
|
|
62
|
-
function parseInput(cmd) {
|
|
78
|
+
async function parseInput(cmd) {
|
|
63
79
|
rl.pause();
|
|
64
80
|
next = false;
|
|
65
|
-
|
|
81
|
+
recorder.session.start('pause');
|
|
66
82
|
if (cmd === '') next = true;
|
|
67
83
|
if (!cmd || cmd === 'resume' || cmd === 'exit') {
|
|
68
84
|
finish();
|
|
@@ -74,37 +90,79 @@ function parseInput(cmd) {
|
|
|
74
90
|
for (const k of Object.keys(registeredVariables)) {
|
|
75
91
|
eval(`var ${k} = registeredVariables['${k}'];`); // eslint-disable-line no-eval
|
|
76
92
|
}
|
|
77
|
-
|
|
93
|
+
|
|
94
|
+
let executeCommand = Promise.resolve();
|
|
95
|
+
|
|
96
|
+
const getCmd = () => {
|
|
97
|
+
debug('Command:', cmd)
|
|
98
|
+
return cmd;
|
|
99
|
+
};
|
|
100
|
+
|
|
78
101
|
let isCustomCommand = false;
|
|
79
102
|
let lastError = null;
|
|
103
|
+
let isAiCommand = false;
|
|
104
|
+
let $res;
|
|
80
105
|
try {
|
|
106
|
+
// eslint-disable-next-line
|
|
81
107
|
const locate = global.locate; // enable locate in this context
|
|
108
|
+
// eslint-disable-next-line
|
|
82
109
|
const I = container.support('I');
|
|
83
110
|
if (cmd.trim().startsWith('=>')) {
|
|
84
111
|
isCustomCommand = true;
|
|
85
112
|
cmd = cmd.trim().substring(2, cmd.length);
|
|
113
|
+
} else if (aiAssistant.isEnabled && !cmd.match(/^\w+\(/) && cmd.includes(' ')) {
|
|
114
|
+
const currentOutputLevel = output.level();
|
|
115
|
+
output.level(0);
|
|
116
|
+
const res = I.grabSource();
|
|
117
|
+
isAiCommand = true;
|
|
118
|
+
executeCommand = executeCommand.then(async () => {
|
|
119
|
+
try {
|
|
120
|
+
const html = await res;
|
|
121
|
+
await aiAssistant.setHtmlContext(html);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
|
|
124
|
+
return;
|
|
125
|
+
} finally {
|
|
126
|
+
output.level(currentOutputLevel);
|
|
127
|
+
}
|
|
128
|
+
// aiAssistant.mockResponse("```js\nI.click('Sign in');\n```");
|
|
129
|
+
const spinner = ora("Processing OpenAI request...").start();
|
|
130
|
+
cmd = await aiAssistant.writeSteps(cmd);
|
|
131
|
+
spinner.stop();
|
|
132
|
+
output.print('');
|
|
133
|
+
output.print(colors.blue(aiAssistant.getResponse()));
|
|
134
|
+
output.print('');
|
|
135
|
+
return cmd;
|
|
136
|
+
})
|
|
86
137
|
} else {
|
|
87
138
|
cmd = `I.${cmd}`;
|
|
88
139
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (isCustomCommand) {
|
|
94
|
-
console.log(val);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (cmd.startsWith('I.see') || cmd.startsWith('I.dontSee')) {
|
|
98
|
-
output.print(output.styles.success(' OK '), cmd);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (cmd.startsWith('I.grab')) {
|
|
102
|
-
output.print(output.styles.debug(val));
|
|
103
|
-
}
|
|
140
|
+
executeCommand = executeCommand.then(async () => {
|
|
141
|
+
const cmd = getCmd();
|
|
142
|
+
if (!cmd) return;
|
|
143
|
+
return eval(cmd); // eslint-disable-line no-eval
|
|
104
144
|
}).catch((err) => {
|
|
145
|
+
debug(err);
|
|
146
|
+
if (isAiCommand) return;
|
|
105
147
|
if (!lastError) output.print(output.styles.error(' ERROR '), err.message);
|
|
148
|
+
debug(err.stack)
|
|
149
|
+
|
|
106
150
|
lastError = err.message;
|
|
107
|
-
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const val = await executeCommand;
|
|
154
|
+
|
|
155
|
+
if (isCustomCommand) {
|
|
156
|
+
if (val !== undefined) console.log('Result', '$res=', val); // eslint-disable-line
|
|
157
|
+
$res = val;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) {
|
|
161
|
+
output.print(output.styles.success(' OK '), cmd);
|
|
162
|
+
}
|
|
163
|
+
if (cmd?.startsWith('I.grab')) {
|
|
164
|
+
output.print(output.styles.debug(val));
|
|
165
|
+
}
|
|
108
166
|
|
|
109
167
|
history.push(cmd); // add command to history when successful
|
|
110
168
|
} catch (err) {
|
|
@@ -117,6 +175,7 @@ function parseInput(cmd) {
|
|
|
117
175
|
// pop latest command from history because it failed
|
|
118
176
|
history.pop();
|
|
119
177
|
|
|
178
|
+
if (isAiCommand) return;
|
|
120
179
|
if (!lastError) output.print(output.styles.error(' FAIL '), msg);
|
|
121
180
|
lastError = err.message;
|
|
122
181
|
});
|
package/lib/plugin/autoLogin.js
CHANGED
|
@@ -9,6 +9,7 @@ const isAsyncFunction = require('../utils').isAsyncFunction;
|
|
|
9
9
|
|
|
10
10
|
const defaultUser = {
|
|
11
11
|
fetch: I => I.grabCookie(),
|
|
12
|
+
check: () => {},
|
|
12
13
|
restore: (I, cookies) => {
|
|
13
14
|
I.amOnPage('/'); // open a page
|
|
14
15
|
I.setCookie(cookies);
|
|
@@ -37,12 +38,14 @@ const defaultConfig = {
|
|
|
37
38
|
* ```js
|
|
38
39
|
* // inside a test file
|
|
39
40
|
* // use login to inject auto-login function
|
|
41
|
+
* Feature('Login');
|
|
42
|
+
*
|
|
40
43
|
* Before(({ login }) => {
|
|
41
44
|
* login('user'); // login using user session
|
|
42
45
|
* });
|
|
43
46
|
*
|
|
44
|
-
* // Alternatively log in for one scenario
|
|
45
|
-
* Scenario('log me in', ( {I, login} ) => {
|
|
47
|
+
* // Alternatively log in for one scenario.
|
|
48
|
+
* Scenario('log me in', ( { I, login } ) => {
|
|
46
49
|
* login('admin');
|
|
47
50
|
* I.see('I am logged in');
|
|
48
51
|
* });
|
|
@@ -61,7 +64,7 @@ const defaultConfig = {
|
|
|
61
64
|
* #### How It Works
|
|
62
65
|
*
|
|
63
66
|
* 1. `restore` method is executed. It should open a page and set credentials.
|
|
64
|
-
* 2. `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged
|
|
67
|
+
* 2. `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged-in user. When you pass the second args `session`, you could perform the validation using passed session.
|
|
65
68
|
* 3. If `restore` and `check` were not successful, `login` is executed
|
|
66
69
|
* 4. `login` should fill in login form
|
|
67
70
|
* 5. After successful login, `fetch` is executed to save cookies into memory or file.
|
|
@@ -212,6 +215,38 @@ const defaultConfig = {
|
|
|
212
215
|
* })
|
|
213
216
|
* ```
|
|
214
217
|
*
|
|
218
|
+
* #### Tips: Using session to validate user
|
|
219
|
+
*
|
|
220
|
+
* Instead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`
|
|
221
|
+
*
|
|
222
|
+
* ```js
|
|
223
|
+
* autoLogin: {
|
|
224
|
+
* enabled: true,
|
|
225
|
+
* saveToFile: true,
|
|
226
|
+
* inject: 'login',
|
|
227
|
+
* users: {
|
|
228
|
+
* admin: {
|
|
229
|
+
* login: async (I) => { // If you use async function in the autoLogin plugin
|
|
230
|
+
* const phrase = await I.grabTextFrom('#phrase')
|
|
231
|
+
* I.fillField('username', 'admin'),
|
|
232
|
+
* I.fillField('password', 'password')
|
|
233
|
+
* I.fillField('phrase', phrase)
|
|
234
|
+
* },
|
|
235
|
+
* check: (I, session) => {
|
|
236
|
+
* // Throwing an error in `check` will make CodeceptJS perform the login step for the user
|
|
237
|
+
* if (session.profile.email !== the.email.you.expect@some-mail.com) {
|
|
238
|
+
* throw new Error ('Wrong user signed in');
|
|
239
|
+
* }
|
|
240
|
+
* },
|
|
241
|
+
* }
|
|
242
|
+
* }
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* ```js
|
|
247
|
+
* Scenario('login', async ( {I, login} ) => {
|
|
248
|
+
* await login('admin') // you should use `await`
|
|
249
|
+
* })
|
|
215
250
|
*
|
|
216
251
|
*
|
|
217
252
|
*/
|
|
@@ -251,27 +286,28 @@ module.exports = function (config) {
|
|
|
251
286
|
} else {
|
|
252
287
|
userSession.login(I);
|
|
253
288
|
}
|
|
254
|
-
|
|
289
|
+
|
|
255
290
|
const cookies = await userSession.fetch(I);
|
|
291
|
+
if (!cookies) {
|
|
292
|
+
debug('Cannot save user session with empty cookies from auto login\'s fetch method');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
256
295
|
if (config.saveToFile) {
|
|
257
296
|
debug(`Saved user session into file for ${name}`);
|
|
258
297
|
fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies));
|
|
259
298
|
}
|
|
260
299
|
store[`${name}_session`] = cookies;
|
|
261
|
-
store.debugMode = false;
|
|
262
300
|
};
|
|
263
301
|
|
|
264
302
|
if (!cookies) return loginAndSave();
|
|
265
303
|
|
|
266
|
-
store.debugMode = true;
|
|
267
|
-
|
|
268
304
|
recorder.session.start('check login');
|
|
269
305
|
if (shouldAwait) {
|
|
270
306
|
await userSession.restore(I, cookies);
|
|
271
|
-
await userSession.check(I);
|
|
307
|
+
await userSession.check(I, cookies);
|
|
272
308
|
} else {
|
|
273
309
|
userSession.restore(I, cookies);
|
|
274
|
-
userSession.check(I);
|
|
310
|
+
userSession.check(I, cookies);
|
|
275
311
|
}
|
|
276
312
|
recorder.session.catch((err) => {
|
|
277
313
|
debug(`Failed auto login for ${name} due to ${err}`);
|
|
@@ -287,7 +323,6 @@ module.exports = function (config) {
|
|
|
287
323
|
});
|
|
288
324
|
});
|
|
289
325
|
recorder.add(() => {
|
|
290
|
-
store.debugMode = false;
|
|
291
326
|
recorder.session.restore('check login');
|
|
292
327
|
});
|
|
293
328
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const Container = require('../container');
|
|
2
|
+
const recorder = require('../recorder');
|
|
3
|
+
const event = require('../event');
|
|
4
|
+
const supportedHelpers = require('./standardActingHelpers');
|
|
5
|
+
const { scanForErrorMessages } = require('../html');
|
|
6
|
+
const { output } = require('..');
|
|
7
|
+
|
|
8
|
+
const defaultConfig = {
|
|
9
|
+
errorClasses: ['error', 'warning', 'alert', 'danger'],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Prints errors found in HTML code after each failed test.
|
|
14
|
+
*
|
|
15
|
+
* It scans HTML and searches for elements with error classes.
|
|
16
|
+
* If an element found prints a text from it to console and adds as artifact to the test.
|
|
17
|
+
*
|
|
18
|
+
* Enable this plugin in config:
|
|
19
|
+
*
|
|
20
|
+
* ```js
|
|
21
|
+
* plugins: {
|
|
22
|
+
* debugErrors: {
|
|
23
|
+
* enabled: true,
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Additional config options:
|
|
28
|
+
*
|
|
29
|
+
* * `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
module.exports = function (config = {}) {
|
|
33
|
+
const helpers = Container.helpers();
|
|
34
|
+
let helper;
|
|
35
|
+
|
|
36
|
+
config = Object.assign(defaultConfig, config);
|
|
37
|
+
|
|
38
|
+
for (const helperName of supportedHelpers) {
|
|
39
|
+
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
40
|
+
helper = helpers[helperName];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!helper) return; // no helpers for screenshot
|
|
45
|
+
|
|
46
|
+
event.dispatcher.on(event.test.failed, (test) => {
|
|
47
|
+
recorder.add('HTML snapshot failed test', async () => {
|
|
48
|
+
try {
|
|
49
|
+
const currentOutputLevel = output.level();
|
|
50
|
+
output.level(0);
|
|
51
|
+
const html = await helper.grabHTMLFrom('body');
|
|
52
|
+
output.level(currentOutputLevel);
|
|
53
|
+
|
|
54
|
+
if (!html) return;
|
|
55
|
+
|
|
56
|
+
const errors = scanForErrorMessages(html, config.errorClasses);
|
|
57
|
+
if (errors.length) {
|
|
58
|
+
output.debug('Detected errors in HTML code');
|
|
59
|
+
errors.forEach((error) => output.debug(error));
|
|
60
|
+
test.artifacts.errors = errors;
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// not really needed
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
@@ -2,20 +2,18 @@ const { faker } = require('@faker-js/faker');
|
|
|
2
2
|
const transform = require('../transform');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Use the
|
|
6
|
-
*
|
|
7
|
-
* 
|
|
5
|
+
* Use the `@faker-js/faker` package to generate fake data inside examples on your gherkin tests
|
|
8
6
|
*
|
|
9
7
|
* #### Usage
|
|
10
8
|
*
|
|
11
|
-
* To start please install
|
|
9
|
+
* To start please install `@faker-js/faker` package
|
|
12
10
|
*
|
|
13
11
|
* ```
|
|
14
|
-
* npm install -D faker
|
|
12
|
+
* npm install -D @faker-js/faker
|
|
15
13
|
* ```
|
|
16
14
|
*
|
|
17
15
|
* ```
|
|
18
|
-
* yarn add -D faker
|
|
16
|
+
* yarn add -D @faker-js/faker
|
|
19
17
|
* ```
|
|
20
18
|
*
|
|
21
19
|
* Add this plugin to config file:
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const debug = require('debug')('codeceptjs:heal');
|
|
2
|
+
const colors = require('chalk');
|
|
3
|
+
const Container = require('../container');
|
|
4
|
+
const AiAssistant = require('../ai');
|
|
5
|
+
const recorder = require('../recorder');
|
|
6
|
+
const event = require('../event');
|
|
7
|
+
const output = require('../output');
|
|
8
|
+
const supportedHelpers = require('./standardActingHelpers');
|
|
9
|
+
|
|
10
|
+
const defaultConfig = {
|
|
11
|
+
healTries: 1,
|
|
12
|
+
healLimit: 2,
|
|
13
|
+
healSteps: [
|
|
14
|
+
'click',
|
|
15
|
+
'fillField',
|
|
16
|
+
'appendField',
|
|
17
|
+
'selectOption',
|
|
18
|
+
'attachFile',
|
|
19
|
+
'checkOption',
|
|
20
|
+
'uncheckOption',
|
|
21
|
+
'doubleClick',
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Self-healing tests with OpenAI.
|
|
27
|
+
*
|
|
28
|
+
* This plugin is experimental and requires OpenAI API key.
|
|
29
|
+
*
|
|
30
|
+
* To use it you need to set OPENAI_API_KEY env variable and enable plugin inside the config.
|
|
31
|
+
*
|
|
32
|
+
* ```js
|
|
33
|
+
* plugins: {
|
|
34
|
+
* heal: {
|
|
35
|
+
* enabled: true,
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* More config options are available:
|
|
41
|
+
*
|
|
42
|
+
* * `healLimit` - how many steps can be healed in a single test (default: 2)
|
|
43
|
+
* * `healSteps` - which steps can be healed (default: all steps that interact with UI, see list below)
|
|
44
|
+
*
|
|
45
|
+
* Steps to heal:
|
|
46
|
+
*
|
|
47
|
+
* * `click`
|
|
48
|
+
* * `fillField`
|
|
49
|
+
* * `appendField`
|
|
50
|
+
* * `selectOption`
|
|
51
|
+
* * `attachFile`
|
|
52
|
+
* * `checkOption`
|
|
53
|
+
* * `uncheckOption`
|
|
54
|
+
* * `doubleClick`
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
module.exports = function (config = {}) {
|
|
58
|
+
const aiAssistant = AiAssistant.getInstance();
|
|
59
|
+
|
|
60
|
+
let currentTest = null;
|
|
61
|
+
let currentStep = null;
|
|
62
|
+
let healedSteps = 0;
|
|
63
|
+
let caughtError;
|
|
64
|
+
let healTries = 0;
|
|
65
|
+
let isHealing = false;
|
|
66
|
+
|
|
67
|
+
const healSuggestions = [];
|
|
68
|
+
|
|
69
|
+
config = Object.assign(defaultConfig, config);
|
|
70
|
+
|
|
71
|
+
event.dispatcher.on(event.test.before, (test) => {
|
|
72
|
+
currentTest = test;
|
|
73
|
+
healedSteps = 0;
|
|
74
|
+
caughtError = null;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
event.dispatcher.on(event.step.started, step => currentStep = step);
|
|
78
|
+
|
|
79
|
+
event.dispatcher.on(event.step.after, (step) => {
|
|
80
|
+
if (isHealing) return;
|
|
81
|
+
const store = require('../store');
|
|
82
|
+
if (store.debugMode) return;
|
|
83
|
+
recorder.catchWithoutStop(async (err) => {
|
|
84
|
+
isHealing = true;
|
|
85
|
+
if (caughtError === err) throw err; // avoid double handling
|
|
86
|
+
caughtError = err;
|
|
87
|
+
if (!aiAssistant.isEnabled) {
|
|
88
|
+
output.print(colors.yellow('Heal plugin can\'t operate, AI assistant is disabled. Please set OPENAI_API_KEY env variable to enable it.'));
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
if (!currentStep) throw err;
|
|
92
|
+
if (!config.healSteps.includes(currentStep.name)) throw err;
|
|
93
|
+
const test = currentTest;
|
|
94
|
+
|
|
95
|
+
if (healTries >= config.healTries) {
|
|
96
|
+
output.print(colors.bold.red(`Healing failed for ${config.healTries} time(s)`));
|
|
97
|
+
output.print('AI couldn\'t identify the correct solution');
|
|
98
|
+
output.print('Probably the entire flow has changed and the test should be updated');
|
|
99
|
+
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (healedSteps >= config.healLimit) {
|
|
104
|
+
output.print(colors.bold.red(`Can't heal more than ${config.healLimit} step(s) in a test`));
|
|
105
|
+
output.print('Entire flow can be broken, please check it manually');
|
|
106
|
+
output.print('or increase healing limit in heal plugin config');
|
|
107
|
+
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
recorder.session.start('heal');
|
|
112
|
+
const helpers = Container.helpers();
|
|
113
|
+
let helper;
|
|
114
|
+
|
|
115
|
+
for (const helperName of supportedHelpers) {
|
|
116
|
+
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
117
|
+
helper = helpers[helperName];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!helper) throw err; // no helpers for html
|
|
122
|
+
|
|
123
|
+
const step = test.steps[test.steps.length - 1];
|
|
124
|
+
debug('Self-healing started', step.toCode());
|
|
125
|
+
|
|
126
|
+
const currentOutputLevel = output.level();
|
|
127
|
+
output.level(0);
|
|
128
|
+
const html = await helper.grabHTMLFrom('body');
|
|
129
|
+
output.level(currentOutputLevel);
|
|
130
|
+
|
|
131
|
+
if (!html) throw err;
|
|
132
|
+
|
|
133
|
+
healTries++;
|
|
134
|
+
await aiAssistant.setHtmlContext(html);
|
|
135
|
+
await tryToHeal(step, err);
|
|
136
|
+
|
|
137
|
+
recorder.add('close healing session', () => {
|
|
138
|
+
recorder.session.restore('heal');
|
|
139
|
+
recorder.ignoreErr(err);
|
|
140
|
+
});
|
|
141
|
+
await recorder.promise();
|
|
142
|
+
|
|
143
|
+
isHealing = false;
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
event.dispatcher.on(event.all.result, () => {
|
|
148
|
+
if (!healSuggestions.length) return;
|
|
149
|
+
|
|
150
|
+
const { print } = output;
|
|
151
|
+
|
|
152
|
+
print('');
|
|
153
|
+
print('===================');
|
|
154
|
+
print(colors.bold.green('Self-Healing Report:'));
|
|
155
|
+
|
|
156
|
+
print(`${colors.bold(healSuggestions.length)} step(s) were healed by AI`);
|
|
157
|
+
|
|
158
|
+
let i = 1;
|
|
159
|
+
print('');
|
|
160
|
+
print('Suggested changes:');
|
|
161
|
+
print('');
|
|
162
|
+
|
|
163
|
+
for (const suggestion of healSuggestions) {
|
|
164
|
+
print(`${i}. To fix ${colors.bold.blue(suggestion.test.title)}`);
|
|
165
|
+
print('Replace the failed code with:');
|
|
166
|
+
print(colors.red(`- ${suggestion.step.toCode()}`));
|
|
167
|
+
print(colors.green(`+ ${suggestion.snippet}`));
|
|
168
|
+
print(suggestion.step.line());
|
|
169
|
+
print('');
|
|
170
|
+
i++;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
async function tryToHeal(failedStep, err) {
|
|
175
|
+
output.debug(`Running OpenAI to heal ${failedStep.toCode()} step`);
|
|
176
|
+
|
|
177
|
+
const codeSnippets = await aiAssistant.healFailedStep(failedStep, err, currentTest);
|
|
178
|
+
|
|
179
|
+
output.debug(`Received ${codeSnippets.length} suggestions from OpenAI`);
|
|
180
|
+
const I = Container.support('I'); // eslint-disable-line
|
|
181
|
+
|
|
182
|
+
for (const codeSnippet of codeSnippets) {
|
|
183
|
+
try {
|
|
184
|
+
debug('Executing', codeSnippet);
|
|
185
|
+
recorder.catch((e) => {
|
|
186
|
+
console.log(e);
|
|
187
|
+
});
|
|
188
|
+
await eval(codeSnippet); // eslint-disable-line
|
|
189
|
+
|
|
190
|
+
healSuggestions.push({
|
|
191
|
+
test: currentTest,
|
|
192
|
+
step: failedStep,
|
|
193
|
+
snippet: codeSnippet,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
recorder.add('healed', () => output.print(colors.bold.green(' Code healed successfully')));
|
|
197
|
+
healedSteps++;
|
|
198
|
+
return;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
debug('Failed to execute code', err);
|
|
201
|
+
recorder.ignoreErr(err); // healing ded not help
|
|
202
|
+
// recorder.catch(() => output.print(colors.bold.red(' Failed healing code')));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
output.debug(`Couldn't heal the code for ${failedStep.toCode()}`);
|
|
207
|
+
}
|
|
208
|
+
return recorder.promise();
|
|
209
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const event = require('../event');
|
|
2
2
|
const recorder = require('../recorder');
|
|
3
|
+
const container = require('../container');
|
|
4
|
+
const { log } = require('../output');
|
|
3
5
|
|
|
4
6
|
const defaultConfig = {
|
|
5
7
|
retries: 3,
|
|
@@ -42,7 +44,7 @@ const defaultConfig = {
|
|
|
42
44
|
* * `factor` - The exponential factor to use. Default is 1.5.
|
|
43
45
|
* * `minTimeout` - The number of milliseconds before starting the first retry. Default is 1000.
|
|
44
46
|
* * `maxTimeout` - The maximum number of milliseconds between two retries. Default is Infinity.
|
|
45
|
-
* * `randomize` - Randomizes the timeouts by multiplying with a factor
|
|
47
|
+
* * `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.
|
|
46
48
|
* * `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:
|
|
47
49
|
* * `amOnPage`
|
|
48
50
|
* * `wait*`
|
|
@@ -98,6 +100,11 @@ module.exports = (config) => {
|
|
|
98
100
|
config.when = when;
|
|
99
101
|
|
|
100
102
|
event.dispatcher.on(event.step.started, (step) => {
|
|
103
|
+
if (process.env.TRY_TO === 'true') {
|
|
104
|
+
log('Info: RetryFailedStep plugin is disabled inside tryTo block');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
// if a step is ignored - return
|
|
102
109
|
for (const ignored of config.ignoredSteps) {
|
|
103
110
|
if (step.name === ignored) return;
|
|
@@ -114,6 +121,8 @@ module.exports = (config) => {
|
|
|
114
121
|
|
|
115
122
|
event.dispatcher.on(event.test.before, (test) => {
|
|
116
123
|
if (test && test.disableRetryFailedStep) return; // disable retry when a test is not active
|
|
124
|
+
// this env var is used to set the retries inside _before() block of helpers
|
|
125
|
+
process.env.FAILED_STEP_RETRIES = config.retries;
|
|
117
126
|
recorder.retry(config);
|
|
118
127
|
});
|
|
119
128
|
};
|
package/lib/plugin/retryTo.js
CHANGED
|
@@ -83,16 +83,15 @@ module.exports = function (config) {
|
|
|
83
83
|
return retryTo;
|
|
84
84
|
|
|
85
85
|
function retryTo(callback, maxTries, pollInterval = undefined) {
|
|
86
|
-
const mode = store.debugMode;
|
|
87
86
|
let tries = 1;
|
|
88
87
|
if (!pollInterval) pollInterval = config.pollInterval;
|
|
89
88
|
|
|
90
89
|
let err = null;
|
|
91
90
|
|
|
92
91
|
return new Promise((done) => {
|
|
93
|
-
const tryBlock = () => {
|
|
92
|
+
const tryBlock = async () => {
|
|
94
93
|
recorder.session.start(`retryTo ${tries}`);
|
|
95
|
-
callback(tries);
|
|
94
|
+
await callback(tries);
|
|
96
95
|
recorder.add(() => {
|
|
97
96
|
recorder.session.restore(`retryTo ${tries}`);
|
|
98
97
|
done(null);
|
|
@@ -113,7 +112,6 @@ module.exports = function (config) {
|
|
|
113
112
|
};
|
|
114
113
|
|
|
115
114
|
recorder.add('retryTo', async () => {
|
|
116
|
-
store.debugMode = true;
|
|
117
115
|
tryBlock();
|
|
118
116
|
});
|
|
119
117
|
}).then(() => {
|
|
@@ -75,7 +75,8 @@ module.exports = function (config) {
|
|
|
75
75
|
event.dispatcher.on(event.test.failed, (test) => {
|
|
76
76
|
recorder.add('screenshot of failed test', async () => {
|
|
77
77
|
let fileName = clearString(test.title);
|
|
78
|
-
|
|
78
|
+
const dataType = 'image/png';
|
|
79
|
+
// This prevents data driven to be included in the failed screenshot file name
|
|
79
80
|
if (fileName.indexOf('{') !== -1) {
|
|
80
81
|
fileName = fileName.substr(0, (fileName.indexOf('{') - 3)).trim();
|
|
81
82
|
}
|
|
@@ -106,7 +107,15 @@ module.exports = function (config) {
|
|
|
106
107
|
|
|
107
108
|
const allureReporter = Container.plugins('allure');
|
|
108
109
|
if (allureReporter) {
|
|
109
|
-
allureReporter.addAttachment('Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)),
|
|
110
|
+
allureReporter.addAttachment('Main session - Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), dataType);
|
|
111
|
+
|
|
112
|
+
if (helper.activeSessionName) {
|
|
113
|
+
for (const sessionName in helper.sessionPages) {
|
|
114
|
+
const screenshotFileName = `${sessionName}_${fileName}`;
|
|
115
|
+
test.artifacts[`${sessionName.replace(/ /g, '_')}_screenshot`] = path.join(global.output_dir, screenshotFileName);
|
|
116
|
+
allureReporter.addAttachment(`${sessionName} - Last Seen Screenshot`, fs.readFileSync(path.join(global.output_dir, screenshotFileName)), dataType);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
const cucumberReporter = Container.plugins('cucumberJsonReporter');
|
package/lib/plugin/selenoid.js
CHANGED
|
@@ -58,7 +58,12 @@ let seleniumUrl = 'http://localhost:$port$';
|
|
|
58
58
|
const supportedHelpers = ['WebDriver'];
|
|
59
59
|
const SELENOID_START_TIMEOUT = 2000;
|
|
60
60
|
const SELENOID_STOP_TIMEOUT = 10000;
|
|
61
|
-
const wait = time => new Promise((res) =>
|
|
61
|
+
const wait = time => new Promise((res) => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
res();
|
|
65
|
+
}, time);
|
|
66
|
+
});
|
|
62
67
|
|
|
63
68
|
/**
|
|
64
69
|
* [Selenoid](https://aerokube.com/selenoid/) plugin automatically starts browsers and video recording.
|