diff --git a/e2e/testcafe-devextreme/eslint-rules/no-is-ready-without-expect.js b/e2e/testcafe-devextreme/eslint-rules/no-is-ready-without-expect.js new file mode 100644 index 000000000000..67d56418f669 --- /dev/null +++ b/e2e/testcafe-devextreme/eslint-rules/no-is-ready-without-expect.js @@ -0,0 +1,71 @@ +/** + * ESLint rule: no-is-ready-without-expect + * + * Disallows calling `.isReady()` outside of `t.expect()`. + * + * Correct: + * await t.expect(dataGrid.isReady()).ok() + * or + * await t + * .expect(dataGrid.isReady()) + * .ok() + * + * Incorrect: + * await dataGrid.isReady() + * dataGrid.isReady() + * const ready = await dataGrid.isReady() + */ + +/* eslint-disable spellcheck/spell-checker */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow calling .isReady() outside of t.expect()', + }, + messages: { + noIsReadyWithoutExpect: + 'isReady() must be used inside t.expect(), e.g.: await t.expect(widget.isReady()).ok()', + }, + schema: [], + }, + create(context) { + return { + CallExpression(node) { + const { callee } = node; + + if ( + callee.type !== 'MemberExpression' + || callee.property.type !== 'Identifier' + || callee.property.name !== 'isReady' + ) { + return; + } + + // Walk up to find if this call is an argument of .expect() + let current = node; + let parent = current.parent; + + while(parent) { + if ( + parent.type === 'CallExpression' + && parent.callee.type === 'MemberExpression' + && parent.callee.property.type === 'Identifier' + && parent.callee.property.name === 'expect' + ) { + // isReady() is inside .expect() — allowed + return; + } + + current = parent; + parent = current.parent; + } + + context.report({ + node, + messageId: 'noIsReadyWithoutExpect', + }); + }, + }; + }, +}; diff --git a/e2e/testcafe-devextreme/eslint.config.mjs b/e2e/testcafe-devextreme/eslint.config.mjs index 232738cff5e5..4fa3cef7e089 100644 --- a/e2e/testcafe-devextreme/eslint.config.mjs +++ b/e2e/testcafe-devextreme/eslint.config.mjs @@ -13,6 +13,8 @@ import spellCheckConfig from 'eslint-config-devextreme/spell-check'; import typescriptConfig from 'eslint-config-devextreme/typescript'; import testcafeConfig from 'eslint-config-devextreme/testcafe'; +import noIsReadyWithoutExpect from './eslint-rules/no-is-ready-without-expect.js'; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compatibility = new FlatCompatibility({ @@ -258,4 +260,22 @@ export default [ '@typescript-eslint/no-empty-object-type': 'off', } }, + { + files: [ + 'tests/dataGrid/**/*.ts', + 'tests/cardView/**/*.ts', + 'tests/common/treeList/**/*.ts', + 'tests/common/filterBuilder/**/*.ts', + ], + plugins: { + 'local': { + rules: { + 'no-is-ready-without-expect': noIsReadyWithoutExpect, + }, + }, + }, + rules: { + 'local/no-is-ready-without-expect': 'error', + }, + }, ]; diff --git a/e2e/testcafe-devextreme/tests/cardView/editing/editing.functional.ts b/e2e/testcafe-devextreme/tests/cardView/editing/editing.functional.ts index 1fdfc9566c0b..37416eca3586 100644 --- a/e2e/testcafe-devextreme/tests/cardView/editing/editing.functional.ts +++ b/e2e/testcafe-devextreme/tests/cardView/editing/editing.functional.ts @@ -30,10 +30,14 @@ const config = { test('should show default values in popup fields after onInitNewCard', async (t) => { const cardView = new CardView(CARD_VIEW_SELECTOR); - await cardView.isReady(); + await t + .expect(cardView.isReady()) + .ok(); await t.click(cardView.getToolbar().getAddButton().element); - await cardView.isReady(); + await t + .expect(cardView.isReady()) + .ok(); const popup = cardView.getEditingPopup(); diff --git a/e2e/testcafe-devextreme/tests/common/filterBuilder/filterBuilderScrolling.ts b/e2e/testcafe-devextreme/tests/common/filterBuilder/filterBuilderScrolling.ts index 67ffd8590506..fdb1449cae8d 100644 --- a/e2e/testcafe-devextreme/tests/common/filterBuilder/filterBuilderScrolling.ts +++ b/e2e/testcafe-devextreme/tests/common/filterBuilder/filterBuilderScrolling.ts @@ -11,7 +11,9 @@ fixture.disablePageReloads`Filter Builder Scrolling Test` test('FilterBuilder - The field drop-down closes with the page scroll', async (t) => { const filterBuilder = new FilterBuilder('#container'); - await filterBuilder.isReady(); + await t + .expect(filterBuilder.isReady()) + .ok(); await t .click(filterBuilder.getItem('operation')) diff --git a/e2e/testcafe-devextreme/tests/common/treeList/adaptiveRow.ts b/e2e/testcafe-devextreme/tests/common/treeList/adaptiveRow.ts index 34f0b55e0620..8b44c2af0252 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/adaptiveRow.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/adaptiveRow.ts @@ -7,7 +7,9 @@ fixture.disablePageReloads`Adaptive Row` test.meta({ browserSize: [400, 400] })('Should be shown and hidden when the window is resized', async (t) => { const treeList = new TreeList('#container'); - await treeList.isReady(); + await t + .expect(treeList.isReady()) + .ok(); const adaptiveButton = treeList.getAdaptiveButton(); await t.expect(adaptiveButton.exists).ok(); diff --git a/e2e/testcafe-devextreme/tests/common/treeList/rowDragging.ts b/e2e/testcafe-devextreme/tests/common/treeList/rowDragging.ts index 44d70c434112..31e4e86eb10b 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/rowDragging.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/rowDragging.ts @@ -67,7 +67,9 @@ test('TreeList - Expand/collapse mechanism breaks after dragging action in the s test(`TreeList - The W1025 warning occurs when dragging a row (height: ${height ?? 'not set'}). (T1280519)`, async (t) => { const treeList = new TreeList('#container'); - await treeList.isReady(); + await t + .expect(treeList.isReady()) + .ok(); await treeList.moveRow(0, 10, 10, true); diff --git a/e2e/testcafe-devextreme/tests/common/treeList/toast.ts b/e2e/testcafe-devextreme/tests/common/treeList/toast.ts index 27cfae640a2d..93e47076e37a 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/toast.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/toast.ts @@ -10,7 +10,10 @@ fixture.disablePageReloads`Toasts in TreeList` test('Toast should be visible after calling and should be not visible after default display time', async (t) => { const treeList = new TreeList('#container'); const { takeScreenshot, compareResults } = createScreenshotsComparer(t); - await treeList.isReady(); + await t + .expect(treeList.isReady()) + .ok(); + await treeList.apiShowErrorToast(); await t.expect(treeList.getToast().exists).ok(); @@ -21,5 +24,5 @@ test('Toast should be visible after calling and should be not visible after defa .ok(compareResults.errorMessages()); await t.expect(treeList.getToast().exists).notOk(); }).before(async () => { - createWidget('dxTreeList', {}); + await createWidget('dxTreeList', {}); }); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/adaptivity/functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/adaptivity/functional.ts index 5d4f1dba5d78..3c0264f41b60 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/adaptivity/functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/adaptivity/functional.ts @@ -7,7 +7,9 @@ fixture.disablePageReloads`Adaptivity.Functional` test.meta({ browserSize: [400, 400] })('Should be shown and hidden when the window is resized', async (t) => { const dataGrid = new DataGrid('#container'); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); const adaptiveButton = dataGrid.getAdaptiveButton(); await t.expect(adaptiveButton.exists).ok(); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnChooser.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnChooser.ts index 8a7f5e0126e0..313213436f50 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/columnChooser.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnChooser.ts @@ -277,7 +277,10 @@ test( test('ColumnChooser should receive and render custom texts', async (t) => { const dataGrid = new DataGrid('#container'); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); + const columnChooserBtn = dataGrid.getColumnChooserButton(); await t.click(columnChooserBtn); const columnChooser = dataGrid.getColumnChooser(); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/visual.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/visual.ts index fc5cfd45a1b0..7e947f6e715b 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/visual.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/visual.ts @@ -305,6 +305,7 @@ test('DataGrid cell with checkbox should have outline on focused', async (t) => const scrollTo = async (y) => { await dataGrid.scrollTo(t, { y }); + // eslint-disable-next-line local/no-is-ready-without-expect return dataGrid.isReady(); }; diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/functional.ts index 420b63d4d106..ce3203e46fa1 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/functional.ts @@ -56,7 +56,9 @@ test('Filter Row\'s Reset button does not work after a custom filter is set in F const dataGrid = new DataGrid('#container'); const filterCell = dataGrid.getFilterCell(0); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t .expect(dataGrid.dataRows.count) @@ -107,7 +109,9 @@ test('DataGrid - filter row\'s search-box\'s aria-label should be customizable v const dataGrid = new DataGrid('#container'); const filterCell = dataGrid.getFilterCell(0); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); const ariaLabel = await filterCell.menuButton.getAttribute('aria-label'); @@ -138,7 +142,9 @@ test('DataGrid - NVDA reads filter menu items as "Search box 1 of 8" (T1290386)' const dataGrid = new DataGrid('#container'); const filterCell = dataGrid.getFilterCell(0); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t .expect(filterCell.menuButton.getAttribute('aria-label')) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/visual.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/visual.ts index cdd90ab7d7e6..5a0571d11bae 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/visual.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/filterRow/visual.ts @@ -98,7 +98,9 @@ test('DataGrid - The `between` filter dropdown sticks to the viewport edge durin const dataGrid = new DataGrid('#container'); const filterCell = dataGrid.getFilterCell(0); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t .click(filterCell.menuButton) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/grouping/calculateGroupValueRuntimeChanges.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/grouping/calculateGroupValueRuntimeChanges.ts index 1b5f239c5763..fb174c7d5602 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/grouping/calculateGroupValueRuntimeChanges.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/grouping/calculateGroupValueRuntimeChanges.ts @@ -12,7 +12,9 @@ test( async (t) => { const dataGrid = new DataGrid(DATA_GRID_SELECTOR); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await dataGrid.apiColumnOption('group', 'calculateGroupValue', () => 'ALL'); await t @@ -57,7 +59,9 @@ test( async (t) => { const dataGrid = new DataGrid(DATA_GRID_SELECTOR); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await dataGrid.apiColumnOption('group', 'calculateGroupValue', () => 'ALL'); await t @@ -100,7 +104,9 @@ test( async (t) => { const dataGrid = new DataGrid(DATA_GRID_SELECTOR); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await dataGrid.apiColumnOption('group', 'calculateGroupValue', () => 'ALL'); await t @@ -154,7 +160,9 @@ test( async (t) => { const dataGrid = new DataGrid(DATA_GRID_SELECTOR); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await dataGrid.apiColumnOption('group', 'calculateGroupValue', () => 'ALL'); await t @@ -204,7 +212,9 @@ test( test('Should not reset sorting parameters after calculateGroupValue update [T1298901]', async (t) => { const dataGrid = new DataGrid(DATA_GRID_SELECTOR); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t .expect(await dataGrid.apiColumnOption('A', 'sortOrder')) @@ -237,7 +247,9 @@ test('Should not reset sorting parameters after calculateGroupValue update [T129 test('Should not reset multiple sorting parameters after calculateGroupValue update [T1298901]', async (t) => { const dataGrid = new DataGrid(DATA_GRID_SELECTOR); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t .expect(await dataGrid.apiColumnOption('A', 'sortOrder')) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/headerFilter/headerFilter.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/headerFilter/headerFilter.ts index ea1e6720d2c8..0dd3238e411f 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/headerFilter/headerFilter.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/headerFilter/headerFilter.ts @@ -335,7 +335,9 @@ test('DataGrid – Header filters show "No data to display" when "not and" or "n const headerFilter = new HeaderFilter(); const listCount = headerFilter.getList().getItems(); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t.click(headerFilterButton); await t diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/markup/T1163515_alternateRowGroupBorders.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/markup/T1163515_alternateRowGroupBorders.ts index b8811bd68d63..7eb63470a801 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/markup/T1163515_alternateRowGroupBorders.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/markup/T1163515_alternateRowGroupBorders.ts @@ -345,7 +345,9 @@ const verifyGridStyles = async (t: TestController, dataGrid: DataGrid, { const functionalTest = (matrixOptions: MatrixOptions) => { test(`Should have correct applied styles with ${getTestParams(matrixOptions)}`, async (t) => { const dataGrid = new DataGrid(`#${SELECTORS.gridContainer}`); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await verifyGridStyles(t, dataGrid, matrixOptions); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/toast.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/toast.ts index f23f49fb6085..d459dc98cc9f 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/toast.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/toast.ts @@ -11,7 +11,10 @@ fixture.disablePageReloads`Toasts in DataGrid`.page( test('Toast should be visible after calling and should be not visible after default display time', async (t) => { const dataGrid = new DataGrid('#container'); const { takeScreenshot, compareResults } = createScreenshotsComparer(t); - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); + await dataGrid.apiShowErrorToast(); await t.expect(dataGrid.getToast().exists).ok(); await testScreenshot(t, takeScreenshot, 'ai-column__toast__at-the-right-position.png', { element: dataGrid.element }); @@ -20,5 +23,5 @@ test('Toast should be visible after calling and should be not visible after defa .ok(compareResults.errorMessages()); await t.expect(dataGrid.getToast().exists).notOk(); }).before(async () => { - createWidget('dxDataGrid', {}); + await createWidget('dxDataGrid', {}); }); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/sticky/common/withGrouping.ts b/e2e/testcafe-devextreme/tests/dataGrid/sticky/common/withGrouping.ts index 0ecee46af66f..659a181837c9 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/sticky/common/withGrouping.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/sticky/common/withGrouping.ts @@ -249,7 +249,9 @@ test('DataGrid - Group row content is scrolled if repaintChangesOnly is enabled const groupRow = dataGrid.getGroupRow(0); const groupPanelToggle = groupRow.getCell(0).element; - await dataGrid.isReady(); + await t + .expect(dataGrid.isReady()) + .ok(); await t .click(groupPanelToggle);