diff --git a/docs/custom-helpers.md b/docs/custom-helpers.md index 74af25a1c..695ccf6f5 100644 --- a/docs/custom-helpers.md +++ b/docs/custom-helpers.md @@ -212,7 +212,7 @@ _before() { } ``` -`recorder.retry` acts similarly to `I.retry()` and accepts the same parameters. It expects the `when` parameter to be set so it would handle only specific errors and not to retry for every failed step. +`recorder.retry` registers a retry rule at the recorder level and accepts the same options as `step.retry()` (`retries`, `minTimeout`, `when`, ...). It expects the `when` parameter to be set so it would handle only specific errors and not to retry for every failed step. Retry rules are available in array `recorder.retries`. The last retry rule can be disabled by running `recorder.retries.pop()`; diff --git a/docs/detox.md b/docs/detox.md index 91e880a7f..fe5776b97 100644 --- a/docs/detox.md +++ b/docs/detox.md @@ -31,7 +31,7 @@ CodeceptJS provides next features over standard Detox: * **Unified API**. The same test can be executed in Appium or Detox. Unified API helps different teams to use the same syntax and easy port tests from one engine to another. * [Interactive pause](/basics#pause). When starting/stopping an application takes a long time, debugging and writing tests can be hard. CodeceptJS solves this by pausing an execution and letting you try different commands and locators. With this feature a test can be writtern during one running session. -* [Auto-retries](/basics#retries) using `retryFailedStepPlugin` and `I.retry()` +* [Auto-retries](/basics#retries) using the `retryFailedStep` plugin and `step.retry()` * **Cross-Platform testing** - one test can be executed on different engines. When needed, platform-specific actions and locators can be easily applied. ## A Test diff --git a/docs/index.md b/docs/index.md index 1d986df90..58c51be48 100644 --- a/docs/index.md +++ b/docs/index.md @@ -100,7 +100,7 @@ Scenario('Create a new store', async ({ I, login, SettingsPage }) => { I.fillField('Email', faker.internet.email()); I.fillField('Telephone', faker.phone.phoneNumberFormat()); I.selectInDropdown('Status', 'Active'); // Use custom methods - I.retry(2).click('Create'); // Retry flaky step + I.click('Create', step.retry(2)); // Retry flaky step I.waitInUrl('/settings/setup/stores'); // Explicit waiter I.see(storeName, '.settings'); // Assert text present inside an element (located by CSS) const storeId = await I.grabTextFrom('#store-id'); // Use await to get information from browser diff --git a/docs/migration-4.md b/docs/migration-4.md index 35c2ad41d..5843261d7 100644 --- a/docs/migration-4.md +++ b/docs/migration-4.md @@ -473,18 +473,26 @@ Use one of: The `customLocators` strategy registration in Playwright config is removed. Use the `customLocator` plugin or built-in ARIA locators (`{ role: 'button', name: 'Submit' }`). -### `I.retry()` is deprecated +### `I.retry()` and `I.limitTime()` removed -Use the step options API: +Both were deprecated in 3.x and are **removed in 4.x**. They configured the *next* step through a chained call; the replacement is the step options API — pass a `step.*` config as the **last argument** of the step itself. ```js import step from 'codeceptjs/steps' -I.click('Submit', step.retry(3)) -I.fillField('Email', 'a@b.c', step.timeout(10)) +// 3.x (removed) → 4.x +I.retry(3).click('Submit') // I.click('Submit', step.retry(3)) +I.limitTime(10).fillField('Email', 'a@b.c') // I.fillField('Email', 'a@b.c', step.timeout(10)) +``` + +`step.*` configs are also composable with the other step options: + +```js I.click('Add', step.opts({ elementIndex: 2 })) ``` +The behavior is unchanged — the option applies only to the step it is attached to, not to subsequent steps (this also fixes the 3.x footgun where `I.retry()` could leak retry settings onto the following step). `recorder.retry()` is unaffected and remains available for custom helpers. + ### `within` Is Now an Effect In 3.x, `within(...)` was a global statement available everywhere. In 4.x it's an effect alongside `tryTo`, `retryTo`, and `hopeThat`. Under `noGlobals: true` you must import it: diff --git a/docs/plugins/retryFailedStep.md b/docs/plugins/retryFailedStep.md index 73a379e52..4d1522406 100644 --- a/docs/plugins/retryFailedStep.md +++ b/docs/plugins/retryFailedStep.md @@ -61,7 +61,7 @@ plugins: { #### Disable Per Test -This plugin can be disabled per test. In this case you will need to stet `I.retry()` to all flaky steps: +This plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps: Use scenario configuration to disable plugin for a test diff --git a/docs/plugins/stepTimeout.md b/docs/plugins/stepTimeout.md index 3416c2a3b..416e3fc4d 100644 --- a/docs/plugins/stepTimeout.md +++ b/docs/plugins/stepTimeout.md @@ -29,7 +29,7 @@ Run tests with plugin enabled: * `timeout` - global step timeout, default 150 seconds -* `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false +* `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false * `noTimeoutSteps` - an array of steps with no timeout. Default: diff --git a/docs/timeouts.md b/docs/timeouts.md index d0cf605b6..ed40cb3a4 100644 --- a/docs/timeouts.md +++ b/docs/timeouts.md @@ -118,7 +118,7 @@ plugins: { When multiple timeouts are configured, CodeceptJS applies them in priority order: 1. **stepTimeoutHard** — plugin with `overrideStepLimits: true` -2. **codeLimitTime** — `step.timeout()` or `I.limitTime()` +2. **codeLimitTime** — `step.timeout()` 3. **stepTimeoutSoft** — plugin with `overrideStepLimits: false` 4. **testOrSuite** — global test/suite timeout diff --git a/lib/actor.js b/lib/actor.js index 5d58a36b6..f16967d36 100644 --- a/lib/actor.js +++ b/lib/actor.js @@ -1,10 +1,7 @@ import Step, { MetaStep } from './step.js' import recordStep from './step/record.js' -import retryStep from './step/retry.js' import { methodsOfObject } from './utils.js' -import { TIMEOUT_ORDER } from './timeout.js' import event from './event.js' -import store from './store.js' import output from './output.js' import Container from './container.js' @@ -30,38 +27,6 @@ class Actor { output.say(msg, `${color}`) }) } - - /** - * set the maximum execution time for the next step - * @function - * @param {number} timeout - step timeout in seconds - * @return {this} - * @inner - */ - limitTime(timeout) { - 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, TIMEOUT_ORDER.codeLimitTime) - }) - - return this - } - - /** - * @function - * @param {*} [opts] - * @return {this} - * @inner - */ - retry(opts) { - console.log('I.retry() is deprecated, use step.retry() instead') - retryStep(opts) - return this - } } /** diff --git a/lib/plugin/retryFailedStep.js b/lib/plugin/retryFailedStep.js index 3d696b4cf..cbe145910 100644 --- a/lib/plugin/retryFailedStep.js +++ b/lib/plugin/retryFailedStep.js @@ -77,7 +77,7 @@ const RETRY_PRIORITIES = { * * #### Disable Per Test * - * This plugin can be disabled per test. In this case you will need to stet `I.retry()` to all flaky steps: + * This plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps: * * Use scenario configuration to disable plugin for a test * diff --git a/lib/plugin/stepTimeout.js b/lib/plugin/stepTimeout.js index fbacb9ff0..cd6e1c5d6 100644 --- a/lib/plugin/stepTimeout.js +++ b/lib/plugin/stepTimeout.js @@ -32,7 +32,7 @@ const defaultConfig = { * #### Configuration: * * * `timeout` - global step timeout, default 150 seconds - * * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false + * * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false * * `noTimeoutSteps` - an array of steps with no timeout. Default: * * `amOnPage` * * `wait*` diff --git a/test/data/sandbox/configs/step_timeout/first_test.js b/test/data/sandbox/configs/step_timeout/first_test.js index b92b1d46c..5defbcc0a 100644 --- a/test/data/sandbox/configs/step_timeout/first_test.js +++ b/test/data/sandbox/configs/step_timeout/first_test.js @@ -1,3 +1,5 @@ +import step from 'codeceptjs/steps' + const { I } = inject() Feature('Steps') @@ -11,7 +13,7 @@ Scenario('Wait command timeout', ({ I }) => { }) Scenario('Rerun sleep', ({ I }) => { - I.retry(2).statefulSleep(2250) + I.statefulSleep(2250, step.retry(2)) }) Scenario('Wait with longer timeout', ({ I }) => { diff --git a/test/data/sandbox/configs/timeouts/suite_test.js b/test/data/sandbox/configs/timeouts/suite_test.js index 7d4edd99f..f9d74bb3d 100644 --- a/test/data/sandbox/configs/timeouts/suite_test.js +++ b/test/data/sandbox/configs/timeouts/suite_test.js @@ -10,11 +10,6 @@ Scenario('timeout test in 0.5 #second', { timeout: 0.5 }, ({ I }) => { I.waitForSleep(1000) }) -Scenario('timeout step in 0.5 old syntax', ({ I }) => { - I.limitTime(0.2).waitForSleep(100) - I.limitTime(0.2).waitForSleep(3000) -}) - Scenario('timeout step in 0.5 new syntax', ({ I }) => { I.waitForSleep(100, step.timeout(0.2)) I.waitForSleep(3000, step.timeout(0.2)) diff --git a/test/data/sandbox/flaky_test.retry.js b/test/data/sandbox/flaky_test.retry.js index dbef1c4d4..0ba0cd332 100644 --- a/test/data/sandbox/flaky_test.retry.js +++ b/test/data/sandbox/flaky_test.retry.js @@ -1,4 +1,5 @@ import assert from 'assert'; +import step from 'codeceptjs/steps'; const recorder = codeceptjs.recorder; @@ -8,10 +9,10 @@ Feature('Retry'); Scenario('flaky step @test1', async ({ I }) => { tries++; - await I.retry(3).failWhen(() => { + await I.failWhen(() => { tries++; return tries < 4; - }); + }, step.retry(3)); assert.equal(tries, 4); }); @@ -20,7 +21,7 @@ Scenario('flaky step passed globally @test2', ({ I }) => { retries: 3, when: () => false, }); - I.retry(5).asyncStep(); + I.asyncStep(step.retry(5)); I.failWhen(() => { tries++; return tries < 4; diff --git a/test/unit/actor_test.js b/test/unit/actor_test.js index c0a7fe7fb..9ab96c5d6 100644 --- a/test/unit/actor_test.js +++ b/test/unit/actor_test.js @@ -5,6 +5,7 @@ import { fileURLToPath } from 'url' import actor from '../../lib/actor.js' import container from '../../lib/container.js' import recorder from '../../lib/recorder.js' +import step from '../../lib/steps.js' import event from '../../lib/event.js' import store from '../../lib/store.js' @@ -107,7 +108,7 @@ describe('Actor', () => { }) it('should take all methods from helpers and built in', () => { - ;['hello', 'bye', 'die', 'failAfter', 'say', 'retry', 'greeting'].forEach(key => { + ;['hello', 'bye', 'die', 'failAfter', 'say', 'greeting'].forEach(key => { expect(I).toHaveProperty(key) }) }) @@ -135,16 +136,6 @@ describe('Actor', () => { }) }) - it('should retry failed step with #retry', () => { - recorder.start() - return I.retry({ retries: 2, minTimeout: 0 }).failAfter(1) - }) - - it('should retry once step with #retry', () => { - recorder.start() - return I.retry().failAfter(1) - }) - it('should alway use the latest global retry options', () => { recorder.start() recorder.retry({ @@ -168,7 +159,7 @@ describe('Actor', () => { minTimeout: 0, when: () => true, }) - I.retry(1).failAfter(1) // before fix: this changed the order of retries + I.failAfter(1, step.retry(1)) // before fix: this changed the order of retries return I.failAfter(2) }) diff --git a/typings/tests/actor.types.ts b/typings/tests/actor.types.ts index fe734b82f..136078ce7 100644 --- a/typings/tests/actor.types.ts +++ b/typings/tests/actor.types.ts @@ -1,10 +1,7 @@ -import { expectError } from 'tsd'; - // @ts-ignore const I = actor(); -I.retry(); -I.retry(1); -I.retry({ retries: 3, minTimeout: 100 }); -// Removed: expectError(I.retry(1, 2)); - retry accepts 'any' type so this doesn't error - +// I.retry() and I.limitTime() were removed in 4.x. +// Use step.retry() / step.timeout() as the last step argument instead. +// `I` resolves to `any` in this typing test, so the removal cannot be +// asserted at the type level here. diff --git a/typings/tests/helpers/Playwright.types.ts b/typings/tests/helpers/Playwright.types.ts index d7fda10af..c3ee53627 100644 --- a/typings/tests/helpers/Playwright.types.ts +++ b/typings/tests/helpers/Playwright.types.ts @@ -55,7 +55,6 @@ expectType>(playwright.handleDownloads(str)); expectType(playwright.click(str)); expectType(playwright.click(str, str)); expectType(playwright.click(str, null, { position })); -expectType(playwright.clickLink()); expectType(playwright.forceClick(str)); expectType(playwright.focus(str)); expectType(playwright.blur(str)); diff --git a/typings/tests/helpers/PlaywrightTs.types.ts b/typings/tests/helpers/PlaywrightTs.types.ts index 2dadbbd17..da631d33f 100644 --- a/typings/tests/helpers/PlaywrightTs.types.ts +++ b/typings/tests/helpers/PlaywrightTs.types.ts @@ -53,7 +53,6 @@ expectType>(playwright.handleDownloads(str)); expectType>(playwright.click(str)); expectType>(playwright.click(str, str)); expectType>(playwright.click(str, null, { position })); -expectType>(playwright.clickLink()); expectType>(playwright.forceClick(str)); expectType>(playwright.focus(str)); expectType>(playwright.blur(str));